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高級,還有很多需要開發者掌握的地方,可以看看我寫的其他博文,持續更新中~