vue源码分析(五)>>:data
今天来看看data,从_init看起,看看初始化时候都走了哪些操作,最终实现了数据响应式的;
源码还是从_init走起,_init方法就是初始化各个options的入口,再看下方法体吧:
Vue.prototype._init = function (options) { console.log("--------1-----------"); var vm = this; // a uid vm._uid = uid$3++; var startTag, endTag; /* istanbul ignore if */ if (config.performance && mark) { startTag = "vue-perf-start:" + (vm._uid); endTag = "vue-perf-end:" + (vm._uid); mark(startTag); } // a flag to avoid this being observed // 一个避免被观察到的标志 vm._isVue = true; // merge options // 合并opt信息 if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. // 优化内部组件实例化,因为动态选项合并非常慢,而且没有一个内部组件选项需要特殊处理。 initInternalComponent(vm, options); } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } /* istanbul ignore else */ { initProxy(vm); } // expose real self vm._self = vm; initLifecycle(vm); initEvents(vm); initRender(vm);// ---2 callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm);// 初始化数据相关 initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); /* istanbul ignore if */ if (config.performance && mark) { vm._name = formatComponentName(vm, false); mark(endTag); measure(("vue " + (vm._name) + " init"), startTag, endTag); } if (vm.$options.el) { vm.$mount(vm.$options.el);// 挂载vue实例到dom上 } };
然后看initState方法,
function initState (vm) { console.log('initState :>---> ', vm); vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); }// 初始化props if (opts.methods) { initMethods(vm, opts.methods); }// 初始化方法 if (opts.data) {// 初始化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); } }
看到,如果存在data属性,就去走initData方法,没有就给实例上加一个_data属性(是一个空对象),然后将这个data走observe方法,这个方法随后上;
然后看initData()方法:
function initData (vm) { var data = vm.$options.data; // 如果data是一个方法 就执行这个方法 data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {}; if (!isPlainObject(data)) { data = {}; warn( 'data functions should return an object:\n' + 'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function', vm ); } // proxy data on instance var keys = Object.keys(data);// 所有定义的data的第一层的key var props = vm.$options.props;// 所有定义的props var methods = vm.$options.methods; // 所有定义的methods var i = keys.length; while (i--) { var key = keys[i]; { if (methods && hasOwn(methods, key)) {// key不能和方法名相同 warn( ("Method \"" + key + "\" has already been defined as a data property."), vm ); } } if (props && hasOwn(props, key)) {// key不可以和props定义的名称相同 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 */); // 走observe方法 }
这里对data的属性做合法性判断,然后先后走了proxy和observe方法,proxy是给属性设置代理,就是让data的属性可以用this.xx直接访问,看下咋实现的:
/** * 让data和props里边的数据可以直接this.key访问 * @param {vm} target 对象 * @param {_data,_props} sourceKey 参数集合 * @param {String} key 参数key */ function proxy (target, sourceKey, key) { sharedPropertyDefinition.get = function proxyGetter () { return this[sourceKey][key] }; sharedPropertyDefinition.set = function proxySetter (val) { this[sourceKey][key] = val; }; Object.defineProperty(target, key, sharedPropertyDefinition); }
接下来看看observe方法:
/** * Attempt to create an observer instance for a value,尝试为一个值创建一个观察者实例, * returns the new observer if successfully observed,如果观测成功,返回新的观察者, * or the existing observer if the value already has one.或现有观察者(如果值已经有一个)。 */ function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } var ob; if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__; } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value._isVue ) { ob = new Observer(value); } if (asRootData && ob) { ob.vmCount++; } return ob }
看着这么多判断,暂时只用关心创建Observer咋实现就行,看看这个类是干啥的:
/** * Observer class that is attached to each observed * object. Once attached, the observer converts the target * object's property keys into getter/setters that * collect dependencies and dispatch updates. * 附加到每个被观察对象的观察者类 *对象。一旦附加,观察者转换目标 对象的属性键转换为getter/setter *收集依赖和调度更新。 */ var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 相当于 value.__ob__ = this def(value, '__ob__', this); if (Array.isArray(value)) {// 是数组 if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else {// 是对象 this.walk(value); } }; /** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. * 遍历所有属性并将它们转换为getter/setter。此方法仅在值类型为Object时调用。 */ Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive$$1(obj, keys[i]); } };
咱初始化进来 data类型肯定是object,所以先看walk方法,这里遍历data对象,走这个defineReactive$$1方法,这个方法:
/** * Define a reactive property on an Object. * 把对象的属性加上geter和setter变成响应式的 */ function defineReactive$$1 ( obj, key, val, customSetter, shallow ) { var dep = new Dep();// 收集依赖的 东西比较多, 随后专门研究 debugger var property = Object.getOwnPropertyDescriptor(obj, key);// 获取对应的东西 东西见下图 if (property && property.configurable === false) {// 如果它不允许修改 直接返回 return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { // 参数是两个 就是刚调用的时候传递了两个参数 val = obj[key]; } // 这里因为没有传shallow 所以会走observe, // 发现没有,又回去了,这就叫递归 // 递归就会有跳出的判断 这里跳出递归的判断就是传进去的val不是对象 // 也就是说 他会递归给每一个属性defineProperty var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var 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) { var 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 (customSetter) { customSetter(); } // #7981: for accessor properties without setter if (getter && !setter) { return } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal);// 这里新值 又会走一遍 dep.notify(); } }); }
这个方法就是将data的所有属性加上set和get方法,让他们变成响应式的;
data大概就是这个逻辑,给每一个属性变成响应式的,收集依赖,dep和watcher之后再细讲;怎么去驱动视图的也等这俩类研究完一起说;
接下来看看遇到数组怎么操作:
var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; // 相当于 value.__ob__ = this def(value, '__ob__', this); if (Array.isArray(value)) {// 是数组 if (hasProto) { protoAugment(value, arrayMethods); } else { copyAugment(value, arrayMethods, arrayKeys); } this.observeArray(value); } else {// 是对象 this.walk(value); } };
首先看看arrayMethods是啥子:
var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto);
就是数组的方法,然后看看这两个方法:
/** * Augment a target Object or Array by intercepting * the prototype chain using __proto__ */ function protoAugment (target, src: Object) { /* eslint-disable no-proto */ target.__proto__ = src /* eslint-enable no-proto */ } /** * Augment a target Object or Array by defining * hidden properties. */ /* istanbul ignore next */ function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
数组实现响应式代码如下:
/* * not type checking this file because flow doesn't play well with * dynamically accessing methods on Array prototype */ var arrayProto = Array.prototype; var arrayMethods = Object.create(arrayProto); var methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ]; /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { debugger // cache original method var original = arrayProto[method]; def(arrayMethods, method, function mutator () { var args = [], len = arguments.length; while ( len-- ) args[ len ] = arguments[ len ]; var result = original.apply(this, args); var ob = this.__ob__; var inserted; switch (method) { case 'push': case 'unshift': inserted = args; break case 'splice': inserted = args.slice(2); break } if (inserted) { ob.observeArray(inserted); }// 对新添加的对象添加响应式操作 // notify change ob.dep.notify();// 通知更新 return result }); }); /* */ var arrayKeys = Object.getOwnPropertyNames(arrayMethods);
总结:
1.data的属性名不能以_或$开头;
2.data的属性名不能和方法名或props名一样,不然就冲突了个屁了;
3.数组操作时候注意,响应式操作不一样;用数组方法操作数组会触发,直接用=号赋值的还有改变长度的,都触发不了
补充一下:对象和数组某些操作没有实现响应式,但vue提供了Vue.set(target,key,value)即this.$set(target,key,value)的方法,这个设置的值是响应式的,以下是set的源码:
/** * Set a property on an object. Adds the new property and * triggers change notification if the property doesn't * already exist. * 在对象上设置属性。添加新属性,并在属性不存在时触发更改通知。 */ function set (target, key, val) { debugger if (isUndef(target) || isPrimitive(target) ) { warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); } if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = Math.max(target.length, key); target.splice(key, 1, val);// 走数组方法 因为数组方法已经实现响应式了 return val }
// 如果对象上已经有这个属性了,就替换值不用往下走了
if (key in target && !(key in Object.prototype)) { target[key] = val; return val } var ob = (target).__ob__; if (target._isVue || (ob && ob.vmCount)) {
warn( 'Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.' ); return val } if (!ob) {// 如果属性有ob,就是已经实现数据劫持了 也不往下走了
target[key] = val; return val } defineReactive$$1(ob.value, key, val);// 添加依赖 ob.dep.notify();// 收集依赖 return val }
这个方法返回的是设置的值。
over
里边 Dep Watcher 有空继续搞