vue3 源码解析学习笔记--- 初识Proxy
什么是Proxy及其语法
Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。我们来看看它的语法:
var proxy = new Proxy(target, handler);
handler常用的方法
方法 | 描述 |
---|---|
handler.has() | in 操作符的捕捉器。 |
handler.get() | 属性读取操作的捕捉器。 |
handler.set() | 属性设置操作的捕捉器。 |
handler.deleteProperty() | delete 操作符的捕捉器。 |
handler.ownKeys() | Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。 |
handler.apply() | 函数调用操作的捕捉器。 |
handler.construct() | new 操作符的捕捉器 |
举个栗子:
const originObj = {}
const proxyObj = new Proxy(originObj,{
get: function (target, propKey, receiver) {
return 10
}
});
// 代理只会对proxy对象生效,如上方的origin就没有任何效果
proxyObj.a // 10;
proxyObj.b // 10;
originObj.a // undefined
origin.b // undefined
// 使用 has 方法隐藏某些属性,不被 in 运算符发现
var handler = {
has (target, key) {
if (key[0] === '_') {
return false;
}
return key in target;
}
};
var target = { _prop: 'foo', prop: 'foo' };
var proxy = new Proxy(target, handler);
console.log('_prop' in proxy); // false
// 又比如说 apply 方法拦截函数的调用、call 和 apply 操作。
// apply 方法可以接受三个参数,分别是目标对象、目标对象的上下文对象(this)和目标对象的参数数组,
var target = function () { return 'I am the target'; };
var handler = {
apply: function () {
return 'I am the proxy';
}
};
var p = new Proxy(target, handler);
p();
// "I am the proxy"
// ownKeys 方法可以拦截对象自身属性的读取操作
let target = {
_bar: 'foo',
_prop: 'bar',
prop: 'baz'
};
let handler = {
ownKeys (target) {
return Reflect.ownKeys(target).filter(key => key[0] !== '_');
}
};
let proxy = new Proxy(target, handler);
for (let key of Object.keys(proxy)) {
console.log(target[key]);
}
// "baz"
理解defineProperty
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
这个定义说明了defineProperty只能针对修改对象上的属性
语法
- obj 要定义属性的对象
- prop 要定义或修改的属性的名称或 Symbol
- descriptor 要定义或修改的属性描述符
Object.defineProperty(obj, prop, descriptor)
举个例子
const obj = {}
Object.defineProperty(obj, "a", {
value : 1,
writable : false, // 是否可写
configurable : false, // 是否可配置
enumerable : false // 是否可枚举
})
// 上面给了三个false, 下面的相关操作就很容易理解了
obj.a = 2 // 无效
delete obj.a // 无效
for(key in obj){
console.log(key) // 无效
}
vue 2.0 数据双向绑定的原理
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ...
if (Dep.target) {
// 收集依赖
dep.depend()
}
return value
},
set: function reactiveSetter (newVal) {
// ...
// 通知视图更新
dep.notify()
}
})
对象的变异
以下的方法为什么不更新
data () {
return {
obj: {
a: 1
}
}
}
methods: {
update () {
this.obj.b = 2
}
}
这个我们现在就很容易理解了,因为在created的时候,我们进行了data init 方法,会对data绑定一个观察者 Observer,之后 data 中的字段更新都会通知依赖收集器Dep触发视图更新,当我们使用defineProperty 这个方法只是针对对象的属性进行监听,但是新增属性b是在之后添加的,没有相应的Observer。当然我们有很多方法去解决这个方法:
vue 全局set方法本质就是手动为新增属性添加Observer方法
// 源码位置 https://github.com/vuejs/vue/blob/dev/src/core/observer/index.js#L201
function set (target: Array<any> | Object, key: any, val: any): any {
// ....
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
数组的变异
var vm = new Vue({
data: {
items: ['1', '2', '3']
}
})
vm.items[1] = '4' // 视图并未更新
vm.items.length = 6 // 视图并未更新
defineProperty 是否能监听到以上的数组变化,
例子如下:
function defineReactive(data, key, val) {
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function defineGet() {
console.log(`get key: ${key} val: ${val}`);
return val;
},
set: function defineSet(newVal) {
console.log(`set key: ${key} val: ${newVal}`);
val = newVal;
}
})
}
function observe(data) {
Object.keys(data).forEach(function(key) {
defineReactive(data, key, data[key]);
})
}
let test = [1, 2, 3];
observe(test);
test[0] = 4 // set key: 0 val: 4
// 以上的例子可以说明这不是defineProperty的锅,但是我们打印test
console.log(test)
(3) [(...), (...), (...)]
将鼠标悬浮于三个点上时,会提示 The property is computed with getter
所以基于性能问题不进行监听,而且新增索引的确是 defineProperty 做不到的
所以vue提供了数组的变异方法
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
// 缓存原生数组
const original = arrayProto[method]
// def使用Object.defineProperty重新定义属性
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args) // 调用原生数组的方法
const ob = this.__ob__ // ob就是observe实例observe才能响应式
let inserted
switch (method) {
// push和unshift方法会增加数组的索引,但是新增的索引位需要手动observe的
case 'push':
case 'unshift':
inserted = args
break
// 同理,splice的第三个参数,为新增的值,也需要手动observe
case 'splice':
inserted = args.slice(2)
break
}
// 其余的方法都是在原有的索引上更新,初始化的时候已经observe过了
if (inserted) ob.observeArray(inserted)
// dep通知所有的订阅者触发回调
ob.dep.notify()
return result
})
})