在上一章节咱们已经粗略的分析了整个的Vue 的源码(还在草稿箱,须要梳理清楚才放出来),可是还有不少东西没有深刻的去进行分析,我会经过以下几个重要点,进行进一步深刻分析。javascript
这一章节咱们针对1. 深刻了解 Vue 响应式原理(数据拦截) 来进行分析。vue
咱们在上一章节中已经分析了,在初始化Vue实例的时候,会执行_init
方法, 其中会执行initState
方法, 这个方法很是重要, 其对咱们new Vue
实例化对象时,传递经来的参数props
, methods
,data
, computed
,watch
的处理。 其代码以下:java
function initState (vm) {
vm._watchers = [];
var 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);
}
}
复制代码
这一章节,咱们只分析对data
的处理, 也就是initData(vm)
方法, 其代码以下(删除了异常处理的代码):react
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
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 */);
}
复制代码
从上面的代码分析,首先能够得出以下一个数组
总结:bash
proxy(vm, "_data", key);
只是将data
里面的属性从新挂载(代理)在vm
实例上,咱们能够经过以下两种方式访问data
里面的数据, 如vm.visibility
或者vm._data.visibility
效果是同样的。 observe(data, true /* asRootData */);
是最重要的一个方法,下面咱们来分析这个方法observe
中文翻译就是观察
, 就是将原始的data
变成一个可观察的对象
, 其代码以下(删除了一些逻辑判断):app
function observe (value, asRootData) {
ob = new Observer(value);
}
复制代码
这个方法就是new
了一个Observer
对象, 其构造函数以下:异步
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
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);
}
};
复制代码
这个方法里面有对Array
作特殊处理,咱们如今传递的对象是一个Object
, 可是里面todos
是一个数组,咱们后面会分析数组处理的状况, 接下来调用this.walk
方法,就是遍历对象中的每个属性:函数
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
复制代码
defineReactive$$1
方法经过Object.defineProperty
来从新封装data
, 给每个属性添加一个getter
,setter
来作数据拦截post
function defineReactive$$1 ( obj, key, val, customSetter, shallow ) {
var dep = new Dep();
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];
}
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();
}
});
}
复制代码
defineReactive$$1
方法就是利用Object.defineProperty
来设置data
里面已经存在的属性来设置getter
,setter
, 具体get
和set
在何时发挥效用咱们先不分析。
var childOb = !shallow && observe(val);
是一个递归调observe
来拦截全部的子属性。
在data
中的属性todos
是一个数组, 咱们又回到observe
方法, 其主要目的是经过ob = new Observer(value);
来生成一个Observer
对象:
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
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);
}
};
复制代码
这里能够看出对Array
有特殊的处理,下面咱们咱们来具体分析protoAugment
方法
protoAugment(value, arrayMethods);
传了两个参数,第一个参数,就是咱们的数组,第二个参数arrayMethods
须要好好分析,是Vue
中对Array
的特殊处理的地方。
其源码文件在vue\src\core\observer\array.js
下,
Array.prototype
原型建立了一个新的对象arrayMethods
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
复制代码
Array
以下7 个方法:var methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
复制代码
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
ob.dep.notify()
return result
})
})
复制代码
总结:从上面可知, Vue
只会对上述七个方法进行监听, 若是使用Array 的其余的方法是不会触发Vue 的双向绑定的。好比说用concat
,map
等方法都不会触发双向绑定。
上面已经分析了Object
,Array
的数据监听,可是上面的状况都是在初始化Vue
实例的时候,已经知道了data
中有哪些属性了,而后对每一个属性进行数据拦截,如今有一种状况就是,若是咱们有须要须要给data
动态的添加属性,咱们该怎么作呢?
Vue
单独开放出了一个接口$set
, 他挂载在vm
原型上,咱们先说下其使用方式是: this.$set(this.newTodo,"name", '30')
function set (target, key, val) {
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) {
target[key] = val;
return val
}
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val
}
复制代码
经过上面的分析,使用$set
方法,须要注意以下几点:
undefined
, null
, string
, number
, symbol
, boolean
六种基础数据类型Vue
实例对象上, 并且不能直接挂载在root data
属性上$set
最终调用defineReactive$$1(ob.value, key, val);
方法去动态添加属性, 而且给该属性添加getter
,setter
动态添加的属性,一样也须要动态更新视图,则是调用ob.dep.notify();
方法来动态更新视图
data
属性是一个Object
, 则将其将其进行转换,主要是作以下两件事情:
- 给对象添加一个
__ob__
的属性, 其是一个Observer
对象
- 遍历
data
的说有属性('key'), 经过Object.defineProperty
设置其getter
和setter
来进行数据拦截
data
(或者子属性)是一个Array
, 则将其原型转换成arrayMethods
(基于Array.prototype
原型建立的一个新的对象,可是从新定义了 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse')七个方法,来进行对Array
的数据拦截(这也就是Vue 对数组操做,只有这七个方法能实现双向绑定的缘由)在这篇文章咱们已经分析了Vue 响应式原理 , 咱们接下来会继续分析深刻了解 Vue.js 是如何进行「依赖收集」,准确地追踪所需修改