vue2.x源码 数据响应式原理
版本
Vue.js v2.6.14
Object.definedProperty
主要思想:
- 通过Object.defineProperty来劫持data里面的数据
- 通过设置get和set属性劫持读取和设置操作
从生命周期入手
- vue在实例化组件的时候会先执行两个生命周期beforeCreate和created
- beforeCreate是实例化后执行的第一个生命周期,这时候data ,props,methods等属性没有初始化
- 然后会执行initState(vm)后再执行created,这时候属性都完成初始化
initState做了什么
这里vm对应着每个component,initState是初始化props methods data computed
watch属性,本文关注的是数据的响应式原理,所以重点关注data的初始化过程:
- 如果存在data => initData(vm)
- 如果不存在data => observe(vm._data = {}, true)
initData(vm)
function initData (vm) {
var data = vm.$options.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);
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 */);
}
- 首先是对data的判断,typeof data === “function”,如果是function 通过getData获取返回的值,否则直接用data值,如果不存在则直接用{}
- isPlainObject(data)判断是否为对象,不是使用{}
- 遍历对象 检查是否存在同名的methods和props
- 执行 observe(data, true );
observe
- 先判断不是object或者是VNode,直接返回undefined
- 主要的目的是为了data的创建观察者
- 如果已经存在观察者,返回原来的观察者
new Observer(value)
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);
}
};
// 对象走这步
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
//数组走这步
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
- 给data绑定__ob__属性
- 这里分了两步 object和array
- 对象会遍历属性执行defineReactive
- 数组会遍历执行,劫持操作数组属性protoAugment,对元素再进行创建观察者
defineReactive
function defineReactive(
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;
// target 会在执行挂载的时候存在,当执行render函数的时候
// 因为已经劫持,所以会自动执行此处收集依赖
// 会初始化 new Watcher 后续另外补充文章
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();//通知依赖更新,更新视图
}
});
}
- 首先创建依赖 dep = new Dep()
- 如果该属性是对象,会执行observe->new Observer()->回到这一步(defineReactive),递归遍历子对象来监听每一个属性
- Object.defineProperty劫持监听每一个对象属性
- get返回值并且如果Dep.target存在收集依赖
- set更新data的属性 val = newVal;
a. 并堆新的值创建观察者,childOb = !shallow && observe(newVal);
b. 通知依赖更新 dep.notify();
整体流程
流程图工具:https://app.diagrams.net/
- 主要原理遍历object对象,劫持每一个属性
- 依赖的收集会与视图更新相关,会在挂载阶段,针对render上使用到data的属性进行收集依赖
- 在更新data上的属性后,会notify通知执行视图,执行diff算法