JavaScript中的Proxy-Reflect操作方法
一、監(jiān)聽對象的操作
我們先來看一個(gè)需求:有一個(gè)對象,我們希望監(jiān)聽這個(gè)對象中的屬性被設(shè)置或獲取的過程
- 通過我們前面所學(xué)的知識,能不能做到這一點(diǎn)呢?
- 其實(shí)是可以的,我們可以通過之前的屬性描述符中的存儲屬性描述符來做到; 監(jiān)聽對象的操作
左邊這段代碼就利用了前面講過的 Object.defineProperty 的存儲屬性描述符
來 對屬性的操作進(jìn)行監(jiān)聽。
const obj = { name: "why", age: 18, height: 1.88 } // 需求: 監(jiān)聽對象屬性的所有操作 // 監(jiān)聽屬性的操作 // 1.針對一個(gè)屬性 // let _name = obj.name // Object.defineProperty(obj, "name", { // set: function(newValue) { // console.log("監(jiān)聽: 給name設(shè)置了新的值:", newValue) // _name = newValue // }, // get: function() { // console.log("監(jiān)聽: 獲取name的值") // return _name // } // }) // 2.監(jiān)聽所有的屬性: 遍歷所有的屬性, 對每一個(gè)屬性使用defineProperty const keys = Object.keys(obj) for (const key of keys) { let value = obj[key] Object.defineProperty(obj, key, { set: function(newValue) { console.log(`監(jiān)聽: 給${key}設(shè)置了新的值:`, newValue) value = newValue }, get: function() { console.log(`監(jiān)聽: 獲取${key}的值`) return value } }) } // console.log(obj.name) // obj.name = "kobe" console.log(obj.age) obj.age = 17 console.log(obj.age)
但是這樣做有什么缺點(diǎn)呢?
- 首先,Object.defineProperty設(shè)計(jì)的初衷,不是為了去監(jiān)聽截止一個(gè)對象中所有的屬性的。
- 我們在定義某些屬性的時(shí)候,初衷其實(shí)是定義普通的屬性,但是后面我們強(qiáng)行將它變成了數(shù)據(jù)屬性描述符。
- 其次,如果我們想監(jiān)聽更加豐富的操作,比如新增屬性、刪除屬性,那么 Object.defineProperty是無能為力的。
所以我們要知道,存儲數(shù)據(jù)描述符
設(shè)計(jì)的初衷并不是為了去監(jiān)聽一個(gè)完整的對象。
二、Proxy類基本使用
在ES6中,新增了一個(gè)Proxy類,這個(gè)類從名字就可以看出來,是用于幫助我們創(chuàng)建一個(gè)代理的:
- 也就是說,如果我們希望
監(jiān)聽一個(gè)對象的相關(guān)操作
,那么我們可以先創(chuàng)建一個(gè)代理對象(Proxy對象); - 之后對該對象的所有操作,都
通過代理對象來完成
,代理對象可以監(jiān)聽我們想要對原對象進(jìn)行哪些操作;
我們可以將上面的案例用Proxy來實(shí)現(xiàn)一次:
- 首先,我們需要new Proxy對象,并且傳入
需要偵聽的對象
以及一個(gè)處理對象
,可以稱之為handler
;- const p = new Proxy(target, handler)
- 其次,我們之后的操作都是直接對Proxy的操作,而不是原有的對象,因?yàn)槲覀冃枰趆andler里面進(jìn)行偵聽;
const obj = { name: "why", age: 18, height: 1.88 } // 1.創(chuàng)建一個(gè)Proxy對象 const objProxy = new Proxy(obj, { set: function(target, key, newValue) { console.log(`監(jiān)聽: 監(jiān)聽${key}的設(shè)置值: `, newValue) target[key] = newValue }, get: function(target, key) { console.log(`監(jiān)聽: 監(jiān)聽${key}的獲取`) return target[key] } }) // 2.對obj的所有操作, 應(yīng)該去操作objProxy // console.log(objProxy.name) // objProxy.name = "kobe" // console.log(objProxy.name) // objProxy.name = "james" objProxy.address = "廣州市" console.log(objProxy.address)
三、Proxy常見捕獲器
1.Proxy的set和get捕獲器
如果我們想要偵聽某些具體的操作,那么就可以在handler中添加對應(yīng)的捕捉器(Trap):
set和get分別對應(yīng)的是函數(shù)類型;
- set函數(shù)有四個(gè)參數(shù):
- target:目標(biāo)對象(偵聽的對象);
- property:將被設(shè)置的屬性key;
- value:新屬性值;
- receiver:調(diào)用的代理對象;
- get函數(shù)有三個(gè)參數(shù):
- target:目標(biāo)對象(偵聽的對象);
- property:被獲取的屬性key;
- receiver:調(diào)用的代理對象;
2.Proxy其它捕獲器
13個(gè)捕獲器分別是做什么的呢?
handler.getPrototypeOf()
- Object.getPrototypeOf 方法的捕捉器。
handler.setPrototypeOf()
- Object.setPrototypeOf 方法的捕捉器。
handler.isExtensible()
- Object.isExtensible 方法的捕捉器(判斷是否可以新增屬性)。
handler.preventExtensions()
- Object.preventExtensions 方法的捕捉器。
handler.getOwnPropertyDescriptor()
- Object.getOwnPropertyDescriptor 方法的捕捉器。
handler.defineProperty()
- Object.defineProperty 方法的捕捉器。 Proxy所有捕獲器
handler.ownKeys()
- Object.getOwnPropertyNames 方法和 Object.getOwnPropertySymbols 方法的捕捉器。
handler.has()
- in 操作符的捕捉器。
handler.get()
- 屬性讀取操作的捕捉器。
handler.set()
- 屬性設(shè)置操作的捕捉器。
handler.deleteProperty()
- delete 操作符的捕捉器。
handler.apply()
- 函數(shù)調(diào)用操作的捕捉器。
- handler.construct()
- new 操作符的捕捉器。
const obj = { name: "why", age: 18, height: 1.88 } // 1.創(chuàng)建一個(gè)Proxy對象 const objProxy = new Proxy(obj, { set: function(target, key, newValue) { console.log(`監(jiān)聽: 監(jiān)聽${key}的設(shè)置值: `, newValue) target[key] = newValue }, get: function(target, key) { console.log(`監(jiān)聽: 監(jiān)聽${key}的獲取`) return target[key] }, deleteProperty: function(target, key) { console.log(`監(jiān)聽: 監(jiān)聽刪除${key}屬性`) delete obj.name }, has: function(target, key) { console.log(`監(jiān)聽: 監(jiān)聽in判斷 ${key}屬性`) return key in target } }) delete objProxy.name console.log("age" in objProxy)
3.Proxy的construct和apply
當(dāng)然,我們還會(huì)看到捕捉器中還有construct和apply,它們是應(yīng)用于函數(shù)對象的:
function foo(num1, num2) { console.log(this, num1, num2) } const fooProxy = new Proxy(foo, { apply: function(target, thisArg, otherArgs) { console.log("監(jiān)聽執(zhí)行了apply操作") target.apply(thisArg, otherArgs) }, construct: function(target, otherArray) { console.log("監(jiān)聽執(zhí)行了new操作") console.log(target, otherArray) return new target(...otherArray) } }) // fooProxy.apply("abc", [111, 222]) new fooProxy("aaa", "bbb")
四、Reflect介紹和作用
Reflect也是ES6新增的一個(gè)API,它是一個(gè)對象
,字面的意思是反射。
那么這個(gè)Reflect有什么用呢?
- 它主要提供了很多操作JavaScript對象的方法,有點(diǎn)像Object中操作對象的方法;
- 比如Reflect.getPrototypeOf(target)類似于 Object.getPrototypeOf();
- 比如Reflect.defineProperty(target, propertyKey, attributes)類似于Object.defineProperty() ;
如果我們有Object可以做這些操作,那么為什么還需要有Reflect這樣的新增對象
呢?
- 這是因?yàn)樵谠缙诘腅CMA規(guī)范中沒有考慮到這種對
對象本身
的操作如何設(shè)計(jì)會(huì)更加規(guī)范,所以將這些API放到了Object上面; - 但是
Object作為一個(gè)構(gòu)造函數(shù)
,這些操作實(shí)際上放到它身上并不合適; - 另外還包含一些
類似于 in、delete操作符
,讓JS看起來是會(huì)有一些奇怪的; - 所以在ES6中
新增了Reflect
,讓我們這些操作都集中到了Reflect對象上; - 另外在使用Proxy時(shí),可以做到
不操作原對象
;
那么Object和Reflect對象之間的API關(guān)系,可以參考MDN文檔:
Object和Reflect之間的區(qū)別:Reflect有返回值
"use strict" const obj = { name: "why", age: 18 } Object.defineProperty(obj, "name", { configurable: false }) // Reflect.defineProperty() // 1.用以前的方式進(jìn)行操作 // delete obj.name // if (obj.name) { // console.log("name沒有刪除成功") // } else { // console.log("name刪除成功") // } // 2.Reflect if (Reflect.deleteProperty(obj, "name")) { console.log("name刪除成功") } else { console.log("name沒有刪除成功") }
五、Reflect的基本使用
Reflect中有哪些常見的方法呢?它和Proxy是一一對應(yīng)的,也是13個(gè):
Reflect.getPrototypeOf(target)
- 類似于 Object.getPrototypeOf()。
Reflect.setPrototypeOf(target, prototype)
- 設(shè)置對象原型的函數(shù). 返回一個(gè) Boolean, 如果更新成功,則返回 true。
Reflect.isExtensible(target)
- 類似于 Object.isExtensible()
Reflect.preventExtensions(target)
- 類似于 Object.preventExtensions()。返回一個(gè)Boolean。
Reflect.getOwnPropertyDescriptor(target, propertyKey)
- 類似于 Object.getOwnPropertyDescriptor()。如果對象中存在 該屬性,則返回對應(yīng)的屬性描述符, 否則返回 undefined.
Reflect.defineProperty(target, propertyKey, attributes)
- 和 Object.defineProperty() 類似。如果設(shè)置成功就會(huì)返回 true Reflect的常見方法
Reflect.ownKeys(target)
- 返回一個(gè)包含所有自身屬性(不包含繼承屬性)的數(shù)組。(類似于 Object.keys(), 但不會(huì)受enumerable影響).
Reflect.has(target, propertyKey)
- 判斷一個(gè)對象是否存在某個(gè)屬性,和 in 運(yùn)算符 的功能完全相同。
Reflect.get(target, propertyKey[, receiver])
- 獲取對象身上某個(gè)屬性的值,類似于 target[name]。
Reflect.set(target, propertyKey, value[, receiver])
- 將值分配給屬性的函數(shù)。返回一個(gè)Boolean,如果更新成功,則返回true。
Reflect.deleteProperty(target, propertyKey)
- 作為函數(shù)的delete操作符,相當(dāng)于執(zhí)行 delete target[name]。
Reflect.apply(target, thisArgument, argumentsList)
- 對一個(gè)函數(shù)進(jìn)行調(diào)用操作,同時(shí)可以傳入一個(gè)數(shù)組作為調(diào)用參數(shù)。和 Function.prototype.apply() 功能類似。
Reflect.construct(target, argumentsList[, newTarget])
- 對構(gòu)造函數(shù)進(jìn)行 new 操作,相當(dāng)于執(zhí)行 new target(…args)。
那么我們可以將之前Proxy案例中對原對象的操作,都修改為Reflect來操作:
const obj = { name: "why", age: 18 } const objProxy = new Proxy(obj, { set: function(target, key, newValue, receiver) { // target[key] = newValue // 1.好處一: 代理對象的目的: 不再直接操作原對象 // 2.好處二: Reflect.set方法有返回Boolean值, 可以判斷本次操作是否成功 const isSuccess = Reflect.set(target, key, newValue) if (!isSuccess) { throw new Error(`set ${key} failure`) } }, get: function(target, key, receiver) { } }) // 操作代理對象 objProxy.name = "kobe" console.log(obj)
1.Reflect的receiver
我們發(fā)現(xiàn)在使用getter、setter的時(shí)候有一個(gè)receiver的參數(shù),它的作用是什么呢?
如果我們的源對象(obj)有setter、getter的訪問器屬性,那么可以通過receiver來改變里面的this
;
const obj = { _name: "why", set name(newValue) { console.log("this:", this) // 默認(rèn)是obj this._name = newValue }, get name() { return this._name } } // obj.name = "aaaa" // console.log(obj.name) // obj.name = "kobe" const objProxy = new Proxy(obj, { set: function(target, key, newValue, receiver) { // target[key] = newValue // 1.好處一: 代理對象的目的: 不再直接操作原對象 // 2.好處二: Reflect.set方法有返回Boolean值, 可以判斷本次操作是否成功 /* 3.好處三: > receiver就是外層Proxy對象 > Reflect.set/get最后一個(gè)參數(shù), 可以決定對象訪問器setter/getter的this指向 */ console.log("proxy中設(shè)置方法被調(diào)用" + key) const isSuccess = Reflect.set(target, key, newValue, receiver) if (!isSuccess) { throw new Error(`set ${key} failure`) } }, get: function(target, key, receiver) { console.log("proxy中獲取方法被調(diào)用") return Reflect.get(target, key, receiver) } }) // 操作代理對象 objProxy.name = "kobe" console.log(objProxy.name)
2.Reflect的construct
function Person(name, age) { this.name = name this.age = age } function Student(name, age) { // Person.call(this, name, age) const _this = Reflect.construct(Person, [name, age], Student) return _this } // const stu = new Student("why", 18) const stu = new Student("why", 18) console.log(stu) console.log(stu.__proto__ === Student.prototype)
到此這篇關(guān)于JavaScript中的Proxy-Reflect詳解的文章就介紹到這了,更多相關(guān)JavaScript Proxy-Reflect內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
處理JavaScript值為undefined的7個(gè)小技巧
這篇文章主要介紹了處理JavaScript值為undefined的7個(gè)小技巧,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07Javascript計(jì)算二維數(shù)組重復(fù)值示例代碼
這篇文章主要給大家介紹了利用Javascript計(jì)算二維數(shù)組重復(fù)值的方法,文中給出了詳細(xì)的示例代碼,相信對大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。2016-12-12js判斷某個(gè)字符出現(xiàn)的次數(shù)的簡單實(shí)例
下面小編就為大家?guī)硪黄猨s判斷某個(gè)字符出現(xiàn)的次數(shù)的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-06-06Code Review 方法論與實(shí)踐總結(jié)梳理
這篇文章主要為大家介紹了Code Review 方法論與實(shí)踐總結(jié)梳理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02