深入理解Vue源码系列-2.initMixin干了什么(上)

开始

接上文,上面留了一个问题,本文会解答,就是this._init()方法在哪里定义的。 我们先看initMixin,就是上文执行完Vue声明函数之后,执行的第一个mixin。 这里直接贴重要的代码

let uid = 0
export function initMixin (Vue: Class<Component>) {
   //这里options?:object是flow的语法,类似typescript.不用太管先
   //_init就是这里挂在原型上的
   Vue.prototype._init = function (options?: Object) {
    //把实例对象给vm变量
    const vm: Component = this
    // vm 添加_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)
    }

    // 避免vm被observer的标志
    vm._isVue = true
    
    //options我们通过new Vue传过来的是{render: h => h(App),},所以执行else
    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 上绑定一个$option属性,并且合并option,去看看   (mergeOptions),
      // 合并之后,可以用$options.data访问Vue实例化传入的data  
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
        
        
    }
       
    //这里执行一个代理的过程   看(initProxy)
    /* istanbul ignore else */
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
    
      //这里的就直接用vm当自己的代理了    
      vm._renderProxy = vm
    }
       
       
       
    // 挂载自己
    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)
    }
  }
}
复制代码

mergeOptions 合并options

我们从上面的代码抽出来讲,先是将vm=this方便以后用vm代替this,接着关键点就是合并options,并把options挂在vm上。怎么合并的呢? 调用了mergeOptions, 看我写主要的部分。这里就是把options给合并了

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    //就直接返回了,没干什么
    checkComponents(child)
  }
    
  if (typeof child === 'function') {
    child = child.options
  }
  //挂载props 
  normalizeProps(child, vm)
  //注入
  normalizeInject(child, vm)
  //标准化指令
  normalizeDirectives(child)


  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }
// 主要部分,将父与子的field字段进行合并,最后还是返回了options
  const options = {}
  let key
   //合并父级属性到options
  for (key in parent) {
    mergeField(key)
  }
  //合并子级属性到options
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
   //合并函数
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }


  return options
}
复制代码

合并中有一个参数是 resolveConstructorOptions(vm.constructor),拿到函数作为参数,我们进去看看怎么搞的

export function resolveConstructorOptions (Ctor: Class<Component>) {

  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}
复制代码

上面我们知道Ctor就是Vue构造函数,他是没有super属性的,所以这里就先返回了options.但是这里你调用debugger可以看到Ctors.options有属性的,什么时候加上的呢?这个要等我们调用initGlobalAPI 才知道。

( 插一句,这里用到源码调试手段,debugger.看源码利器。)

initProxy 代理

再看最_init()里面有个 initProxy(vm)函数,这里其实是一个代理的过程。 我们先讲什么是代理,最基础的用法(es6的一个语法), 下面本来调用a.name是"mik",但是代理proxy重写了get方法,直接返回了"张三",所以读取proxy.name就是张三。

 var a={"name":"mik"};
    let proxy=new  Proxy(a,{
      get:function (target,property) {
        return '张三';
      }
    })
    console.log(proxy.name)  //张三
复制代码

看了基础代理,再看Vue里面的代理

initProxy = function initProxy (vm) {
    
    //   hasProxy   true
    if (hasProxy) {
      // 用哪个handler处理代理
      const options = vm.$options
      const handlers = options.render && options.render._withStripped
        ? getHandler
        : hasHandler
      //用hasHandler,看Proxy  ,百度下js的proxy代理,就是 vm._renderProxy的改变会被映射到vm
      //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
      
      //最主要的是这里,vm 的代理处理函数handlers,在下面了
      vm._renderProxy = new Proxy(vm, handlers)
    } else {
      vm._renderProxy = vm
    }
  }

    const allowedGlobals = makeMap(
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
    'require' 
  )
     
  const hasHandler = {
    has (target, key) {
      //是否有这个属性
      const has = key in target
      //属性是否在全局里面,全局的在上面
      const isAllowed = allowedGlobals(key) ||
        (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data))
        
      if (!has && !isAllowed) {
        if (key in target.$data) warnReservedPrefix(target, key)
        else warnNonPresent(target, key)
      }
      return has || !isAllowed
    }
  }
复制代码

总结

这篇就先讲这么多,主要是options的合并和initproxy的代理。

转载于:https://juejin.im/post/5c753edbe51d455069045b15

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值