后面咱们讲到了_init函数的执行流程,简略回顾下:

  • 初始化生命周期-initLifecycle
  • 初始化事件-initEvents
  • 初始化渲染函数-initRender
  • 调用钩子函数-beforeCreate
  • 初始化依赖注入-initInjections
  • 初始化状态信息-initState
  • 初始化依赖提供-initProvide
  • 调用钩子函数-created
    一共通过下面8步,init函数执行实现,开始mount渲染。

初始化状态信息

本章咱们次要解说initState函数的处理过程,咱们先看下init的主函数

function initState(vm: Component) {    vm._watchers = []    const opts = vm.$options    if (opts.props) {        initProps(vm, opts.props)    }    if (opts.methods) {        initMethods(vm, opts.methods)    }    if (opts.data) {        initData(vm)    } else {        observe(vm._data = {}, true /* asRootData */)    }    if (opts.computed) {        initComputed(vm, opts.computed)    }    if (opts.watch && opts.watch !== nativeWatch) {        initWatch(vm, opts.watch)    }}

看下面代码,先申明了一个_watchers的空数组;而后顺次判断传递进来的options是否蕴含系列参数;顺次执行initProps、initMethods、initData、initComputed、initWatch。

initProps

initProps函数次要是解决传进来的props对象,然而这个props对象是在上一篇文章中讲到的normalizeProps函数解决后的对象,不是传递进来的原对象。来看下initProps的代码:

function initProps(vm: Component, propsOptions: Object) {    const propsData = vm.$options.propsData || {}    const props = vm._props = {}    const keys = vm.$options._propKeys = []    const isRoot = !vm.$parent    if (!isRoot) {        toggleObserving(false)    }    for (const key in propsOptions) {        keys.push(key)        const value = validateProp(key, propsOptions, propsData, vm)        defineReactive(props, key, value)        if (!(key in vm)) {            proxy(vm, `_props`, key)        }    }    toggleObserving(true)}

下面代码解读:

  • 第一步获取了propsData;
  • 第二步给以后实例增加了_props属性,新增了一个props援用,指向了_props属性;
  • 第三步给以后实例减少了_propKeys属性,新增了一个keys的援用,指向了_propKeys属性;
  • 第四步判断了是否须要进行监听;
  • 遍历normalizeProps函数解决后的对象propsOptions;
    • 存储key
    • 校验props格局
    • 为以后key定义响应式的属性:defineReactive
    • 把以后key的拜访形式进步到实例下面:proxy,即能够vm.name来拜访vm._props.name
function proxy(target: Object, sourceKey: string, key: string) {    sharedPropertyDefinition.get = function proxyGetter() {        return this[sourceKey][key]    }    sharedPropertyDefinition.set = function proxySetter(val) {        this[sourceKey][key] = val    }    Object.defineProperty(target, key, sharedPropertyDefinition)}
  • 关上监听。

initMethods

initMethods办法是用来解决传递进来的methods参数,把methods绑定到以后实例下面

function initMethods(vm: Component, methods: Object) {    const props = vm.$options.props    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)    }}

下面代码解读:

  • 第一步获取了props;
  • 第二步遍历methods;
    • 判断以后method是否是函数,不是函数则在开发环境下报警
    • 判断props是否曾经有了以后method的key,如有则在开发环境下报警
    • 判断以后method是否曾经在vm下面了,并且以$或_结尾,如是,则在开发环境下报警
    • 为以后实例增加办法;

    参考Vue3源码视频解说:进入学习


initData

initData办法是用来解决传递进来的data参数,增加监听

function initData(vm: Component) {    let data = vm.$options.data    data = vm._data = typeof data === 'function'        ? getData(data, vm)        : data || {}    const keys = Object.keys(data)    const props = vm.$options.props    const methods = vm.$options.methods    let i = keys.length    while (i--) {        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)) {            // 实现代理,能够this.massage 来进行拜访this._data.message            proxy(vm, `_data`, key)        }    }    observe(data, true /* asRootData */)}

下面代码解读:

  • 第一步获取传递进来的data,判断data是否为函数,是函数则执行函数获取以后对象,否则间接读取以后对象;
  • 第二步,获取上一步的data所有的key,赋值给keys;
  • 第三步获取props;
  • 第四步获取methods;
  • 第五步,循环keys;
    • 判断是否和methods外面是否反复,反复则开发环境进行报警
    • 判断是否和props外面是否反复,反复则开发环境进行报警
    • 判断如不是以_或$结尾的key,则进行代理解决,把以后key的拜访形式进步到实例下面:proxy,即能够vm.name来拜访vm._datas.name
  • 对以后data对象进行observe解决,临时先不必关注observe,前面会讲到是做什么的。

initComputed

initComputed是用来解决传进来的computed参数

function initComputed(vm: Component, computed: Object) {    const watchers = vm._computedWatchers = Object.create(null)    const isSSR = isServerRendering()    for (const key in computed) {        const userDef = computed[key]        const getter = typeof userDef === 'function' ? userDef : userDef.get        if (!isSSR) {            watchers[key] = new Watcher(                vm,                getter || noop,                noop,                {                    lazy: true                }            )        }        if (!(key in vm)) {            defineComputed(vm, key, userDef)        }    }}

initComputed办法解读:

  • 第一步为实例减少了_computedWatchers属性,申明援用watchers;
  • 获取是否是服务端渲染-isSSR;
  • 遍历computed;
    • 获取用户定义的内容-userDef
    • 依据用户定义的内容来获取以后属性key的getter函数
    • 为以后key减少Watcher,临时不必关注Watcher前面会讲到
    • 调用defineComputed,参数为以后实例,以后属性key和userDef
      上面来看下defineComputed的实现:
function defineComputed(target: any, key: string, userDef: Object | Function) {    const shouldCache = !isServerRendering()    if (typeof userDef === 'function') {        sharedPropertyDefinition.get = shouldCache            ? createComputedGetter(key)            : createGetterInvoker(userDef)        sharedPropertyDefinition.set = noop    } else {        sharedPropertyDefinition.get = userDef.get            ? shouldCache && userDef.cache !== false                ? createComputedGetter(key)                : createGetterInvoker(userDef.get)            : noop        sharedPropertyDefinition.set = userDef.set || noop    }    Object.defineProperty(target, key, sharedPropertyDefinition)}

defineComputed

defineComputed办法解读:

  • 判断是否须要应用cache,非server端渲染,应用cache,即浏览器状况下都是true;
  • 分状况探讨:
    • userDef为函数时,调用createComputedGetter函数生成get函数,set函数为空函数
    • userDef不为函数时,get函数为createComputedGetter或者createGetterInvoker生成的函数;
  • 调用Object.defineProperty为以后实例增加定义属性;

createGetterInvoker

上面来看下createGetterInvoker:

function createGetterInvoker(fn) {    return function computedGetter() {        return fn.call(this, this)    }}

下面代码间接返回了一个函数,函数外部调用的是传递进来的fn函数,fn函数是从defineComputed传进来的,值为userDef或者userDef.get。

createComputedGetter

上面来看下createComputedGetter:

function createComputedGetter(key) {    return function computedGetter() {        const watcher = this._computedWatchers && this._computedWatchers[key]        if (watcher) {            if (watcher.dirty) {                watcher.evaluate()            }            if (Dep.target) {                watcher.depend()            }            return watcher.value        }    }}

下面代码返回了一个computedGetter的函数,函数外部剖析:

  • 获取了在initComputed函数外面申明的_computedWatchers,
  • watcher必定是有值的,dirty属性的值在此处也相当于lazy属性,因为创立watcher的时候传的是true,所以此处也是true;
  • 执行watcher.evaluate,该办法会获取以后watcher的value,并且把dirty属性变为false;
  • 判断Dep.target,而后调用watcher的收集依赖;
  • 返回watcher.value;

initWatch

initWatch是用来解决传进来的watch参数。

function initWatch(vm: Component, watch: Object) {    for (const key in watch) {        const handler = watch[key]        if (Array.isArray(handler)) {            for (let i = 0; i < handler.length; i++) {                createWatcher(vm, key, handler[i])            }        } else {            createWatcher(vm, key, handler)        }    }}

initWatch函数解读:
遍历watch,依据key获取handler,handler为数组遍历执行createWatcher,不为数组间接执行createWatcher;
来看下createWatcher:

createWatcher

function createWatcher(vm: Component, expOrFn: string | Function, handler: any, options?: Object) {    if (isPlainObject(handler)) {        options = handler        handler = handler.handler    }    if (typeof handler === 'string') {        handler = vm[handler]    }    return vm.$watch(expOrFn, handler, options)}

createWatcher代码解读:

  • 判断handler是否为对象,如果为对象,则把以后handler作为options,options.handler作为handler;
  • 判断handler是否为字符串,字符串的话,则间接获取实例的handler办法;
  • 调用$watch返回;
    综上剖析,watch的传参能够分为以下几种:
watch: {    telephone: function (newValue, oldValue) {      console.log('telephone')    },    name: 'printName',    message: ['printName', 'printValue'],    address: [{      handler: function (newValue, oldValue) {        console.log('address')      }    }]  },  methods: {    printName(newValue, oldValue) {      console.log('printName')    },    printValue(newValue, oldValue) {      console.log('printValue')    }  }
  • 第一种:间接传办法;
  • 第二种:传递办法的字符串名称;
  • 第三种:传递办法的字符串名称数组;
  • 第四种:传递一个蕴含handler属性的对象数组;
    接下来咱们看下$watch办法的实现

$watch

当初咱们来看下watch的实现,watch是Vue原型上的办法,主流程篇简略提了一下,流程图下面看到$watch是在statesMixin函数外面给Vue挂载到原型对象上的。

Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {    const vm: Component = this    if (isPlainObject(cb)) {        return createWatcher(vm, expOrFn, cb, options)    }    options = options || {}    options.user = true    const watcher = new Watcher(vm, expOrFn, cb, options)    if (options.immediate) {        try {            cb.call(vm, watcher.value)        } catch (error) {            handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)        }    }    return function unwatchFn() {        watcher.teardown()    }}

下面代码就是$watch函数的实现,咱们一步步来看下。

  • 参数,蕴含3个,第一个就是须要watch的key,比方下面例子代码的name;第二个就是回调函数,当name属性扭转的时候会调用此回调函数;第三个参数为options,顾名思义,就是配置信息;
  • 第一步:实例vm的申明;
  • 第二步:判断cb是否为对象,如果是则调用下面的createWatcher;
  • 第三步:options检测是否有值,无值则赋值为空对象;
  • 第四步:设置options.user为true,即这是用户所定义和调用触发的;
  • 第五步:创立Watcher;
  • 第六步:如果是立刻调用,则调用cb,即回调函数;
  • 返回一个函数,此函数为watcher的销毁函数。
    下面就是整个initWatch的调用过程。