源码级剖析Vue-初始化流程-数据绑定

前言

大家好,我叫good。本章节适合阅读过部分源码,但理解不是很透彻的同学。

源码分析分为以下几个部分,之后会更新后续篇章

  1. Vue初始化流程-数据绑定
  2. Vue模板编译原理
  3. Vue依赖收集原理
  4. Vue全局钩子函数以及生命周期钩子的原理
  5. Vue diff核心流程原理

Vue响应式原理

初始化流程分析:
初始化实例 -> 初始化状态 -> 绑定数据

这个就是我们的模板

let vm = new Vue({
    el: '#app',
    data() { // 根组件中data是一个函数
        return {
            name: 'vue',
            obj: {
                name: 'obj'
            },
            ary: ['one', {
                message: 'inAry'
            }]
        }
    }
});
复制代码

初始化实例

闲话不多扯,直接上代码

import { initMixin } from './init'

// 在new Vue的时候会执行实例中的_init方法,然后初始化配置
function Vue (options) {
  // if (process.env.NODE_ENV !== 'production' &&
  //   !(this instanceof Vue) // 如果this不是Vue实例就抛错
  // ) {
  //   warn('Vue is a constructor and should be called with the `new` keyword')
  // }
  this._init(options); // 将配置对象传递过去
}

initMixin(Vue); // new Vue之前,将Vue传递过去
复制代码

如果不分文件模块划分, 在代码特别多的情况下将难以管理

进入到_init方法

export function initMixin (Vue) {
  Vue.prototype._init = function (options) {
    const vm = this
    
    // 省略校验源码...
    // if (options && options._isComponent) { 我们初始化页面不是组件所以走 else
    //   initInternalComponent(vm, options)
    // } else { 
    // 我们后面章节再详细说合并的事情,先知道把options挂载到实例上
      vm.$options = mergeOptions(  
        resolveConstructorOptions(vm.constructor),
        options || {},
        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') // 生命周期钩子
    
    // 页面挂载逻辑 
    // 在配置中传递el 等价于 new Vue().$mount('#app')
    // 最后还是会走到 $mount方法
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
复制代码

_init 做的事情主要是初始化状态和挂载页面,当然还有其他的初始化流程。我们后续会一一讲解

初始化状态

进入initState方法

export function initState (vm) {
  vm._watchers = []
  const opts = vm.$options
  // 由于我们模板只有data就先省略其他的源码
  if (opts.data) { 
    initData(vm) // 将vm传递进去 绑定data中的属性
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
}
复制代码

进入 initData方法

const sharedPropertyDefinition = { // Object.defineProperty的第三个参数
  enumerable: true,
  configurable: true,
  get: () => {},
  set: () => {}
};
/* 代理函数proxy */
export function proxy (target, sourceKey, key) {
                     //  vm , `_data`, key 为了清晰表明参数,注意看`_data`是字符串其他是变量
                     
  // 重新对sharedPropertyDefinition的get/set赋值
  sharedPropertyDefinition.get = function proxyGetter () { 
    return this[sourceKey][key]; // 相当于代理到 vm['_data'][key]
  }
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;  // 等价于 vm['_data'][key] = val
  }
  // 调用defineProperty进行代理,当我们调用 vm[key] 时就会调用get/set方法 
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

/* initData 方法 */
function initData (vm) {
  let data = vm.$options.data; // 将vm.$options.data 给到 data
    
  // 将结果赋值给 data 和 vm._data
  data = vm._data = typeof data === 'function'  // 如果是一个函数那么先将他执行再返回
    ? getData(data, vm)  // 我们的data是一个函数,所以先执行在返回结果
    : data || {}
  
  // 省略校验源码...
  
  const keys = Object.keys(data); // 获取data中的key
  let i = keys.length
  while (i--) {
    const key = keys[i]; //获取keys中的当前i这一项
    
    // 省略校验源码...
    
    // 如果不是 _ 和 $ 开头我们才将data[key] 代理到 vm 上
    if (!isReserved(key)) { 
      proxy(vm, `_data`, key);  // 通过vm.xxx直接能直接访问到data.xxx的属性
    }
  }
  // 数据绑定的核心方法
  observe(data, true /* asRootData */)
}
复制代码

绑定数据

  • 数据绑定的核心

进入observer(核心方法)

export function observe (value, asRootData) {
  if (!isObject(value) || value instanceof VNode) { // 如果不是一个对象类型或者是一个VNode实例
    return
  }
  
  let ob;
  // 如果已经有__ob__这个歌属性,并且属性值是Observer的实例
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { 
    ob = value.__ob__;  // 直接将值赋值到ob
  } else if ( 
    shouldObserve && // 是否观察,默认是true
    !isServerRendering() && // 不是服务器渲染
    (Array.isArray(value) || isPlainObject(value)) && // vlaue是一个数组或对象
    Object.isExtensible(value) && // vlaue是个可增加新属性的对象
    !value._isVue // value不是Vue
  ) {
    ob = new Observer(value); // 创建一个观察者观察value
  }
  
  if (asRootData && ob) { // 初始化绑定数据asRootData是true
    ob.vmCount++
  }
  return ob
}
复制代码

进入Observer方法

import { arrayMethods } from './array'

export const hasProto = '__proto__' in {}; // 是否可用__proto__

function protoAugment (target, src) { // 将目标原型链的指向src
  target.__proto__ = src
}

export class Observer {
  constructor (value) {
    this.value = value
    // this.dep = new Dep(); 依赖收集,下边会讲但现在先忽略
    // this.vmCount = 0
    
    def(value, '__ob__', this); // 粗略的认为将value['__ob__'] = this
    if (Array.isArray(value)) { // 对数组进行不同处理
      if (hasProto) { // 可用__proto__
        protoAugment(value, arrayMethods) // 将value的原型链指向arrayMethods
      }
      this.observeArray(value); // 监听数组中含有对象类型的值
    } else { // 对象直接获取到key监听属性
      this.walk(value)
    }
  }

  walk (obj) {
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) {
      defineReactive(obj, keys[i]); // 将对象每一个值进行绑定
    }
  }

  observeArray (items) {
    for (let i = 0, l = items.length; i < l; i++) {
      // 递归观察数组每一项,如果不是对象会退回来
      observe(items[i]); 
    }
  }
}

export function defineReactive (obj, key, val) {
  // const dep = new Dep(); 对象依赖收集,这个后续再讲

  // getOwnPropertyDescriptor 获取一个自身描述对象
  const property = Object.getOwnPropertyDescriptor(obj, key);
  // 自身描述对象属性不可被改变和删除,什么都不做直接返回
  if (property && property.configurable === false) { 
    return
  }
 
  
  const getter = property && property.get;
  const setter = property && property.set;
  if ((!getter || setter) && arguments.length === 2) {
    /*
     * 这里有几种情况:
     *   1. 我们在外面对属性定义了getter,那么vue会将我们的getter存储起来,并且有setter
     *   2. 没有getter那么vue就会将obj[key] 赋值给 val,并且有setter
     *   3. getter/setter都没有,直接 赋值val
     *   4. getter有但setter没有,不会走到这里
     */ 
    val = obj[key];
  }

  // 递归监听val
  let childOb = !shallow && observe(val);
  
  Object.defineProperty(obj, key, {  // 绑定数据
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      // 只要我们传递了getter函数,就以我们传递getter返回值为准
      // 但没传递getter的话,它就会去让 val = obj[key]
      const value = getter ? getter.call(obj) : val
      // if (Dep.target) { // 收集依赖,先忽略它
      //   dep.depend()
      //   if (childOb) {
      //     childOb.dep.depend()
      //     if (Array.isArray(value)) {
      //       dependArray(value)
      //     }
      //   }
      // }
      // 将内容返回
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      // 参数校验
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter()
      }
      if (getter && !setter) return
      if (setter) { // 通知我们定义的setter执行,并且将newVal传递过去
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal); // 将newVal重新监听起来
      // dep.notify() 更新视图,后面说依赖收集原理再说
    }
  })
}
复制代码

arrayMethods数组变异方法

  • 上面处理数组的protoAugment(value, arrayMethods)中arrayMethods就是来自这里的
  • 变异方法为可以改变原数组的数组原型方法
const arrayProto = Array.prototype; // 获取数组的原型
export const arrayMethods = Object.create(arrayProto); // 创建一个空对象,并且原型指向arrayProto

const methodsToPatch = [ // 除了这些7个方法其他的都无法改变原数组
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]; // 获取数组原型上的方法
  
  // 粗略的认为将arrayMethods[method] = function mutator (...args) {...}
  def(arrayMethods, method, function mutator (...args) { // args是一个数组,里面的参数是数组方法的参数
   // 谁调用这个mutator方法,this就是谁,当前只是声明
    const result = original.apply(this, args); // 调用了源数组的方法,只是通过apply将this改成了当前调用它的数组
    const ob = this.__ob__; // 获取 __ob__属性,之前在创建Observer中赋值过了。如果忘记可以去上个代码块回顾
    let inserted;
    switch (method) { 
      case 'push':
      case 'unshift':
      // 将数组新增的值赋值给inserted
        inserted = args;  
        break
      case 'splice':
      // 将数组新增的值第二项以上截取出来赋值给 inserted
        inserted = args.slice(2);  // 例如: [0, 0, 4].slice(2) 截取出 [4]
        break
    }
    // 如果inserted有值,将数组新增的值监听起来
    if (inserted) ob.observeArray(inserted); 
    
    // ob.dep.notify() 通知依赖视图更新数据,这个后续篇章会讲到
    return result
  });
});
复制代码

总结

  1. Vue之所以可以直接通过vm.xxx访问到data中的属性,全都是proxyvm._data的属性代理到vm上。
  2. Vue劫持配置对象中的data对象,然后获取data的key监听绑定。如果data[key] 也是一个对象数据类型的值,将其递归observe(data[key]),如果是一个数组的话,不会直接循环索引监听,而是重定向数组原型链,增加7个变异方法push/pop/unshift/shift/splice/reverse/sort,让观察者实例的observeArray内部循环数组每一项,将每一项调用observe递归监听

结尾

感谢您看到最后!如果有什么不正确的地方欢迎在评论区或私信本人改正,谢谢您!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享