Proxy
监听对象的操作
如果我们希望监听一个对象中的属性被设置或获取的过程
在之前,我们可以通过 Object.defineProperty
的存储属性描述符(get set)来监听到
const obj = { name: 'xxx', age: 18 }
Object.keys(obj).forEach(key => { let value = obj[key]
Object.defineProperty(obj, key, { get: function() { console.log(`监听到obj对象的${key}属性被访问了`); return value }, set: function(newValue) { console.log(`监听到obj对象的${key}属性被设置值`); value = newValue } }) })
obj.name = 'asd' console.log(obj.name);
|
但是如果我们想要监听更加丰富的操作,比如说新增属性、删除属性,那么这个方法是无能为力的
而且我们要知道,这个方法的存储描述符设计的初衷也不是为了监听一个完整的对象的
Proxy的基本使用
这也是我们监听对象操作的第二种方式
在 ES6 中,新增了Proxy类,用于帮助我们创建一个代理
如果我们希望监听一个对象的相关操作,我们可以先根据这个对象创建一个代理对象(Proxy对象)。
然后对该对象的所有操作,我们都通过代理对象完成,因为这个代理对象可以帮我们监听我们对原对象进行的操作
怎么来使用这个Proxy代理呢?
const obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get: function(target, key) { console.log(`监听到对象的${key}属性被访问了`, target); return target[key] }, set: function(target, key, newValue) { console.log(`监听到对象的${key}属性被设置值`, target); target[key] = newValue }, has: function(target, key) { console.log(`监听到对象的${key}属性in操作`, target); return key in target }, deleteProperty: function(target, key) { console.log(`监听到对象的${key}属性delete操作`, target); delete target[key] } })
console.log(objProxy.name);
objProxy.name = 'kkk'
console.log(obj.name);
console.log("name" in objProxy); delete objProxy.name
|
为什么我们对代理对象做出的改变,原对象也是会变的?
因为我们设置值的时候,就是直接对原对象设置的,target[key] = newValue
Proxy所有捕获器
有13个捕获器
Proxy 对函数的监听:
function foo() {}
const fooProxy = new Proxy(foo, { apply: function(target, thisArg, arrArray) { console.log("对foo函数进行了apply调用"); return target.apply(thisArg, arrArray) },
construct: function(target, arrArray, newTarget) { console.log("对foo函数进行了new调用"); return new target(...arrArray) } })
fooProxy.apply({}, ['aaa','bbb']) new fooProxy("ddd", "fff")
|
Reflect
简单介绍
Reflect常见方法
跟Proxy一一对应的,也是13个
Reflect 和 Proxy 一起使用
我们可以对前面Proxy案例中,对原对象的操作都修改为Reflect来操作
const obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get(target, key, receiver) { console.log("get---------") return Reflect.get(target, key) }, set(target, key, newValue, receiver) { console.log("set---------") const result = Reflect.set(target, key, newValue)
if (result) { } else {} } })
objProxy.name = 'kkk' console.log(objProxy.name);
|
Receiver参数的作用
访问器还有一个参数 receiver
作用:如果我们的源对象有setter,getter的访问器属性,那么可以通过receiver来改变里面的this
const obj = { _name: "xxx", age: 18, get name() { return this._name }, set name(newValue) { this._name = newValue } }
const objProxy = new Proxy(obj, { get(target, key, receiver) { console.log(receiver === objProxy); console.log('get方法被访问', key, receiver); return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { let res = Reflect.set(target, key, newValue, receiver) console.log('set成功', res); }
})
objProxy.name = 'kkk' console.log(objProxy.name);
|
改变this,让它指向代理对象 又对代理对象有操作,所以捕获了两次
Reflect中的construct
很少很少用到叭,ryfES6文档:
Reflect.construct(target, args)
等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
应用场景,直接看例子
function Student(name, age) { this.name = name this.age = age }
function Teacher() {}
const teacher = Reflect.construct(Student, ["xxx", 18], Teacher)
console.log(teacher);
console.log(teacher.__proto__ === Teacher.prototype);
|
响应式原理
什么是响应式
先看一段代码:
let m = 100
console.log(m); console.log(m * 2); console.log(m + 100);
m = 200
|
当 m 发生改变的时候,上面依赖m的一段代码自动重新执行
这种可以自动响应数据变量的代码机制,我们就称之为响应式的
实现响应式
响应式函数设计与封装
首先,需要重新执行的代码可能有很多行,因此我们可以将这些代码放到一个函数中
当数据发生变化的时候,我们让这个函数执行
const obj = { name: 'xxx', age: 18 }
function objFn() { console.log('obj变了'); }
obj.name = 'kkk'
objFn()
|
这是我们简单的思想,如果我们有多个响应式函数需要再次执行呢?
那么我们可以封装一个函数,把这些响应式函数都收集起来
let reactiveFns = [] function watchFn(fn) { reactiveFns.push(fn) }
const obj = { name: 'xxx', age: 18 }
watchFn(function() { console.log('obj变了'); })
watchFn(function() { console.log('name变了'); })
obj.name = 'kkk'
reactiveFns.forEach(fn => { fn() })
|
响应式依赖的收集
目前我们收集的依赖全都放到一个数组来保存,这存在很大的问题。
实际开发中我们需要监听很多对象的响应式,这些对象需要监听的属性也不止一个,它们都会有对应的响应式函数
所以我们要设计一个类,这个类用于管理某一个对象的某一个属性的所有响应式函数
class Depend { constructor() { this.reactiveFns = [] }
addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) }
notify() { this.reactiveFns.forEach(fn => { fn() }) } }
const depend = new Depend() function watchFn(fn) { depend.addDepend(fn) }
const obj = { name: 'xxx', age: 18 }
watchFn(function() { console.log('obj的name变了', obj.name); })
watchFn(function() { console.log('name变了', obj.name); })
obj.name = 'kkk'
depend.notify()
|
自动监听对象变化
现在我们有一个问题,数据发生改变的时候,我们是手动去调用notify()让依赖函数执行的
我们需要自动监听对象变化
注意,我们创建了代理对象之后,对原对象的操作都通过代理对象完成了
class Depend { constructor() { this.reactiveFns = [] } addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) } notify() { this.reactiveFns.forEach(fn => { fn() }) } }
const depend = new Depend() function watchFn(fn) { depend.addDepend(fn) }
const obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) depend.notify() } })
watchFn(function() { console.log('obj的name变了', objProxy.name); })
watchFn(function() { console.log('name变了', objProxy.name); })
objProxy.name = 'kkk'
|
Vue3 用的Proxy,Vue2用Object.defineProperty() 监听
依赖收集的管理
我们可以看出来,上面只创建了一个depend对象来管理obj.name变化需要监听的响应式函数
如果我们有多个对象,需要监听不同的属性,那么我们可以怎么样来管理不同对象的不同依赖关系呢?
依赖收集的数据结构,这里必须要理清楚这种结构的设计,我们用到了 Map,WeakMap
伪代码:结构大概就是这样子
const obj1Map = new Map() obj1Map.set("name", "name的所有depend") obj1Map.set("age", "age的所有depend")
const obj2Map = new Map() obj2Map.set("height", "height的所有depend")
let targetMap = new WeakMap()
targetMap(obj1, obj1Map) targetMap(obj2, obj2Map)
obj1.name = ''
const depend = targetMap.get(obj).get("name")
|
具体代码实现:
先创建一个weakMap,并封装一个获取depend的函数
class Depend { constructor() { this.reactiveFns = [] }
addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) }
notify() { this.reactiveFns.forEach(fn => { fn() }) } }
const depend = new Depend() function watchFn(fn) { depend.addDepend(fn) }
const targetMap = new WeakMap() function getDepends(target, key) { let map = targetMap.get(target) if (!map) { map = new Map() targetMap.set( target, map) }
let depend = map.get(key) if (!depend) { depend = new Depend() map.set(key, depend) } return depend }
const obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get(target, key, receiver) { return Reflect.get(target, key, receiver) }, set(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver)
const depend = getDepends(target, key) depend.notify() } })
watchFn(function() { console.log('obj的name变了', objProxy.name); })
watchFn(function() { console.log('name变了', objProxy.name); })
objProxy.name = 'kkk'
|
现在我们执行会发现,根本没有实现响应式,为什么?
因为我们在watchFn添加依赖函数的时候,不管三七二十一,全都添加到一个depend里面了
所以当我们获取依赖的时候,根本无法根据对象,key来正确的获取对应key的依赖函数
那么正确的依赖应该在哪里收集呢?
应该在我们调用Proxy的get捕获器的时候。当我们第一次获取属性值的时候,我们就应该对这个key收集它自己的依赖
另外,当我们在get里面添加依赖的时候出现一个问题,我们怎么拿到这个响应函数呢?
定义一个全局的变量 activeReactiveFn
class Depend { constructor() { this.reactiveFns = [] }
addDepend(reactiveFn) { this.reactiveFns.push(reactiveFn) }
notify() { this.reactiveFns.forEach(fn => { fn() }) } }
let activeReactiveFn = null function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null }
const targetMap = new WeakMap() function getDepends(target, key) { let map = targetMap.get(target) if (!map) { map = new Map() targetMap.set( target, map) }
let depend = map.get(key) if (!depend) { depend = new Depend() map.set(key, depend) } return depend }
let obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get: function(target, key, receiver) { const depend = getDepends(target, key) depend.addDepend(activeReactiveFn) return Reflect.get(target, key, receiver) }, set: function(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver)
const depend = getDepends(target, key) depend.notify() } })
watchFn(function() { console.log('obj的name', objProxy.name); })
watchFn(function() { console.log('name', objProxy.name); })
watchFn(function() { console.log('age', objProxy.age); })
console.log('----------修改前---');
objProxy.name = 'kkk'
|
一个注意的地方:在watchFn()函数里面,我们会先调用一次传入的函数,为什么呢?
因为这个函数如果有对某个属性有get的操作的话,我们就能捕获到,而我们在get里面进行收集依赖的操作,就可以收集到这个属性的依赖了
对 Depend 重构
现在依然存在问题:
解决方法:使用Set来保存依赖函数(Set元素不重复)
- 还有一个可以优化的地方:我们并不希望将添加 activeReactiveFn 方法放在get里面,因为这是属于 Depend 类的行为
修改后:
let activeReactiveFn = null class Depend { constructor() { this.reactiveFns = new Set() } depend() { if (activeReactiveFn) { this.reactiveFns.add(activeReactiveFn) } } notify() { this.reactiveFns.forEach(fn => { fn() }) } }
function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null }
const targetMap = new WeakMap() function getDepends(target, key) { let map = targetMap.get(target) if (!map) { map = new Map() targetMap.set( target, map) }
let depend = map.get(key) if (!depend) { depend = new Depend() map.set(key, depend) } return depend }
let obj = { name: 'xxx', age: 18 }
const objProxy = new Proxy(obj, { get: function(target, key, receiver) { const depend = getDepends(target, key) depend.depend() return Reflect.get(target, key, receiver) }, set: function(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) const depend = getDepends(target, key) depend.notify() } })
watchFn(function() { console.log('name', objProxy.name); console.log('name22', objProxy.name); }) console.log('----------修改前---'); objProxy.name = 'kkk'
|
对象的响应式操作
最后一个问题,前面我们都是对一个对象(obj)实现响应式,那么如果是多个对象呢?
所以我们要创建一个函数reactive
,针对所有的对象都可以变成响应式对象
也就是实现多个对象的响应式
let activeReactiveFn = null
class Depend { constructor() { this.reactiveFns = new Set() }
depend() { if (activeReactiveFn) { this.reactiveFns.add(activeReactiveFn) } }
notify() { this.reactiveFns.forEach(fn => { fn() }) } }
function watchFn(fn) { activeReactiveFn = fn fn() activeReactiveFn = null }
const targetMap = new WeakMap() function getDepends(target, key) { let map = targetMap.get(target) if (!map) { map = new Map() targetMap.set( target, map) }
let depend = map.get(key) if (!depend) { depend = new Depend() map.set(key, depend) } return depend }
function reactive(obj) { return new Proxy(obj, { get: function(target, key, receiver) { const depend = getDepends(target, key) depend.depend() return Reflect.get(target, key, receiver) }, set: function(target, key, newValue, receiver) { Reflect.set(target, key, newValue, receiver) const depend = getDepends(target, key) depend.notify() } }) }
let obj = reactive({ name: 'xxx', age: 18 })
let info = reactive({ height: 1.88 })
watchFn(function() { console.log('name', obj.name); console.log('name22', obj.name); })
watchFn(function() { console.log('height', info.height); })
console.log('--------------修改前---');
info.height = 1.78 obj.name = 'kkk'
|
到此!我们的响应式就实现完成了
Vue2响应式原理
前面所实现的响应式的代码,其实是Vue3中的响应式原理
Vue3主要是通过Proxy来监听数据的变化以及收集相关 的依赖的
而Vue2是通过Object.defineProerty 的方式来实现对象属性的监听
function reactive(obj) { Object.keys(obj).forEach(key => { let value = obj[key] Object.defineProperty(obj, key, { get() { const depend = getDepends(obj, key) depend.depend() return value }, set(newValue) { const depend = getDepends(obj, key) value = newValue depend.notify() } }) }) return obj }
|
在传入对象的时候,遍历所有的key,并且通过属性存储描述符来监听属性的获取和修改,
其他的逻辑和前面Vue3的响应式实现是一致的