Proxy 、Relect、响应式
- Proxy 、Relect、响应式
-
- 1. 监听对象的操作
- 2. Proxy基本使用
-
- 2.1 Proxy 的 set 和 get 捕获器
- 2.2 Proxy 所有捕获器 (13个)
- 2.3 Proxy 的 construct 和 apply
- 3. Reflect
-
- 3.1 Reflect 的作用
- 3.2 Reflect 的常见方法
- 3.3 Reflect 的使用
- 3.4 Receiver的作用
- 3.5 Reflect 的 construct
- 4. 响应式
-
- 4.1 什么是响应式?
- 4.2 响应式函数设计
- 4.3 响应式依赖的收集
- 4.4 监听对象的变化
- 4.5 对象的依赖管理
- 4.6 对 Depend 重构
- 4.7 创建响应式对象
- 4.8 Vue2 响应式原理
Proxy 、Relect、响应式
1. 监听对象的操作
-
需求:有一个对象,我们希望监听这个对象中的属性被设置或获取的过程
-
这段代码就利用了 Object.defineProperty
的存储属性描述符来对属性的操作进行监听
const obj = {name: 'why',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 = 'kobe'
obj.age = 30
console.log(obj.name)
console.log(obj.age)
-
属性描述符监听对象的缺点:
- 首先,
Object.defineProperty
设计的初衷,不是为了去监听截止一个对象中所有的属性的
- 我们在定义某些属性的时候,初衷其实是定义普通的属性,但是后面我们强行将它变成了数据属性描述符
- 其次,如果我们想监听更加丰富的操作,比如新增属性、删除属性,那么
Object.defineProperty
是无能为力的
- 所以我们要知道,存储数据描述符设计的初衷并不是为了去监听一个完整的对象
- Ps: 原来的对象是 数据属性描述符,通过
Object.defineProperty
变成了 访问属性描述符
2. Proxy基本使用
-
在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:
- 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象)
- 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作
-
将上面的案例用 Proxy
来实现一次:
- 首先,我们需要
new Proxy
对象,并且传入需要侦听的对象以及一个处理对象,可以称之为 handler
;
-
const p = new Proxy(target, handler)
-
其次,我们之后的操作都是直接对 Proxy
的操作,而不是原有的对象,因为我们需要在 handler
里面进行侦听
const obj = {name: 'why',age: 18
}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return target[key]},set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)target[key] = newValue}
})console.log(objProxy.name)
console.log(objProxy.age)objProxy.name = 'kobe'
objProxy.age = 30console.log(obj.name)
console.log(obj.age)
2.1 Proxy 的 set 和 get 捕获器
- 如果我们想要侦听某些具体的操作,那么就可以在
handler
中添加对应的捕捉器(Trap)
set
和 get
分别对应的是函数类型
set
函数有四个参数:
target
:目标对象(侦听的对象)
property
:将被设置的属性 key
value
:新属性值
receiver
:调用的代理对象
get
函数有三个参数
target
:目标对象(侦听的对象)
property
:被获取的属性 key
receiver
:调用的代理对象
2.2 Proxy 所有捕获器 (13个)
const obj = {name: 'why',age: 18
}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return target[key]},set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)target[key] = newValue},has: function (target, key) {console.log(`监听到obj对象的${key}属性的in操作`)return key in target},deleteProperty: function (target, key) {console.log(`监听到obj对象的${key}属性的delete操作`)delete target[key]}
})
console.log('name' in objProxy)
delete objProxy.name
2.3 Proxy 的 construct 和 apply
- 到捕捉器中还有
construct
和 apply
,它们是应用于函数对象的
function foo() {console.log('调用了 foo')
}const fooProxy = new Proxy(foo, {apply: function (target, thisArg, argArray) {console.log(`对 foo 函数进行了 apply 调用`)target.apply(thisArg, argArray)},construct: function (target, argArray, newTarget) {console.log(`对 foo 函数进行了 new 调用`)return new target(...argArray)}
})fooProxy.apply({}, ['abc', 'cba'])
new fooProxy('abc', 'cba')
3. Reflect
3.1 Reflect 的作用
Reflect
也是 ES6
新增的一个 API
,它是一个对象,字面的意思是反射
Reflect
的作用:
- 它主要提供了很多操作
JavaScript
对象的方法,有点像 Object
中操作对象的方法
- 比如
Reflect.getPrototypeOf(target)
类似于 Object.getPrototypeOf()
- 比如
Reflect.defineProperty(target, propertyKey, attributes)
类似于 Object.defineProperty()
- 如果我们有
Object
可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢?
- 这是因为在早期的
ECMA
规范中没有考虑到这种对 对象本身 的操作如何设计会更加规范,所以将这些 API
放到了 Object
上面
- 但是
Object
作为一个构造函数,这些操作实际上放到它身上并不合适
- 另外还包含一些类似于
in
、delete
操作符,让 JS 看起来是会有一些奇怪的
- 所以在
ES6
中新增了 Reflect
,让我们这些操作都集中到了 Reflect
对象上
- 那么
Object
和 Reflect
对象之间的 API
关系,可以参考 MDN 文档:
3.2 Reflect 的常见方法
Reflect中有哪些常见的方法呢?它和Proxy是一一对应的,也是13个
Reflect.getPrototypeOf(target)
- 类似于
Object.getPrototypeOf()
Reflect.setPrototypeOf(target, prototype)
- 设置对象原型的函数. 返回一个
Boolean
, 如果更新成功,则返回 true
Reflect.isExtensible(target)
类似于 Object.isExtensible()
Reflect.preventExtensions(target)
- 类似于
Object.preventExtensions()
, 返回一个 Boolean
Reflect.getOwnPropertyDescriptor(target, propertyKey)
- 类似于
Object.getOwnPropertyDescriptor()
, 如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined
Reflect.defineProperty(target, propertyKey, attributes)
- 和
Object.defineProperty()
类似, 如果设置成功就会返回 true
Reflect.ownKeys(target)
- 返回一个包含所有自身属性(不包含继承属性)的数组 (类似于
Object.keys()
, 但不会受 enumerable
影响)
Reflect.has(target, propertyKey)
- 判断一个对象是否存在某个属性,和
in
运算符 的功能完全相同
Reflect.get(target, propertyKey[, receiver])
- 获取对象身上某个属性的值,类似于
target[name]
Reflect.set(target, propertyKey, value[, receiver])
- 将值分配给属性的函数,返回一个
Boolean
,如果更新成功,则返回 true
Reflect.deleteProperty(target, propertyKey)
- 作为函数的
delete
操作符,相当于执行 delete target[name]
Reflect.apply(target, thisArgument, argumentsList)
- 对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和
Function.prototype.apply()
功能类似
Reflect.construct(target, argumentsList[, newTarget])
- 对构造函数进行
new
操作,相当于执行 new target(...args)
3.3 Reflect 的使用
那么我们可以将之前Proxy案例中对原对象的操作,都修改为Reflect来操作
const obj = {name: 'why',age: 18
}const objProxy = new Proxy(obj, {get: function (target, key) {console.log(`监听到obj对象的${key}属性被访问了`)return Reflect.get(target, key)},set: function (target, key, newValue) {console.log(`监听到obj对象的${key}属性被设置值`)Reflect.set(target, key, newValue)}
})objProxy.name = 'kobe'
console.log(objProxy.name)
3.4 Receiver的作用
我们发现在使用getter、setter的时候有一个receiver的参数,它的作用是什么呢?
- 如果我们的源对象(obj)有
setter
、getter
的访问器属性,那么可以通过 receiver
来改变里面的 this
const obj = {_name: 'why',get name() {return this._name },set name(newValue) {this._name = newValue}
}const objProxy = new Proxy(obj, {get: function (target, key, receiver) {console.log('get 方法被访问-------', key, receiver)console.log(objProxy === receiver) return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)}
})objProxy.name = 'kobe'
console.log(objProxy.name)
3.5 Reflect 的 construct
function Student(name, age) {this.name = namethis.age = age
}function Teacher() {}const stu = new Student('why', 18)
console.log(stu)
console.log(stu.__proto__ === Student.prototype)
const teacher = Reflect.construct(Student, ['why', 18], Teacher)
console.log(teacher)
console.log(teacher.__proto__ === Teacher.prototype)
4. 响应式
4.1 什么是响应式?

4.2 响应式函数设计
-
首先,执行的代码中可能不止一行代码,所以我们可以将这些代码放到一个函数中:
- 那么问题就变成了,当数据发生变化时,自动去执行某一个函数;

-
但是有一个问题:在开发中是有很多的函数的,如何区分一个函数需要响应式,还是不需要响应式呢?
const obj = {name: 'why',age: 18
}function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)
}function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')
}obj.name = 'kobe'
-
响应式函数的实现 watchFn
- 如何区分响应式函数?
- 这个时候我们封装一个新的函数
watchFn
- 凡是传入到
watchFn
的函数,就是需要响应式的
- 其他默认定义的函数都是不需要响应式的
let reactiveFns = []
function watchFn(fn) {reactiveFns.push(fn)
}
const obj = {name: 'why',age: 18
}watchFn(function foo() {const newName = obj.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(obj.name)
})watchFn(function demo() {console.log(obj.name, 'demo function ---------')
})function bar() {console.log('普通的其他函数')console.log('这个函数不需要有任何的响应式')
}obj.name = 'kobe' reactiveFns.forEach((fn) => {fn()
})
4.3 响应式依赖的收集
4.4 监听对象的变化
-
那么接下来就可以通过之前的方式来监听对象的变化:
-
我们这里先以Proxy的方式来监听
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: 'why', age: 18
}
const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)depend.notify()}
})watchFn(function foo() {const newName = objProxy.nameconsole.log('你好啊,李银河')console.log('Hello World')console.log(objProxy.name)
})watchFn(function demo() {console.log(objProxy.name, 'demo function ---------')
})objProxy.name = 'kobe'
objProxy.name = 'james'
4.5 对象的依赖管理

-
实现
- 可以写一个
getDepend
函数专门来管理这种依赖关系
const taregtMap = new WeakMap()
function getDepend(target, key) {const map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}const depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}
const objProxy = new Proxy(obj, {get: function (target, key, receiver) {return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
})
-
正确的依赖收集
- 我们之前收集依赖的地方是在
watchFn
中:
- 但是这种收集依赖的方式我们根本不知道是哪一个
key
的哪一个 depend
需要收集依赖;
- 只能针对一个单独的
depend
对象来添加你的依赖对象;
- 那么正确的应该是在哪里收集呢?应该在我们调用了
Proxy
的 get
捕获器时
- 因为如果一个函数中使用了某个对象的
key
,那么它应该被收集依赖
let activeReactviceFn = null
function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}
const taregtMap = new WeakMap()
function getDepend(target, key) {let map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}
const objProxy = new Proxy(obj, {get: function (target, key, receiver) {const depend = getDepend(target, key)activeReactviceFn && depend.addDepend(activeReactviceFn)return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
})
4.6 对 Depend 重构
- 两个问题:
- 问题一:如果函数中有用到两次
key
,比如 name
,那么这个函数会被收集两次
- 问题二:我们并不希望将添加
reactiveFn
放到 get
中,因为它是属于 Depend
的行为
- 所以我们需要对
Depend
类进行重构:
- 解决问题一的方法:不使用数组,而是使用
Set
- 解决问题二的方法:添加一个新的方法,用于收集依赖
let activeReactviceFn = null
class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
}
const obj = {name: 'why', age: 18
}function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}
const taregtMap = new WeakMap()
function getDepend(target, key) {let map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}
const objProxy = new Proxy(obj, {get: function (target, key, receiver) {const depend = getDepend(target, key)depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}
})watchFn(function () {console.log(objProxy.name, '--------------')console.log(objProxy.name, '++++++++++++++')
})objProxy.name = 'kobe'
4.7 创建响应式对象
-
目前的响应式是针对于obj
一个对象的,我们可以创建出来一个函数,针对所有的对象都可以变成响应式对象
let activeReactviceFn = null
class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
}
function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}
const taregtMap = new WeakMap()
function getDepend(target, key) {let map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.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 = getDepend(target, key)depend.depend()return Reflect.get(target, key, receiver)},set: function (target, key, newValue, receiver) {Reflect.set(target, key, newValue, receiver)const depend = getDepend(target, key)depend.notify()}})
}const info = reactive({address: '广州市',height: 1.88
})watchFn(() => {console.log(info.address, '---')
})info.address = '北京市'
4.8 Vue2 响应式原理
- 前面所实现的响应式的代码,其实就是 Vue3 中的响应式原理:
- Vue3 主要是通过
Proxy
来监听数据的变化以及收集相关的依赖的
- Vue2 中通过
Object.defineProerty
的方式来实现对象属性的监听
- 可以将
reactive
函数进行如下的重构:
- 在传入对象时,我们可以遍历所有的
key
,并且通过属性存储描述符来监听属性的获取和修改
- 在
setter
和 getter
方法中的逻辑和前面的 Proxy
是一致的
let activeReactviceFn = null
class Depend {constructor() {this.reactiveFns = new Set()}depend() {if (activeReactviceFn) {this.reactiveFns.add(activeReactviceFn)}}addDepend(reactiveFn) {this.reactiveFns.add(reactiveFn)}notify() {this.reactiveFns.forEach((fn) => {fn()})}
}
function watchFn(fn) {activeReactviceFn = fnfn()activeReactviceFn = null
}
const taregtMap = new WeakMap()
function getDepend(target, key) {let map = taregtMap.get(target)if (!map) {map = new Map()taregtMap.set(target, map)}let depend = map.get(key)if (!depend) {depend = new Depend()map.set(key, depend)}return depend
}
function reactive(obj) {Object.keys(obj).forEach((key) => {let value = obj[key]Object.defineProperty(obj, key, {get: function () {const dep = getDepend(obj, key)dep.depend()return value},set: function (newValue) {value = newValueconst dep = getDepend(obj, key)dep.notify()}})})return obj
}const info = reactive({address: '广州市',height: 1.88
})watchFn(() => {console.log(info.address, '---')
})info.address = '北京市'