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