Vue中的initLifecycle(vm)方法及eventsMixin(Vue)方法

2021-02-13 JavaScript高級程序設計

 日拱一卒。

前情回顧

上篇文章大致分析了Vue 的init方法,大致的流程是,構造函數接收options,然後構造函數中調用this._init(options),之後通過initMixin(Vue)方法,在構造函數的原型上添加_init(options)方法。_init方法的主要功能是合併options,同時設置proxy。代碼框架如下:


// index.js
function Vue(options){

  this._init(options)
  // init相關
  initMixin(vue)
  // 事件相關
  eventsMixin(Vue)
  
}
// init.js
export function initMixin(Vue){
  Vue.prototype._init = function(options){
    const vm = this;
    // 合併options
    if(options){
      mergeOptions()
    }
    // 設置代理
    initProxy(vm)
    // 生命周期
    initLifecycle(vm)
    
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    ...
    
    // 最後調用$mount()
    if(vm.$options.el){
      vm.$mount(vm.$options.el)
    }
  }
}

initLifecycle(vm)

initLifecycle(vm)定義在src/core/instance/lifecycle.js中。該方法似乎什麼也沒做,只是在Vue的私有屬性上添加了默認值。該方法代碼如下:

  export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

可以看出,該方法只是設置了vm的$parent,$root,$children,$refs,_watcher,__isMounted,_isDestroyed,_isbeingDestroyed的默認值。

重點內容

執行完initLifecycle(vm)添加了屬性的默認值之後,接著執行了initEvent(vm)方法,然而我們先不考慮initEvent(vm),重點關注一下,文章開頭的eventsMixin(Vue)。

之前也知道vue的事件系統是基於發布訂閱模式,也知道發布訂閱模式的簡單實現如下:

  let event = {
    listener:[],
    add:(name,fn)=>{
      if( !this.listener[ name ] ){ 
        this.listener[name] = [] 
    }
      this.listener[name].push(fn)
    },
    trigger:function (key)=>{
      let fns = this.listener[key]
      if(!fns || fns.length ==0){
        return
      }
      for(let i = 0;i<fns.length;i++){
        fns[i].call(this,arguments)
      }
      
    }
  }

大致流程就是用數組緩存,事件名及對應的函數,然後觸發事件時,遍歷緩存列表,執行對應的函數。

eventsMixin(Vue) 這個方法就是將上面說的流程,添加到了Vue的原型上面,對外暴露了幾個API,$on,$once,$off,#emit。

$on方法,過濾掉了鉤子函數中的方法。

$off方法,當事件是字符串時,將對應的回調設置為null,當為數組時,將對應的函數從緩存列表中刪除。

$emit就不用說了,從緩存列表中找到對應的函數去執行。

源碼如下:

  export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
    const vm: Component = this
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$on(event[i], fn)
      }
    } else {
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      if (hookRE.test(event)) {
        vm._hasHookEvent = true
      }
    }
    return vm
  }

  Vue.prototype.$once = function (event: string, fn: Function): Component {
    const vm: Component = this
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    vm.$on(event, on)
    return vm
  }

  Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
    const vm: Component = this
    // all
    if (!arguments.length) {
      vm._events = Object.create(null)
      return vm
    }
    // array of events
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        this.$off(event[i], fn)
      }
      return vm
    }
    // specific event
    const cbs = vm._events[event]
    if (!cbs) {
      return vm
    }
    if (arguments.length === 1) {
      vm._events[event] = null
      return vm
    }
    if (fn) {
      // specific handler
      let cb
      let i = cbs.length
      while (i--) {
        cb = cbs[i]
        if (cb === fn || cb.fn === fn) {
          cbs.splice(i, 1)
          break
        }
      }
    }
    return vm
  }

  Vue.prototype.$emit = function (event: string): Component {
    const vm: Component = this
    if (process.env.NODE_ENV !== 'production') {
      const lowerCaseEvent = event.toLowerCase()
      if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
        tip(
          `Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}".`
        )
      }
    }
    let cbs = vm._events[event]
    if (cbs) {
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      const args = toArray(arguments, 1)
      for (let i = 0, l = cbs.length; i < l; i++) {
        try {
          cbs[i].apply(vm, args)
        } catch (e) {
          handleError(e, vm, `event handler for "${event}"`)
        }
      }
    }
    return vm
  }
}

今日總結

開卷有益,書要多讀,細讀。勤於思考總結。

最後說兩句javascript基礎知識總結

相關焦點

  • Vue中mixin怎麼理解?
    Vue中mixin怎麼理解?定義: mixin是為了讓可復用的功能靈活的混入到當前組件中,混合的對象可以包含任意組件選項(生命周期,指令之類等等), mixin翻譯過來叫混合,高級的詞彙可以叫插件入侵簡單使用// 定義一個混合對象const myMixin = {  created: function () {    this.hello
  • 2021全新 最火Vue面試題(一)(附源碼)
    默認.vue文件中的template處理是通過vue-loader來進行處理的並不是通過運行時的編譯 - 後面我們會說到默認vue項目中引入的vue.js是不帶有compiler模塊的)。}new Vue({ beforeCreate() { console.log('before create') }})源碼位置:src/core/util/options.js:146、core/instance/lifecycle.js:3365.
  • Vue.extend API(源碼級詳解)
    _init(options)}複製代碼那麼上文的例子中的new Profile()執行的就是這個方法了,因為繼承了 Vue 的原型,這裡的_init就是 Vue 原型上的_init方法,你可以在源碼目錄下src/core/instance/init.js中找到它:Vue.prototype._init = function(options?
  • 解讀 vue-class-component 源碼實現原理
    這三個都是方法,定義在原型上,需要拿到原型對象,找到這三類方法,按照特性放在指定位置。這就引發一個問題,怎麼把這些定義的屬性放在 Vue 需要解析的數據中,「上帝的歸上帝,凱撒的歸凱撒」。_init = function () {    var _this = this;    // 拿到渲染組件對象上的屬性,包括不可枚舉的屬性,包含組件內定義的 $開頭屬性 和 _開頭屬性,還有自定義的一些方法    var keys = Object.getOwnPropertyNames(vm);     // 如果渲染組件含有,props,但是並沒有放在原組件實例上
  • vue-router 實現分析
    $router 引用當前組件對應的 router 對象,該對象在初始化時(在 vm 創建過程中執行初始化),會啟動對頁面地址變更的監聽,從而在變更時更新 vm 的數據($route),進而觸發視圖的更新。深入如何實現對地址變更的監聽?
  • 面試官:說說你對vue的mixin的理解,有哪些應用場景?
    一、mixin是什麼Mixin是面向對象程序設計語言中的類,提供了方法的實現。其他類可以訪問mixin類的方法而不必成為其子類Mixin類通常作為功能模塊使用,在需要該功能時「混入」,有利於代碼復用又避免了多繼承的複雜Vue中的mixin先來看一下官方定義mixin(混入),提供了一種非常靈活的方式,來分發 Vue 組件中的可復用功能。
  • 總結 Vue 知識體系之高級技巧應用篇
    那麼怎麼系統地學習和掌握 vue 呢?為此,我做了簡單的知識體系體系總結,不足之處請各位大佬多多包涵和指正,如果喜歡的可以點個小贊!本文主要講述一些vue開發中的幾個高級應用,希望能對大家有所幫助。Vue.use我們使用的第三方 Vue.js 插件。如果插件是一個對象,必須提供install方法。
  • 怎麼使用vue中的實例屬性vm.$data
    ElementJavaScript在vue中,vm.1、打開HBuilderX工具,創建vue項目,然後在src/components新建vue文件DataAtrr.vue引入Element的樣式和模塊3、在新建的vue文件中,插入一個輸入框和一個按鈕,按鈕上綁定點擊事件
  • 詳解Vue中的computed和watch
    如果有閱讀過Vue源碼的同學對這個原因應該會比較清楚,Vue在初始化的時候會按照:initProps-> initMethods -> initData -> initComputed -> initWatch這樣的順序對數據進行初始化,並且會通過Object.definedProperty將數據定義到vm實例上,在這個過程中同名的屬性會被後面的同名屬性覆蓋
  • vue初探之初始化data
    _init(options);};//初始化Vue.prototype._init = function(options){ let vm = this; vm.$options = options; //攔截數組的方法 和 對象的屬性 //初始化函數(下面我們詳細解釋這個方法的實現) initState(vm);}初探MVVM Object.definePropertyfunction initState(vm){     //做不同的初始化工作     let opts = vm
  • Vue3.0:Lifecycle Hooks & Template Refs in Composition API 個人翻譯
    lifecycle hook by prefixing the lifecycle hook with "on".The following table contains how the lifecycle hooks are invoked inside of setup():你可以通過"on"前綴訪問組件的生命周期鉤子.
  • 教你如何以Vue.js插件的形式實現消息提示框
    在Vue-CLI 3腳手架項目中的src目錄下新建plugin文件夾,在該文件夾下新建MessageBox.vue,代碼如例1-1所示。我們知道Vue.extend()方法可以創建一個組件的「子類」,我們可以利用這個「子類」來構造一個提示框組件對象,並設置組件的數據屬性的默認值。
  • vue中的生命周期
    簡單來說,vue生命周期就是我們在瀏覽器中打開和關閉頁面的過程中,vue實例的創建和銷毀的全過程。vue裡提供了一些生命周期鉤子函數,使得開發者可以在實例創建的某個階段上插入自定義代碼,從而實現響應的功能處理。
  • 2020大廠前端面試之vue專題
    function initComputed(vm: Component, computed: Object) { const watchers = vm.是異步執行的服務端渲染不支持mounted方法,所以在服務端渲染的情況下統一放到created中10.何時需要使用beforeDestroy理解:11.Vue中模板編譯原理 function baseCompile
  • Vue進階 day01
    vue對象操作另一個vue對象的內容,維度有兩個,操作屬性和操作方法其中調用的屬性是data或computed中定義的調用其他Vue中的方法:vm中toUpCase:function () { this.title = this.title.toLocaleUpperCase(); }vm1中toUseapp:function () { vm.toUpCase(); }2.為vue實例設置屬性
  • 重學Vue源碼【Vue實例掛載的實現】
    ,把 .vue 文件編譯成JavaScript 使用。還記得 Vue在初始化的時候有一個 vm.$mount(vm.$options.el) 麼:// src/core/instance/init.jsif (vm.$options.el) {  vm.$mount(vm.
  • 配置 Vue 實例——選項
    計算屬性默認只擁有 getter 方法,但是可以自定義一個 setter 方法。<script> ... ... ...var mixin = { created: function () { console.log("mixin 鉤子") }, methods: { foo() { console.log("foo") }, conflicting() { console.log("mixin conflicting") } }}
  • Vue全家桶&vue-router原理
    原理 Vue全家桶:vue + vue-router(路由)+ vuex(狀態管理)+ axios(請求)本次主要分析並實現簡版vue-router,從源碼中學習借鑑$options.routes.forEach(route => {            this.routeMap[route.path] = route      });      const initial = window.location.hash.slice(1) || '/'      // 使用vue提供的工具方法defineReactive實現數據響應
  • 手寫Vuex核心原理,再也不怕面試官問我Vuex原理
    Store這個類擁有commit,dispatch這些方法,Store類裡將用戶傳入的state包裝成data,作為new Vue的參數,從而實現了state 值的響應式。二、基本準備工作我們先利用vue-cli建一個項目
  • Vue常見面試題攻略
    (Object.defineProperty或者Proxy),利用發布訂閱的設計模式,在getter方法中進行訂閱,在setter方法中發布通知,讓所有訂閱者完成響應。實現原理:當子組件vm實例化時,獲取到父組件傳入的slot標籤的內容,存放在vm.$slot中,默認插槽為vm.$slot.default,具名插槽為vm.$slot.xxx,xxx 為插槽名,當組件執行渲染函數時候,遇到slot標籤,使用$slot中的內容進行替換,此時可以為插槽傳遞數據,若存在數據,則可稱該插槽為作用域插槽。