前言
vue数据代理的学习笔记
一、问题
首先对data进行读操作
const vm = new Vue({
el: "#test",
data:{
name: "fubuki",
}
})
console.log(vm.name === vm._data.vm)// true
console.log(vm)
输出结果看到在vm对象中多了name属性,且name的值和vm本身的_data属性中name的值相同
那么vue是如何把传入的data里的属性传给vm对象本身的呢?vm.name和vm._data.name又是什么关系呢?
二、源码分析
1.绑定属性
vm.$options = options// 将整个Vue参数对象传入$options属性
var data = vm.$options.data// 取出data值
/*data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};*/ // 对函数和对象类型的data进行不同处理
var keys = Object.keys(data);
while (i--) {
var key = keys[i];
if (!isReserved(key)) {// 如果当前属性不是$或_开头,则将该属性添加进vm对象_data属性中,
proxy(vm, "_data", key);
}
observe(data, true /* asRootData */);// 给data加观察,通过defineReactive中的defineProperty给每个key修改get/set方法
接着在观察过程中如果触发了data中的get特性(set特性类似),则自动为vm对象添加属性值(即vm.name),其值为下面的返回值value
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {// dep不做讨论,就是被观察对象触发get时有反馈
dep.depend();// 调用depend会调用该Watcher实例的addDep方法,不做讨论
if (childOb) {// 忽略
childOb.dep.depend();
if (Array.isArray(value)) {// value不是数组,略
dependArray(value);
}
}
}
return value
},
2.验证
将上述代码返回值value改为’aqua’,则输出如下
可见vm.name与vm._data.name同时改变(因为data和vm._data是同一个地址)
3.源码补充
①defineProperty
//对象已有的属性添加特性描述
Object.defineProperty(obj,"test",{// 若已有属性则修改,没有则添加
value:任意类型,// 属性对应的值,默认undefined
configurable:true | false,// 是否可修改属性特性(使用defineProperty重配置)
enumerable:true | false,// 是否可遍历(eg. Object.keys())
writable:true | false// 是否可修改属性(不同于configurable,直接vm.name修改)
/*get
set*/
});
②noop空函数
// 函数初始化时可用,后续不需判断callback类型,直接调用callback()
if(typeof callback != 'function')
callback = noop
③isReserved
function isReserved (str) {
var c = (str + '').charCodeAt(0);
return c === 0x24 || c === 0x5F// 0x24=>'$',0x5F=>'_'
}
④observe
function observe (value, asRootData) {// value = data,asRootData = true
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {// 判断是否被观察到
ob = value.__ob__;
} else if (
observerState.shouldConvert &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);//不符合条件则添加观察
}
if (asRootData && ob) {// 看不懂
ob.vmCount++;
}
return ob
}
⑤Observer
var Observer = function Observer (value) {// 此时value为data
this.value = value;
this.dep = new Dep();
this.vmCount = 0;
def(value, '__ob__', this);// 向data内添加'__ob__'属性,作为已经被观察的标志,值为Observer对象(注意此时存在递归)
if (Array.isArray(value)) {// 如果value是数组,则对每个元素进行观察
var augment = hasProto// hasProto = '__proto__' in {} #居然能这么写!
? protoAugment
: copyAugment;
augment(value, arrayMethods, arrayKeys);
/*各参数定义如下
var arrayProto = Array.prototype;
var arrayMethods = Object.create(arrayProto);以arrayProto为原型对象创造空对象,然后增添各种方法,不详细展开
var arrayKeys = Object.getOwnPropertyNames(arrayMethods);返回arrayMethods对象的所有属性(不同于Object.keys())
*/
this.observeArray(value);// 遍历,对数组每个元素调用observe
} else {// 如果value是对象,则对其每个key调用defineReactive来获得该key的set/get控制权
this.walk(value);
}
};
⑥copyAugment
function copyAugment (target, src, keys) {// 向data中添加先前定义的一些属性,不细说
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
⑦protoAugment
function protoAugment (target, src, keys) {
/* eslint-disable no-proto */
target.__proto__ = src;// 看不懂
/* eslint-enable no-proto */
}
⑧walk
Observer.prototype.walk = function walk (obj) {// 相当重要!为data每个key调用defineReactive
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]);
}
};
三、总结
核心部分很简单,首先取出data并绑定vm._data对象和被观察对象var data(此时vm._data内只有’__ob__’),接着通过添加observer并修改属性的get和set特性,进而同步添加输入的属性,实现外层对象数据代理(vm.name === vm._data.name)
小p第一次写博客,难免漏洞百出,以后要更加努力啊!
四、补充
Dep、Watcher、Array methods
原理是当有get/set动作时,Dep通知Watcher执行相应程序,具体过程怎样以后有时间再总结吧
看到vm属性和vm._data里还有name属性对应的get和set方法,暂时就不写啦