Vue 2.6.13 源码解析

Posted 白瑕

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue 2.6.13 源码解析相关的知识,希望对你有一定的参考价值。

文章目录


前言

首篇提到initProvideinitInjections中间为何调用initState, 那么initState里做了什么?
这篇因为函数调用很多所以篇幅长, 但因为只是一个子模块, 所以函数的复杂性不及上篇.

思路的话, 这些函数的大部都是在处理数据格式问题, 核心基本就是最后的一两次调用.
比如initDatainitPropsobserveObserver判断和处理数据格式问题, 响应式核心依靠defineReactive()(核心也是defineProperty), initComputed核心defineProperty().

规范了一下结构, 第一篇感觉写的很乱, 这次采用了多级标题, 子标题函数父标题函数的内部调用.

不用自己写例子, Vue源码example目录下的页面基本能满足本章的输出测试需求, 记得修改引入的vue.min.jsdist下的vue.js, 打包完用LiveServer打开页面即可.


一、initState

初始化Props, methods, data, computed, watch

export function initState (vm: Component) 
  vm._watchers = []
  const opts = vm.$options
  
  if (opts.props) initProps(vm, opts.props) 
  // 初始化props
  if (opts.methods) initMethods(vm, opts.methods)
  // 初始化methods
  if (opts.data)  
  // 如果data存在, 那么初始化data
    initData(vm)
   else  
  // data不存在则返回defineReactive()的返回值
    observe(vm._data = , true /* asRootData */)
  
  if (opts.computed) initComputed(vm, opts.computed)
  // computed存在则初始化computed
  if (opts.watch && opts.watch !== nativeWatch) 
  // watch存在则初始化watch
    initWatch(vm, opts.watch)
  


二、支线

2.1.initProps

function initProps (vm: Component, propsOptions: Object)  // vm, vm.$options.props
  const propsData = vm.$options.propsData || 
  /*
    propsData :[
      todoList: [
        0: 
          id: 0,
          text: '工作'
        ,
        1: 
          id: 1,
          text: '早饭'
        
      ]
    ]
  */
  const props = vm._props = 
  
  const keys = vm.$options._propKeys = []
  const isRoot = !vm.$parent
  if (!isRoot) 
    toggleObserving(false)
  
  for (const key in propsOptions) 
  /* 
    propsOptions: 
      color:  type: null ,
      visible:  type: null 
    
 */
    keys.push(key) // keys内为所有props参数名
    const value = validateProp(key, propsOptions, propsData, vm) // 所有遍历的props的新值
	/* value: [
	  0: 
	    id: 1,
	    text: 'Hello',
	  ,
	  0: 
	    id: 2,
	    text: 'World',
	  
	] */
    if (process.env.NODE_ENV !== 'production') 
      // 生产模式略
     else 
    // 上面提到如果没有data就直接将data设为defineReactive(), 现在有的话将每个值设置为响应式
      defineReactive(props, key, value)
    
    if (!(key in vm))  // 如果指定的属性在指定的对象或其原型链中, in运算符返回true
      proxy(vm, `_props`, key) // // 代理key到vm
    
  
  toggleObserving(true)


2.1.1.initProps—defineReactive

/**
 * 在一个对象上定义响应式属性
 */
export function defineReactive (
  obj: Object,
  key: string, // 当前遍历的props属性名
  val: any, // 当前遍历的props值, 不一定最新
  customSetter?: ?Function,
  shallow?: boolean
) 
  const dep = new Dep() // 实例化Dep
  // Dep接收Observer的变化通知并向订阅它的Watcher分发变化通知
  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) 
    return
  

  // cater for pre-defined getter/setters
  const getter = property && property.get // getter setter这俩在很多时候是undefined, 也可以是undefined
  const setter = property && property.set
  if ((!getter || setter) && arguments.length === 2) 
    val = obj[key]
  

  let childOb = !shallow && observe(val) // val每次是data中的一个属性值
  Object.defineProperty(obj, key, 
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () 
      const value = getter ? getter.call(obj) : val 
      // props情况和computed情况因为有更新值的需求所以有set和get方法, 由sharedPropertyDefinition所设, 参考2.1.2和2.4.1
      if (Dep.target) 
        dep.depend() // 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
      /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) 
        return
      
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) 
        customSetter()
      
      // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) 
        setter.call(obj, newVal) // 如果有setter就将当前newVal更新到当前key上
       else  
      // 没有setter那就不是props或者computed情况, data之类直接设置当前值为最新值
        val = newVal
      
      childOb = !shallow && observe(newVal) // 见2.3.2.1
      dep.notify() // 见2.4.1.1
    
  )


2.1.2.initProps—proxy

props属性名定义到vm上并且挨个对应他们自己的setget, sharedPropertyDefinition全局数组.

const sharedPropertyDefinition = 
  enumerable: true,
  configurable: true,
  get: noop,
  set: noop

export function proxy (target: Object, sourceKey: string, key: string)  // vm, `_props`, 当前props名
  sharedPropertyDefinition.get = function proxyGetter () 
    return this[sourceKey][key] // sharedPropertyDefinition[_`props`][props名]
  
  sharedPropertyDefinition.set = function proxySetter (val) 
    this[sourceKey][key] = val // sharedPropertyDefinition[_`props`][props名] = val
  
  Object.defineProperty(target, key, sharedPropertyDefinition)

这种情况下defineReactive中的property.getset就不是undefined了.


2.1.3.initProps—dependArray

特殊的 用于收集数组元素依赖的方法.

/**
 * Collect dependencies on array elements when the array is touched, since
 * we cannot intercept array element access like property getters.
 */
function dependArray (value: Array<any>) 
  for (let e, i = 0, l = value.length; i < l; i++) 
    e = value[i]
    e && e.__ob__ && e.__ob__.dep.depend()
    if (Array.isArray(e))  
    // 如果还有数组就继续重来, 不是就到上一行三个值纳入依赖depend()结束
      dependArray(e)
    
  


2.2. initMethods

前面都在判重, 核心是最后的bind().

function initMethods (vm: Component, methods: Object)  // vm, vm.$options.methods
  const props = vm.$options.props
  /* props
  
    color: 
      id: 1,
      text: 'Hello'
    ,
    text: 
      id: 2,
      text: 'World'
    
  ,
  */
  for (const key in methods) 
    if (process.env.NODE_ENV !== 'production') 
      if (typeof methods[key] !== 'function') 
        warn(
          `Method "$key" has type "$typeof methods[key]" in the component definition. ` +
          `Did you reference the function correctly?`,
          vm
        )
      
      if (props && hasOwn(props, key)) 
        warn(
          `Method "$key" has already been defined as a prop.`,
          vm
        )
      
      if ((key in vm) && isReserved(key)) 
        warn(
          `Method "$key" conflicts with an existing Vue instance method. ` +
          `Avoid defining component methods that start with _ or $.`
        )
      
    
    vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)
    // vm[key]即当前遍历的key对应的methods函数, 判定其是否为函数类型并判重
    // 重的话直接vm[key] = noop, 不重的话
  


2.2.1.initMethods----bind

src/core/observer/index.js

export const bind = Function.prototype.bind ? nativeBind : polyfillBind

function nativeBind (fn: Function, ctx: Object): Function  // methods函数, vm
  return fn.bind(ctx)


function polyfillBind (fn: Function, ctx: Object): Function 
  function boundFn (a) 
    const l = arguments.length
    return l
      ? l > 1
        ? fn.apply(ctx, arguments)
        : fn.call(ctx, a)
      : fn.call(ctx)
  

  boundFn._length = fn.length
  return boundFn


2.3.initData

完整代码:

function initData (vm: Component)  // vm
  let data = vm.$options.data // 函数mergedInstanceDataFn ()
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || 
  /* data格式: 
	   options: [
		  id: 0, name: 'color' ,
		  id: 1, name: 'color' 
	   ],
	   selected: 0
     
  */
  if (!isPlainObject(data))  // !true
    data = 
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    )
  

  const keys = Object.keys(data) // data中的属性名构成数组keys
  const props = vm.$options.props
  /* props
    
      color: 
        id: 1,
        text: 'Hello'
      ,
      text: 
        id: 2,
        text: 'World'
      
    ,
  */
  const methods = vm.$options.methods
  /* methods:
    
  	  handleClick: function () ,
  	  handleBlur: function() 
  	  ...
	
  */
  let i = keys.length
  while (i--)  // 挨个检查data中的属性名有没有和props或者methods重名
    const key = keys[i]
    if (process.env.NODE_ENV !== 'production') 
      if (methods && hasOwn(methods, key)) 
        warn(
          `Method "$key" has already been defined as a data property.`,
          vm
        )
      
    
    if (props && hasOwn(props, key)) 
      process.env.NODE_ENV !== 'production' && warn(
        `The data property "$key" is already declared as a prop. ` +
        `Use prop default value instead.`,
        vm
      )
     else if (!isReserved(key)) 
      proxy(vm, `_data`, key)
    
  
  // observe data
  observe(data, true /* asRootData */)
  /*
    data最终被传入new Obsever(value), 然后因为是一个对象走了else线this.walk(value),
    内部属性被循环defineReactive() 
  */


2.3.1.initData—observe

需要过一遍这里基本是因为格式可能不能直接处理, 需要判定.

export function observe (value: any, asRootData: ?boolean): Observer | void 
/* value格式: 
  
    options: [
	   id: 0, name: 'color' ,
	   id: 1, name: 'color' 
	],
	selected: 0
  
*/
// asRootData: true
  if (!isObject(value) || value instanceof VNode)  
  // 这一句导致进到下面的只有对象数组
    return
  
  let ob: Observer | void
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) 
    // 如果value上有__ob__说明该value已经进行过观察, 直接返回value.__ob__
    ob = value.__ob__
   else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value._isVue
  ) 
    // 如果没被观察过, 那么创建观察者实例, 劫持监听所有属性并向Dep通知变化
    ob = new Observer(value)
  
  if (asRootData && ob) 
    ob.vmCount++
  
  return ob


2.3.2.1.initData—observe—Observer类

export class Observer 
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) 
    this.value = value
    this.dep = new Dep() // 实例化一个Dep
    this.vmCount = 0
    def(value, '__ob__', this) // 为value defineProperty: __ob__
    /* value格式:
      
        input: "Hello"
        __ob__:  
          value: 
            input: "Hello",
            dep: Dep,
            vmCount: 1 
          
        
      
    */
    if (Array.isArray(value))  // 由上不是value
      if (hasProto) 
        protoAugment(value, arrayMethods)
       else 
        copyAugment(value, arrayMethods, arrayKeys)
      
      this.observeArray(value)
     else 
      this.walk(value) 
      // 提取value全部键组成数组, 然后循环将每个值传入defineReactive(obj, keys[i])做响应式处理(见下walk)
    
  

  walk (obj: Object) 
    const keys = Object.keys(obj)
    for (let i = 0; i < keys.length; i++) 
      defineReactive(obj, keys[i])
    
  

  observeArray (items: Array<any>)  
  /* 为数组的每一项new Observer()设置观察, 如果数组内部不是对象就继续到这再来一次, 看着像个递归, 这步只是不停的依靠循环不断深入提取数据, 最终目的还是达到walk()的执行条件去执行walk(), 只有walk()里面有defineReactive(). */
    for (let i = 0, l = items.length; i < l; i++) 
      observe(items[i])
    
  


2.4.initComputed

const computedWatcherOptions =  lazy: true  // watcher类的options, 有lazy的话会懒执行

function initComputed (vm: Component, computed: Object)  // vm, vm.$options.computed

  const watchers = vm._computedWatchers = Object.Vue 2.6.13 源码解析

Vue 2.6.13 源码解析

Vue 2.6.13 源码分析

Vue 2.6.13 源码解析 ObserverDepWatcher与订阅

Vue 2.6.13 源码解析 感知变化以发起通知

Vue 2.6.13 源码解析 initLifeCycle && initEvents