vue3通过Proxy+Reflect实现响应式,vue2通过defineProperty来实现
Proxy
Proxy是什么
Proxy是ES6中增加的类,表示代理。
如果我们想要监听对象的操作过程,可以先创建一个代理对象,之后所有对于对象的操作,都由代理对象来完成,代理对象可以监听到我们对于原对象进行了哪些操作。
Proxy怎么使用
Proxy是一个类,通过new关键字创建对象,传入原对象和处理监听的捕获器
const user = {
name: 'alice'
}
const proxy = new Proxy(user, {})
console.log(proxy)
proxy.name = 'kiki'
console.log(proxy.name)
console.log(user.name)
对代理所作的操作,同样会作用于原对象
什么是捕获器
捕获器就是用来监听对于对象操作的方法
const user = {
name: 'alice',
age: 18
}
const proxy = new Proxy(user, {
get: function(target, key){
console.log(`调用${key}属性的读取操作`)
return target[key]
},
set: function(target, key, value){
console.log(`调用${key}属性的设置操作`)
target[key] = value
}
})
proxy.name = 'kiki'
console.log(proxy.age)
以上get、set是捕获器中常用的两种,分别用于对象数据的“读取”操作和“设置”操作
有哪些捕获器
Proxy里对应捕获器与普通对象的操作和定义是一一对应的
- handler.getPrototypeOf()
Object.getPrototypeOf 方法的捕捉器。 - handler.setPrototypeOf()
Object.setPrototypeOf 方法的捕捉器。 - handler.isExtensible()
Object.isExtensible 方法的捕捉器。 - handler.preventExtensions()
Object.preventExtensions 方法的捕捉器。 - handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor 方法的捕捉器。
- handler.defineProperty()
Object.defineProperty 方法的捕捉器。 - handler.has()
in 操作符的捕捉器。 - handler.get()
属性读取操作的捕捉器。 - handler.set()
属性设置操作的捕捉器。 - handler.deleteProperty()
delete 操作符的捕捉器。 - handler.ownKeys()
Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。 - handler.apply()
函数调用操作的捕捉器。 - handler.construct()
new 操作符的捕捉器。
将这些捕获器以及对应的对象操作写在了以下示例中
const user = {
name: "alice",
age: 18
};
const proxy = new Proxy(user, {
get(target, key) {
console.log("执行了get方法");
return target[key];
},
set(target, key, value) {
target[key] = value;
console.log("执行了set方法");
},
has(target, key) {
return key in target;
},
deleteProperty(target, key){
console.log('执行了delete的捕获器')
delete target[key]
},
ownKeys(target){
console.log('ownKeys')
return Object.keys(target)
},
defineProperty(target, key, value){
console.log('defineProperty', target, key, value)
return true
},
getOwnPropertyDescriptor(target, key){
return Object.getOwnPropertyDescriptor(target, key)
},
preventExtensions(target){
console.log('preventExtensions')
return Object.preventExtensions(target)
},
isExtensible(target){
return Object.isExtensible(target)
},
getPrototypeOf(target){
return Object.getPrototypeOf(target)
},
setPrototypeOf(target, prototype){
console.log('setPrototypeOf', target, prototype)
return false
}
});
console.log(proxy.name);
proxy.name = "kiki";
console.log("name" in proxy);
delete proxy.age
console.log(Object.keys(proxy))
Object.defineProperty(proxy, 'name', {
value: 'alice'
})
console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Object.preventExtensions(proxy))
console.log(Object.isExtensible(proxy))
console.log(Object.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})
通过捕获器去监听对象的修改、查询、删除等操作
以上捕获器中只有apply和constructor是属于函数对象的
function foo(){}
const proxy = new Proxy(foo, {
apply: function(target, thisArg, args){
console.log('执行proxy的apply方法', target, thisArg, args)
return target.apply(thisArg, args)
},
construct: function(target, argArray){
console.log('执行proxy的construct方法',target, argArray)
return new target()
}
})
proxy.apply({}, [1,2])
new proxy('alice', 18)
apply方法执行apply捕获器,new操作执行constructor捕获器
Reflect
Reflect是什么
Reflect也是ES6新增的一个API,表示反射。它是一个对象,提供了很多操作对象的方法,类似于Object中的方法,比如 Reflect.getPrototypeOf 和 Object.getPrototypeOf。
早期操作对象的方法都是定义在Object上,但Object作为构造函数,直接放在它身上并不合适,所以新增Reflect对象来统一操作,并且转换了对象中in、delete这样的操作符
Reflect中有哪些方法
Reflect中的方法与Proxy中是一一对应的
- Reflect.apply(target, thisArgument, argumentsList)
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和 Function.prototype.apply() 功能类似。 - Reflect.construct(target, argumentsList[, newTarget])
对构造函数进行 new 操作,相当于执行 new target(...args)。 - Reflect.defineProperty(target, propertyKey, attributes)和 Object.defineProperty() 类似。如果设置成功就会返回 true
- Reflect.deleteProperty(target, propertyKey)
作为函数的delete操作符,相当于执行 delete target[name]。 - Reflect.get(target, propertyKey[, receiver])
获取对象身上某个属性的值,类似于 target[name]。 - Reflect.getOwnPropertyDescriptor(target, propertyKey)
类似于 Object.getOwnPropertyDescriptor()。如果对象中存在该属性,则返回对应的属性描述符, 否则返回 undefined. - Reflect.getPrototypeOf(target)
类似于 Object.getPrototypeOf()。 - Reflect.has(target, propertyKey)
判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。 - Reflect.isExtensible(target)
类似于 Object.isExtensible(). - Reflect.ownKeys(target)
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于 Object.keys(), 但不会受enumerable影响). - Reflect.preventExtensions(target)
类似于 Object.preventExtensions()。返回一个Boolean。 - Reflect.set(target, propertyKey, value[, receiver])
将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true。 - Reflect.setPrototypeOf(target, prototype)
设置对象原型的函数. 返回一个 Boolean, 如果更新成功,则返回true。
将之前通过Proxy设置代理的对象操作全都变为Reflect
const user = {
name: "alice",
age: 18
};
const proxy = new Proxy(user, {
get(target, key) {
console.log("执行了get方法");
return Reflect.get(target, key)
},
set(target, key, value) {
Reflect.set(target, key, value)
console.log("执行了set方法");
},
has(target, key) {
return Reflect.has(target, key)
},
deleteProperty(target, key){
Reflect.deleteProperty(target, key)
},
ownKeys(target){
console.log('ownKeys')
return Reflect.ownKeys(target)
},
defineProperty(target, key, value){
console.log('defineProperty', target, key, value)
return true
},
getOwnPropertyDescriptor(target, key){
return Reflect.getOwnPropertyDescriptor(target, key)
},
preventExtensions(target){
console.log('preventExtensions')
return Reflect.preventExtensions(target)
},
isExtensible(target){
return Reflect.isExtensible(target)
},
getPrototypeOf(target){
return Reflect.getPrototypeOf(target)
},
setPrototypeOf(target, prototype){
console.log('setPrototypeOf', target, prototype)
return false
}
});
console.log(proxy.name);
proxy.name = "kiki";
console.log(Reflect.has(proxy, 'name'));
delete proxy.age
console.log(Reflect.ownKeys(proxy))
Reflect.defineProperty(proxy, 'name', {
value: 'alice'
})
console.log(Reflect.getOwnPropertyDescriptor(proxy, 'name'))
console.log(Reflect.preventExtensions(proxy))
console.log(Reflect.isExtensible(proxy))
console.log(Reflect.getPrototypeOf(proxy))
Reflect.setPrototypeOf(proxy, {})
实现的效果是完全一致的
Reflect的receiver
Reflect中在进行get/set捕获器操作的时候,还有一个入参是receiver,指的是代理对象,用于改变this指向
const user = {
_name: 'alice',
get name(){
return this._name
},
set name(value){
this._name = value
}
}
const proxy = new Proxy(user, {
get: function(target, key, receiver){
console.log('get操作', key)
return Reflect.get(target, key, receiver)
},
set: function(target, key, receiver){
console.log('set操作', key)
return Reflect.set(target, key, receiver)
},
})
console.log(proxy.name)
proxy.name = 'kiki'
(1) 如果没有receiver,那么当修改name属性时,objProxy先执行key为name时的get操作
(2) 然后代理到obj里的get方法,读取this的_name属性,此时的this是obj,会直接修改
obj._name,不会再经过objProxy
(3) 增加了receiver之后,执行obj的get方法,读取this的_name属性,此时this是proxy
对象,所以会再次到get的捕获器中
set操作同理
Reflect中的constructor
用于改变this的指向
function Person(){
}
function Student(name, age){
this.name = name;
this.age = age
}
const student = Reflect.construct(Student, ['aclie', 18], Person)
console.log(student)
console.log(student.__proto__ === Person.prototype)
此时创建的student对象虽然拥有Student的属性和方法,但是它的this指向Person
vue的响应式
通过以下步骤一步步实现响应式
1、定义一个数组收集所有的依赖
- 定义全局变量reactiveFns用来保存所有的函数
- 定义方法watchFn,入参为函数,代码体为将入参保存进全局变量中
- 修改对象的值,遍历全局变量,执行每一个函数
let user = {
name: "alice",
};
let reactiveFns = [];
function watchFn(fn) {
reactiveFns.push(fn);
}
watchFn(function () {
console.log("哈哈哈");
});
watchFn(function () {
console.log("hello world");
});
function foo(){
console.log('普通函数')
}
user.name = "kiki";
reactiveFns.forEach((fn) => {
fn();
});
此时通过响应式函数 watchFn 将所有需要执行的函数收集进了数组中,然后当变量的值发生变化时,手动遍历执行所有的函数
2.收集依赖类的封装
以上只有一个数组来收集对象的执行函数,真实情况下,不止一个对象需要对操作状态进行监听,需要监听多个对象就可以使用类。
- 定义Depend类,给每个实例对象提供addDepend方法用于添加绑定的方法, notify方法用于执行该实例的reactiveFns属性上添加的所有方法
- 封装响应式函数watchFn,函数里调用实例对象的appDepend方法
- 修改对象的值,调用实例对象的notify方法
class Depend {
constructor() {
this.reactiveFns = [];
}
addDepend(fn) {
this.reactiveFns.push(fn);
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
let user = {
name: "alice",
age: 18,
};
const depend = new Depend();
function watchFn(fn) {
depend.addDepend(fn);
}
watchFn(function(){
console.log('啦啦啦啦啦啦')
})
watchFn(function(){
console.log('hello hello')
})
function foo(){
console.log('foo')
}
user.name = 'kiki'
depend.notify()
将收集操作和依次执行函数的方法都定义在类中
3.自动监听对象的变化
以上仍然是我们自己手动调用执行函数的方法,以下自动监听
- 在通过类收集依赖的基础上,增加Proxy来定义对象,Reflect执行对象的方法
- 在set方法中执行实例对象depend的notify方法
- 修改代理proxy属性的值
class Depend {
constructor() {
this.reactiveFns = [];
}
addDepend(fn) {
this.reactiveFns.push(fn);
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
let user = {
name: "alice",
age: 18,
};
const depend = new Depend();
function watchFn(fn) {
depend.addDepend(fn);
}
const proxy = new Proxy(user, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
depend.notify();
},
});
watchFn(function () {
console.log("name变化执行的函数");
});
watchFn(function () {
console.log("age变化执行的函数");
});
function foo() {
console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;
此时的问题是,修改了对象的任一属性,所有的函数都会调用,没有按照一一对应的关系来保存对象的属性和对应的函数
4、收集依赖的管理
- 定义weakMap用来管理对象,[对象名, map],定义map保存 [对象的属性名, 实例对象depend]
- 定义获取depend实例对象的方法getDepend,先从weakMap中获取map,如果没有就new 一个,再从map中获取depend对象,如果没有再new一个
- 在set方法中获取depend对象,调用notify方法
class Depend {
constructor() {
this.reactiveFns = [];
}
addDepend(fn) {
this.reactiveFns.push(fn);
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
let user = {
name: "alice",
age: 18,
};
const depend = new Depend();
function watchFn(fn) {
depend.addDepend(fn);
}
const weakMap = new WeakMap()
function getDepend(obj, key){
let map = weakMap.get(obj)
if(!map){
map = new Map()
weakMap.set(obj, map)
}
let depend = map.get(key)
if(!depend){
depend = new Depend()
map.set(key, depend)
}
return depend
}
const proxy = new Proxy(user, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
const depend = getDepend(target, key)
depend.notify();
},
});
watchFn(function () {
console.log("name变化执行的函数");
});
watchFn(function () {
console.log("age变化执行的函数");
});
function foo() {
console.log("foo");
}
proxy.name = "kiki";
proxy.age = 20;
此时proxy对应的depend是没有值的,所以此时没有任何打印的数据
5、正确管理收集的依赖
- 全局定义变量activeReactiveFn指向watchFn传入的方法,执行传入的方法,再将 activeReactiveFn指向null
- watchFn传入时便会被执行一次,用于代码对象的get方法中收集依赖
- Depend类中使用addDepend方法无需传参,直接使用全局的activeReactiveFn
- get方法中通过getDepend获取depend,并使用addDepend方法,收集依赖
class Depend {
constructor() {
this.reactiveFns = [];
}
addDepend(fn) {
this.reactiveFns.push(fn);
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
let user = {
name: "alice",
age: 18,
};
let activeReactiveFn = null;
function watchFn(fn) {
activeReactiveFn = fn;
fn();
activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
let map = weakMap.get(obj);
if (!map) {
map = new Map();
weakMap.set(obj, map);
}
let depend = map.get(key);
if (!depend) {
depend = new Depend();
map.set(key, depend);
}
return depend;
}
const proxy = new Proxy(user, {
get: function (target, key, receiver) {
const depend = getDepend(target, key)
depend.addDepend(activeReactiveFn)
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
const depend = getDepend(target, key);
depend.notify();
},
});
watchFn(function () {
console.log(proxy.name, "name变化执行的函数");
console.log(proxy.name, "name变化执行的函数 again");
});
watchFn(function () {
console.log(proxy.age, "age变化执行的函数");
});
function foo() {
console.log("foo");
}
proxy.name = "kiki";
此时已经能够根据属性值的变化而执行对应的函数了,但同一个函数会执行两次
6、重构
- 当函数中调用两次proxy的属性时,会将同一个函数添加到数组中两次,所以将 reactiveFn数据结构由数组变成set
- 定义reactive函数用来收集处理需要代理及响应式的对象
let activeReactiveFn = null;
class Depend {
constructor() {
this.reactiveFns = new Set();
}
addDepend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn);
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
function watchFn(fn) {
activeReactiveFn = fn;
fn();
activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
let map = weakMap.get(obj);
if (!map) {
map = new Map();
weakMap.set(obj, 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.addDepend();
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
Reflect.set(target, key, value, receiver);
const depend = getDepend(target, key);
depend.notify();
},
});
}
const user = reactive({
name: "alice",
age: 18,
})
const info = reactive({
message: 'hello'
})
watchFn(function () {
console.log(user.name, "name变化执行的函数");
console.log(user.name, "name变化执行的函数 again");
});
watchFn(function () {
console.log(user.age, "age变化执行的函数");
});
watchFn(function(){
console.log(info.message, "message发生变化了")
})
function foo() {
console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'
此时已实现vue3的响应式~
vue2的实现原理
vue2的实现就是将Proxy和Reflect替换成了Object.defineProperty和Object本身的一些方法
let activeReactiveFn = null;
class Depend {
constructor() {
this.reactiveFns = new Set();
}
addDepend() {
if (activeReactiveFn) {
this.reactiveFns.add(activeReactiveFn);
}
}
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
function watchFn(fn) {
activeReactiveFn = fn;
fn();
activeReactiveFn = null;
}
const weakMap = new WeakMap();
function getDepend(obj, key) {
let map = weakMap.get(obj);
if (!map) {
map = new Map();
weakMap.set(obj, 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 depend = getDepend(obj, key);
depend.addDepend();
return value
},
set: function (newValue) {
value = newValue
const depend = getDepend(obj, key);
depend.notify();
},
})
})
return obj
}
const user = reactive({
name: "alice",
age: 18,
})
const info = reactive({
message: 'hello'
})
watchFn(function () {
console.log(user.name, "name变化执行的函数");
console.log(user.name, "name变化执行的函数 again");
});
watchFn(function () {
console.log(user.age, "age变化执行的函数");
});
watchFn(function(){
console.log(info.message, "message发生变化了")
})
function foo() {
console.log("foo");
}
user.name = "kiki";
user.age = 20;
info.message = 'ooo'
和上面实现的效果是一致的
以上就是通过Proxy和Reflect实现vue的响应式原理,关于js高级,还有很多需要开发者掌握的地方,可以看看我写的其他博文,持续更新中~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。