今天小新独自一人在家学习Vue,十分好奇上次跟小兰探讨的双向数据绑定依赖的响应式,这回他决定仔细看看是怎么回事。
让我们来看一下 new Vue()
发生了些什么:
-
new Vue()
调用构造函数创建实例this
及执行_init
,同时把options
通过new
的动作传入。function Vue(options) { if (__DEV__ && !(this instanceof Vue)) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) // 执行init } new Vue({ // options el: '#app', data(){ return { name: 'Vue' } } })
-
_init
初始化options
、事件、声明周期。export function initMixin(Vue: typeof Component) { Vue.prototype._init = function (options?: Record<string, any>) { const vm: Component = this // a uid vm._uid = uid++ // 给当前组件添加标识 // 初始化事件、声明周期等 initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate', undefined, false /* setContext */) initInjections(vm) // resolve injections before data/props initState(vm) // 初始化 data initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (__DEV__ && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } // 判断是否有root if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
-
在第2步骤时有这么一个函数
initState
,这个函数就是对data
的属性进行响应性关键。export function initState(vm: Component) { const opts = vm.$options if (opts.props) initProps(vm, opts.props) // Composition API initSetup(vm) if (opts.methods) initMethods(vm, opts.methods) if (opts.data) { initData(vm) // 就是这个地方,把当前实例传入初始化data } else { const ob = observe((vm._data = {})) ob && ob.vmCount++ } if (opts.computed) initComputed(vm, opts.computed) if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } } function initData(vm: Component) { let data: any = vm.$options.data // 判断data是函数还是对象,同时返回data里的内容 data = vm._data = isFunction(data) ? getData(data, vm) : data || {} // observe data 处理data const ob = observe(data) ob && ob.vmCount++ }
-
observe
方法对数组进行单独处理export function observe( value: any, shallow?: boolean, ssrMockReactivity?: boolean ): Observer | void { return new Observer(value, shallow, ssrMockReactivity) } export class Observer { dep: Dep vmCount: number // number of vms that have this object as root $data constructor(public value: any, public shallow = false, public mock = false) { for (let i = 0, l = arrayKeys.length; i < l; i++) { const key = arrayKeys[i] def(value, key, arrayMethods[key]) } if (isArray(value)) { if (!shallow) { // 处理数组 this.observeArray(value) } } else { // 处理对象 const keys = Object.keys(value) for (let i = 0; i < keys.length; i++) { const key = keys[i] defineReactive(value, key, NO_INITIAL_VALUE, undefined, shallow, mock) } } } /** * Observe a list of Array items. 数组循环调用 */ observeArray(value: any[]) { for (let i = 0, l = value.length; i < l; i++) { observe(value[i], false, this.mock) } } }
-
数组原型方法的处理。
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator(...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // notify change if (__DEV__) { ob.dep.notify({ type: TriggerOpTypes.ARRAY_MUTATION, target: this, key: method }) } else { ob.dep.notify() } return result }) })
-
$set
给目标对象添加响应式属性。export function set( target: any[] | Record<string, any>, key: any, val: any ): any { const ob = (target as any).__ob__ if (isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key) target.splice(key, 1, val) // when mocking for SSR, array methods are not hijacked if (ob && !ob.shallow && ob.mock) { observe(val, false, true) } return val } defineReactive(ob.value, key, val, undefined, ob.shallow, ob.mock) return val }
结语:
经过上面5个步骤我们不难看出只有在data
里定义的属性才能被Object.defineProperty
编辑,除此之外的是不能被响应式,解决方法是通过实例方法$set
。
提示:文章展示的源码有删节,只保留说明此次研学的内容。