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 高级,还有很多须要开发者把握的中央,能够看看我写的其余博文,继续更新中~