vue2.x源码解析 — initState 函数

455 阅读2分钟

先来看一下 initState函数主要做了什么
src/core/instance/state.js

export 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)
    }
}

可以看到,initState内部很简单,主要是vm状态的初始化,props/methods/data/computed//watch都在这里完成初始化,因此该函数也是Vue实例create的关键。下面简单看一下各属性初始化的过程。

initProps

initProps主要对props进行校验,并通过defineReactive进行可监听处理

function initProps (vm: Component, propsOptions: Object) {
    const propsData = vm.$options.propsData || {}
    const props = vm._props = {}
    // cache prop keys so that future props updates can iterate using Array
    // instead of dynamic object key enumeration.
    // 用于保存当前组件的props里的key; 以便之后在父组件更新props时可以直接使用数组迭代,而不需要动态枚举键值
    const keys = vm.$options._propKeys = []
    const isRoot = !vm.$parent

    // root instance props should be converted
    observerState.shouldConvert = isRoot

    for (const key in propsOptions) {
        keys.push(key)

        // 执行validateProp检查propsData里的key值是否符合propsOptions里对应的要求,并将值保存到value里面
        const value = validateProp(key, propsOptions, propsData, vm)

        ... 

        // 进行可监听处理
        defineReactive(props, key, value)
        
        ...

        if (!(key in vm)) {
            proxy(vm, `_props`, key)
        }
    }

    observerState.shouldConvert = true
}

initMethods

initMethods的作用就是将options.methods里定义的方法挂载到vm上

function initMethods (vm: Component, methods: Object) {
    const props = vm.$options.props
    for (const key in methods) {
        ...
        vm[key] = methods[key] == null ? noop : bind(methods[key], vm)
    }
}

initData

initData中,通过vm.xx来代理data.xx,再通过observe函数将data设置为可监听

function initData (vm: Component) {
    let data = vm.$options.data
    data = vm._data = typeof data === 'function'
        ? getData(data, vm)
        : data || {}

    if (!isPlainObject(data)) {
        data = {}
        ...
    }
    // proxy data on instance
    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]
        ...
        // 代理数据,当访问```this.xxx```的时候,代理到```this.data.xxx```
        proxy(vm, `_data`, key)
    }

    // observe data
    observe(data, true /* asRootData */)
}

initComputed

initComputed为每个computed创建watcher

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
        
        ...

        // 为computed创建watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
        )
        
        ...

        defineComputed(vm, key, userDef)
        
        ...
    }
}

defineComputed通过defineProperty将computed挂载到vm上

export function defineComputed (
    target: any,
    key: string,
    userDef: Object | Function
) {
    // 如果非服务端渲染,进行缓存处理
    const shouldCache = !isServerRendering()

    // 定义setter和getter
    if (typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache
            ? createComputedGetter(key)
            : userDef
        sharedPropertyDefinition.set = noop
    } else {
        sharedPropertyDefinition.get = userDef.get
            ? shouldCache && userDef.cache !== false
                ? createComputedGetter(key)
                : userDef.get
            : noop
        sharedPropertyDefinition.set = userDef.set
            ? userDef.set
            : noop
    }
    
    ...
    
    // 将computed挂载到vm实例上
    Object.defineProperty(target, key, sharedPropertyDefinition)
}

createComputedGetter

function createComputedGetter (key) {
    return function computedGetter () {
        const watcher = this._computedWatchers && this._computedWatchers[key]
        if (watcher) {
            // 是否需要重新计算watcher.value
            if (watcher.dirty) {
                watcher.evaluate()
            }

            // 收集依赖
            if (Dep.target) {
                watcher.depend()
            }

            return watcher.value
        }
    }
}

initWatch

initWatch通过option.watch的不同类型进行处理,最后调用createWatcher来构建Watcher对象

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)
        }
    }
}

createWatcher

function createWatcher (
    vm: Component,
    keyOrFn: string | Function,
    handler: any,
    options?: Object
) {
    // handler 是obj时,必须设置handler.handler,
    // obj包含 handler,deep,immediate,
    // eg: https://cn.vuejs.org/v2/api/#watch
    if (isPlainObject(handler)) {
        options = handler
        handler = handler.handler
    }

    // 回调函数是一个字符串,从 vm 获取
    if (typeof handler === 'string') {
        handler = vm[handler]
    }

    // expOrFn 是 key,options 是 watch 的全部选项
    return vm.$watch(keyOrFn, handler, options)
}

$watch

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

    // expOrFn是监听的key,cb是监听的回调,options是监听的所有选项
    const watcher = new Watcher(vm, expOrFn, cb, options)

    // 如果设置immediate: true 将立即以表达式的当前值触发回调
    // https://cn.vuejs.org/v2/api/#vm-watch
    if (options.immediate) {
        cb.call(vm, watcher.value)
    }

    // 返回unWatch的方法
    return function unwatchFn () {
        watcher.teardown()
    }
}