最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue2.0源码-initMixin(一)

    正文概述 掘金(可能是叫皮皮虾吧)   2020-12-22   469

    Vue initMixin

    接下来我们看看new Vue()都会发生些什么事情

    function Vue(options) {
      if (process.env.NODE_ENV !== 'production' &&
        !(this instanceof Vue)
      ) {
        warn('Vue is a constructor and should be called with the `new` keyword')
      }
      this._init(options)
    }
    
    initMixin(Vue)
    

    在我们对new Vue()时,会调用this._init()方法开始生成创建出一个vue实例,这个方法是initMixin中进行定义

    在init.js我们可以找到这个方法,我们来对这个方法来进行拆分分析

    export function initMixin(Vue: Class<Component>) {
      Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
    
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = 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')
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    }
    

    performance 性能

    下面这段代码是在window环境下,使用window.performance来进行前端性能监控

    mark方法就是window.performance.mark

    measure方法就是window.performance.measure

        vm._uid = uid++
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
        
        ...
        
         /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }
    

    我们可以这样理解上面的mark就是打点,一个开始节点,一个结束节点,然后通过measure获取两个节点之间的情况,并记录下来

    vm._name 如果是根组件拿到的就是 <Root> 如果是子组件,如<demo></demo>这样的组件,我们拿到的就是<Demo> 因此这里的measure(`vue ${vm._name} init`, startTag, endTag)就等于measure(`vue <Root> init`, startTag, endTag)

    接下来我们可以通过在控制台输入performance.getEntriesByName("vue <Root> init");来获取数据

    vue2.0源码-initMixin(一)

    如果不理解里面各参数代表的意义,我们可以在刚刚的代码上加入performance.now来查看

     let beginTime,endTime
     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          beginTime = performance.now();
          console.log('开始时间',beginTime)  //打印开始时间
          mark(startTag)
        }
        
        
         if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          endTime = performance.now();
          console.log('结束时间',endTime); //打印结束时间
          console.log('相差时间',endTime - beginTime); //打印相差时间
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
          console.log(performance.getEntriesByName("vue <Root> init"))
        }
    
    

    vue2.0源码-initMixin(一)

    由此我们便可以知道startTime就是开始时间,duration就是开始时间和结束时间的时间差

    beforeCreate 生命周期

    接下来我们看在beforeCreate之前,都进行了哪些初始化的操作

    resolveConstructorOptions(vm)

        // merge options
        if (options && options._isComponent) {
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
    

    options._isComponent是判断元素是否为组件,首先我们可以在用debugger的方式,看下这段代码做了些什么操作

    vue2.0源码-initMixin(一)

    首先看没有执行之前,vm是什么样的

    vue2.0源码-initMixin(一)

    接下来看执行之后

    vue2.0源码-initMixin(一)

    通过断点的方式,我们可以看的出来,这段代码主要是用于创建$options合并我们传入vue的options;

    initProxy(vm)

    通过刚刚的断点法,我们很快就可以知道initProxy方法,给我们vm的_renderProxy进行了赋值操作

    initProxy = function initProxy(vm) {
        if (hasProxy) {
          // determine which proxy handler to use
          const options = vm.$options;
          const handlers =
            options.render && options.render._withStripped
              ? getHandler
              : hasHandler;
          vm._renderProxy = new Proxy(vm, handlers);
        } else {
          vm._renderProxy = vm;
        }
      };
    

    我们可以先简单了解下,hasProxy是利用/native code/.test(Proxy)判断当前浏览器是否支持Proxy,不支持则将vm直接赋值给vm._renderProxy,如果在支持的情况下,先判断是否有render的写法,如果没有,则利用Proxy去改写vm的has方法,也就是let key in target这一类操作,如果存在,则改写get方法。具体可以去了解Proxy的用法。

    initProxy方法使用Proxy主要是在操作vm属性的时候,判断该属性是否存在,给出一些警告提示之类的。

    initLifecycle(vm)

    这次我们直接看源码,可以从很清楚的看出,initLifecycle主要处理组件的父子关系,定义了parentparent,parent,children,refsrefs,refs,root等等属性

    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
    }
    

    我们可以看下的具体处理方式

      let parent = options.parent
      if (parent && !options.abstract) {
        while (parent.$options.abstract && parent.$parent) {
          parent = parent.$parent
        }
        parent.$children.push(vm)
      }
    

    由于是处理父子关系,应该我们可以再定义一个组件

    vue2.0源码-initMixin(一)

    然后进行打印和断点测试都可以,我们就继续选择断点

    vue2.0源码-initMixin(一)

    首先是第一次进入,很明显parent是空的,因为这次进入的是根组件,接下来就是demo组件了

    vue2.0源码-initMixin(一)

    这个时候,我们可以很清楚的看到组件demo的vm.$options.parent是存在值的,就是我们的根组件,而在获取到父组件以后,还会判断options.abstract,这个是用来判断,父组件是否为抽象组件,如<keep-alive><transition>这一类是不会成为父组件的,因此要对此进行判断。

    还有一些属性,我们等到被赋值的时候,再来看具体效果。

    initEvents(vm)

    export function initEvents (vm: Component) {
      vm._events = Object.create(null)
      vm._hasHookEvent = false
      // init parent attached events
      const listeners = vm.$options._parentListeners
      if (listeners) {
        updateComponentListeners(vm, listeners)
      }
    }
    

    首先我们看第一句代码,这句代码创建一个以null为原型的空对象,并赋值给了_events

    vm._events = Object.create(null)
    

    第二行,主要用于判断是否存在hook类的事件,如果存在,在vm.$on的时候,会赋值为true

    vm._hasHookEvent = false
    

    下面这部分代码,我们主要看下具体作用。

     // init parent attached events
     const listeners = vm.$options._parentListeners
     if (listeners) {
     updateComponentListeners(vm, listeners)
     }
    

    我们可以先在组件上定义两个事件

    vue2.0源码-initMixin(一)

    接着进行断点,可以看出根组件进入时为undefined

    vue2.0源码-initMixin(一)

    demo组件进入时,可以看出listeners就是我们通过v-on或者@绑定在demo子组件上的事件。

    vue2.0源码-initMixin(一)

    当子组件上存在绑定的事件时,则调用updateComponentListeners,使用vm.$on的方法,将事件添加到vm.__events上,并判断是否存在hook类的事件,如果存在则将vm._hasHookEvent赋值为true

    vue2.0源码-initMixin(一)

    initRender(vm)

    export function initRender (vm: Component) {
      vm._vnode = null // the root of the child tree
      vm._staticTrees = null // v-once cached trees
      const options = vm.$options
      const parentVnode = vm.$vnode = options._parentVnode // the placeholder node in parent tree
      const renderContext = parentVnode && parentVnode.context
      vm.$slots = resolveSlots(options._renderChildren, renderContext)
      vm.$scopedSlots = emptyObject
      vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
      vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
      const parentData = parentVnode && parentVnode.data
    
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, () => {
          !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
        }, true)
        defineReactive(vm, '$listeners', options._parentListeners || emptyObject, () => {
          !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
        }, true)
      } else {
        defineReactive(vm, '$attrs', parentData && parentData.attrs || emptyObject, null, true)
        defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true)
      }
    }
    

    从源码中可以在initRender定义了$createElement这个方法,并调用defineReactive使用Object.defineProperty定义了$attrs,$listeners两个属性。

    以上就是beforeCreate之前vue进行的操作了。我们可以通过断点的方式看下在beforeCreate之前,我们可以拿到哪些值

    根组件

    vue2.0源码-initMixin(一)

    demo组件

    vue2.0源码-initMixin(一)

    以上就是beforeCreate之前的一些简单理解了,在后面我们在对created之前的操作进行分析,created分析结束以后,我们再来理一理其中的一些方法具体的实现方式。


    起源地下载网 » vue2.0源码-initMixin(一)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元