溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Javascript的Proxy與Reflect怎么調(diào)用

發(fā)布時(shí)間:2022-02-10 08:22:17 來(lái)源:億速云 閱讀:121 作者:iii 欄目:web開(kāi)發(fā)

這篇文章主要介紹了Javascript的Proxy與Reflect怎么調(diào)用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Javascript的Proxy與Reflect怎么調(diào)用文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

Javascript的Proxy與Reflect怎么調(diào)用

ECMAScript 在 ES6 規(guī)范中加入了 Proxy 與 Reflect 兩個(gè)新特性,這兩個(gè)新特性增強(qiáng)了 JavaScript 中對(duì)象訪問(wèn)的可控性,使得 JS 模塊、類的封裝能夠更加嚴(yán)密與簡(jiǎn)單,也讓操作對(duì)象時(shí)的報(bào)錯(cuò)變得更加可控。

Proxy

Proxy,正如其名,代理。這個(gè)接口可以給指定的對(duì)象創(chuàng)建一個(gè)代理對(duì)象,對(duì)代理對(duì)象的任何操作,如:訪問(wèn)屬性、對(duì)屬性賦值、函數(shù)調(diào)用,都會(huì)被攔截,然后交由我們定義的函數(shù)來(lái)處理相應(yīng)的操作,
JavaScript 的特性讓對(duì)象有很大的操作空間,同時(shí) JavaScript 也提供了很多方法讓我們?nèi)ジ脑鞂?duì)象,可以隨意添加屬性、隨意刪除屬性、隨意更改對(duì)象的原型……但是此前 Object 類提供的 API 有許多缺點(diǎn):

  • 如果要用 Object.defineProperty 定義某個(gè)名稱集合內(nèi)的全部屬性,只能通過(guò)枚舉的方式為全部屬性設(shè)置 getter 和 setter,而且由于只能每個(gè)屬性創(chuàng)造一個(gè)函數(shù),集合太大會(huì)造成性能問(wèn)題。

  • Object.defineProperty 定義后的屬性,如果仍想擁有正常的存取功能,只能將數(shù)據(jù)存放在對(duì)象的另一個(gè)屬性名上或者需要另一個(gè)對(duì)象來(lái)存放數(shù)據(jù),對(duì)于只想監(jiān)聽(tīng)屬性的場(chǎng)合尤為不便。

  • Object.defineProperty 無(wú)法修改類中不可重新定義的屬性,例如數(shù)組的 length 屬性。

  • 對(duì)于那些尚不存在且名稱不好預(yù)測(cè)的屬性,Object.defineProperty 愛(ài)莫能助。

  • 無(wú)法修改或阻止某些行為,如:枚舉屬性名、修改對(duì)象原型。

Proxy 接口的出現(xiàn)很好地解決了這些問(wèn)題:

  • Proxy 接口將對(duì)對(duì)象的所有操作歸類到數(shù)個(gè)類別中,通過(guò) Proxy 提供的陷阱攔截特定的操作,再在我們定義的處理函數(shù)中進(jìn)行邏輯判斷就可以實(shí)現(xiàn)復(fù)雜的功能,并且還能控制比以前更多的行為。

  • Proxy 創(chuàng)造的代理對(duì)象以中間人形式存在,其本身并不負(fù)責(zé)存放數(shù)據(jù),我們只需要提供代理對(duì)象給外部使用者,讓外部使用者在代理對(duì)象的控制下訪問(wèn)我們的原對(duì)象即可。

Proxy 接口在 JS 環(huán)境中是一個(gè)構(gòu)造函數(shù):

? Proxy ( target: Object, handlers: Object ) : Proxy

這個(gè)構(gòu)造函數(shù)有兩個(gè)參數(shù),第一個(gè)是我們要代理的對(duì)象,第二個(gè)是包含處理各種操作的函數(shù)的對(duì)象。
下面是調(diào)用示例:

//需要代理的目標(biāo)
var target = { msg: "I wish I was a bird!" }; 
//包含處理各種操作的函數(shù)的對(duì)象
var handler = {
	//處理其中一種操作的函數(shù),此處是訪問(wèn)屬性的操作
	get(target, property) {
		//在控制臺(tái)打印訪問(wèn)了哪個(gè)屬性
		console.log(`你訪問(wèn)了 ${property} 屬性`);
		//實(shí)現(xiàn)操作的功能
		return target[property];
	}
}
//構(gòu)造代理對(duì)象
var proxy = new Proxy( target , handler);
//訪問(wèn)代理對(duì)象
proxy.msg
//控制臺(tái): 你訪問(wèn)了 msg 屬性
//← I wish I was a bird!

在上面的例子中,先創(chuàng)建了一個(gè)對(duì)象,賦值給 target ,然后再以 target 為目標(biāo)創(chuàng)建了一個(gè)代理對(duì)象,賦值給 proxy。在作為第二個(gè)參數(shù)提供給 Proxy 構(gòu)造函數(shù)的對(duì)象里有屬性名為“get”的屬性,是一個(gè)函數(shù),“get”是 Proxy 接口一個(gè)陷阱的名稱,Proxy 會(huì)參照我們作為第二個(gè)參數(shù)提供的對(duì)象里的屬性,找到那些屬性名與陷阱名相同的屬性,自動(dòng)設(shè)置相應(yīng)的陷阱并把屬性上的函數(shù)作為陷阱的處理函數(shù)。陷阱能夠攔截對(duì)代理對(duì)象的特定操作,把操作的細(xì)節(jié)轉(zhuǎn)換成參數(shù)傳遞給我們的處理函數(shù),讓處理函數(shù)去完成這一操作,這樣我們就可以通過(guò)處理函數(shù)來(lái)控制對(duì)象的各種行為。
在上面的例子里,構(gòu)造代理對(duì)象時(shí)提供的 get 函數(shù)就是處理訪問(wèn)對(duì)象屬性操作的函數(shù),代理對(duì)象攔截訪問(wèn)對(duì)象屬性的操作并給 get 函數(shù)傳遞目標(biāo)對(duì)象請(qǐng)求訪問(wèn)的屬性名兩個(gè)參數(shù),并將函數(shù)的返回值作為訪問(wèn)的結(jié)果。

Proxy 的陷阱一共有13種:

陷阱名與對(duì)應(yīng)的函數(shù)參數(shù)攔截的操作操作示例
get(target, property)訪問(wèn)對(duì)象屬性target.property
target[property]
set(target, property, value, receiver)賦值對(duì)象屬性target.property = value
target[property] = value
has(target, property)判斷對(duì)象屬性是否存在property in target
isExtensible(target)判斷對(duì)象可否添加屬性Object.isExtensible(target)
preventExtensions(target)使對(duì)象無(wú)法添加新屬性Object.preventExtensions(target)
defineProperty(target, property, descriptor)定義對(duì)象的屬性Object.defineProperty(target, property, descriptor)
deleteProperty(target, property)刪除對(duì)象的屬性delete target.property
delete target[property]
Object.deleteProperty(target, property)
getOwnPropertyDescriptor(target, property)獲取對(duì)象自有屬性的描述符Object.getOwnPropertyDescriptor(target, property)
ownKeys(target)枚舉對(duì)象全部自有屬性Object.getOwnPropertyNames(target).
concat(Object.getOwnPropertySymbols(target))
getPrototypeOf(target)獲取對(duì)象的原型Object.getPrototypeOf(target)
setPrototypeOf(target)設(shè)置對(duì)象的原型Object.setPrototypeOf(target)
apply(target, thisArg, argumentsList)函數(shù)調(diào)用target(...arguments)
target.apply(target, thisArg, argumentsList)
construct(target, argumentsList, newTarget)構(gòu)造函數(shù)調(diào)用new target(...arguments)

在上面列出的陷阱里是有攔截函數(shù)調(diào)用一類操作的,但是只限代理的對(duì)象是函數(shù)的情況下有效,Proxy 在真正調(diào)用我們提供的接管函數(shù)前是會(huì)進(jìn)行類型檢查的,所以通過(guò)代理讓普通的對(duì)象擁有函數(shù)一樣的功能這種事就不要想啦。
某一些陷阱對(duì)處理函數(shù)的返回值有要求,如果不符合要求則會(huì)拋出 TypeError 錯(cuò)誤。限于篇幅問(wèn)題,本文不深入闡述,需要了解可自行查找資料。

除了直接 new Proxy 對(duì)象外,Proxy 構(gòu)造函數(shù)上還有一個(gè)靜態(tài)函數(shù) revocable,可以構(gòu)造一個(gè)能被銷毀的代理對(duì)象。

Proxy.revocable( target: Object, handlers: Object ) : Object

Proxy.revocable( target, handlers ) → {
	proxy: Proxy,
	revoke: ? ()
}

這個(gè)靜態(tài)函數(shù)接收和構(gòu)造函數(shù)一樣的參數(shù),不過(guò)它的返回值和構(gòu)造函數(shù)稍有不同,會(huì)返回一個(gè)包含代理對(duì)象和銷毀函數(shù)的對(duì)象,銷毀函數(shù)不需要任何參數(shù),我們可以隨時(shí)調(diào)用銷毀函數(shù)將代理對(duì)象和目標(biāo)對(duì)象的代理關(guān)系斷開(kāi)。斷開(kāi)代理后,再對(duì)代理對(duì)象執(zhí)行任何操作都會(huì)拋出 TypeError 錯(cuò)誤。

//創(chuàng)建代理對(duì)象
var temp1 = Proxy.revocable({a:1}, {});
//← {proxy: Proxy, revoke: ?}
//訪問(wèn)代理對(duì)象
temp1.proxy.a
//← 1
//銷毀代理對(duì)象
temp1.revoke();
//再次訪問(wèn)代理對(duì)象
temp1.proxy.a
//未捕獲的錯(cuò)誤: TypeError: Cannot perform 'get' on a proxy that has been revoked

弄清楚了具體的原理后,下面舉例一個(gè)應(yīng)用場(chǎng)景。
假設(shè)某個(gè)需要對(duì)外暴露的對(duì)象上有你不希望被別人訪問(wèn)的屬性,就可以找代理對(duì)象作替身,在外部訪問(wèn)代理對(duì)象的屬性時(shí),針對(duì)不想被別人訪問(wèn)的屬性返回空值或者報(bào)錯(cuò):

//目標(biāo)對(duì)象
var target = {
	msg: "我是鮮嫩的美少女!",
	secret: "其實(shí)我是800歲的老太婆!" //不想被別人訪問(wèn)的屬性
};
//創(chuàng)建代理對(duì)象
var proxy = new Proxy( target , {
	get(target, property) {
		//如果訪問(wèn) secret 就報(bào)錯(cuò)
		if (property == "secret") throw new Error("不允許訪問(wèn)屬性 secret!");
		return target[property];
	}
});
//訪問(wèn) msg 屬性
proxy.msg
//← 我是鮮嫩的美少女!
//訪問(wèn) secret 屬性
proxy.secret
//未捕獲的錯(cuò)誤: 不允許訪問(wèn)屬性 secret!

在上面的例子中,我針對(duì)對(duì) secret 屬性的訪問(wèn)進(jìn)行了報(bào)錯(cuò),守護(hù)住了“美少女”的秘密,讓我們歌頌 Proxy 的偉大!
只不過(guò),Proxy 只是在程序邏輯上進(jìn)行了接管,上帝視角的控制臺(tái)依然能打印代理對(duì)象完整的內(nèi)容,真是遺憾……(不不不,這挺好的?。?/p>

proxy//控制臺(tái): Proxy {msg: '我是鮮嫩的美少女!', secret: '其實(shí)我是800歲的老太婆!'}

以下是關(guān)于 Proxy 的一些細(xì)節(jié)問(wèn)題:

  • Proxy 在處理屬性名的時(shí)候會(huì)把除 Symbol 類型外的所有屬性名都轉(zhuǎn)化成字符串,所以處理函數(shù)在判斷屬性名時(shí)需要尤其注意。

  • 對(duì)代理對(duì)象的任何操作都會(huì)被攔截,一旦代理對(duì)象被創(chuàng)建就沒(méi)有辦法再修改它本身。

  • Proxy 的代理是非常底層的,在沒(méi)有主動(dòng)暴露原始目標(biāo)對(duì)象的情況下,沒(méi)有任何辦法越過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象(在控制臺(tái)搞騷操作除外)。

  • Proxy 代理的目標(biāo)只能是對(duì)象,不能是 JavaScript 中的原始類型。

Reflect

學(xué)過(guò)其他語(yǔ)言的人看到 Reflect 這個(gè)詞可能會(huì)首先聯(lián)想到“反射”這個(gè)概念,但 JavaScript 由于語(yǔ)言特性是不需要反射的,所以這里的 Reflect 其實(shí)和反射無(wú)關(guān),是 JavaScript 給 Proxy 配套的一系列函數(shù)。
Reflect 在 JS 環(huán)境里是一個(gè)全局對(duì)象,包含了與 Proxy 各種陷阱配套的函數(shù)。

Reflect: Object
Reflect → {
	apply: ? apply(),
	construct: ? construct(),
	defineProperty: ? defineProperty(),
	deleteProperty: ? deleteProperty(),
	get: ? (),
	getOwnPropertyDescriptor: ? getOwnPropertyDescriptor(),
	getPrototypeOf: ? getPrototypeOf(),
	has: ? has(),
	isExtensible: ? isExtensible(),
	ownKeys: ? ownKeys(),
	preventExtensions: ? preventExtensions(),
	set: ? (),
	setPrototypeOf: ? setPrototypeOf(),
	Symbol(Symbol.toStringTag): "Reflect"
}

可以看到,Reflect 上的所有函數(shù)都對(duì)應(yīng)一個(gè) Proxy 的陷阱。這些函數(shù)接受的參數(shù),返回值的類型,都和 Proxy 上的別無(wú)二致,可以說(shuō) Reflect 就是 Proxy 攔截的那些操作的原本實(shí)現(xiàn)。

那 Reflect 存在的意義是什么呢?
上文提到過(guò),Proxy 上某一些陷阱對(duì)處理函數(shù)的返回值有要求。如果想讓代理對(duì)象能正常工作,那就不得不按照 Proxy 的要求去寫(xiě)處理函數(shù)?;蛟S會(huì)有人覺(jué)得只要用 Object 提供的方法不就好了,然而不能這么想當(dāng)然,因?yàn)槟承┫葳逡蟮姆祷刂岛?Object 提供的方法拿到的返回值是不同的,而且有些陷阱還會(huì)有邏輯上的要求,和 Object 提供的方法的細(xì)節(jié)也有所出入。舉個(gè)簡(jiǎn)單的例子:Proxy 的 defineProperty 陷阱要求的返回值是布爾類型,成功就是 true,失敗就是 false。而 Object.defineProperty 在成功的時(shí)候會(huì)返回定義的對(duì)象,失敗則會(huì)報(bào)錯(cuò)。如此應(yīng)該能夠看出為陷阱編寫(xiě)實(shí)現(xiàn)的難點(diǎn),如果要求簡(jiǎn)單那自然是輕松,但是要求一旦復(fù)雜起來(lái)那真是想想都頭大,大多數(shù)時(shí)候我們其實(shí)只想過(guò)濾掉一部分操作而已。Reflect 就是專門(mén)為了解決這個(gè)問(wèn)題而提供的,因?yàn)?Reflect 里的函數(shù)都和 Proxy 的陷阱配套,返回值的類型也和 Proxy 要求的相同,所以如果我們要實(shí)現(xiàn)原本的功能,直接調(diào)用 Reflect 里對(duì)應(yīng)的函數(shù)就好了。

//需要代理的對(duì)象
var target = {
	get me() {return "我是鮮嫩的美少女!"} //定義 me 屬性的 getter
};
//創(chuàng)建代理對(duì)象
var proxy = new Proxy( target , {
	//攔截定義屬性的操作
	defineProperty(target, property, descriptor) {
		//如果定義的屬性是 me 就返回 false 阻止
		if (property == "me") return false;
		//使用 Reflect 提供的函數(shù)實(shí)現(xiàn)原本的功能
		return Reflect.defineProperty(target, property, descriptor);
	}
});
//嘗試重新定義 me 屬性
Object.defineProperty(proxy , "me", {value: "我是800歲的老太婆!"})
//未捕獲的錯(cuò)誤: TypeError: 'defineProperty' on proxy: trap returned falsish for property 'me'
//嘗試定義 age 屬性
Object.defineProperty(proxy , "age", {value: 17})
//← Proxy {age: 17}

//使用 Reflect 提供的函數(shù)來(lái)定義屬性
Reflect.defineProperty(proxy , "me", {value: "我是800歲的老太婆!"})
//← false
Reflect.defineProperty(proxy , "age", {value: 17})
//← true

在上面的例子里,由于我很懶,所以我在接管定義屬性功能的地方“偷工減料”用了 Reflect 提供的 defineProperty 函數(shù)。用 Object.defineProperty 在代理對(duì)象上定義 me 屬性時(shí)報(bào)了錯(cuò),表示失敗,而定義 age 屬性則成功完成了??梢钥吹剑吮粓?bào)錯(cuò)的 me 屬性,對(duì)其他屬性的定義是可以成功完成的。我還使用 Reflect 提供的函數(shù)執(zhí)行了同樣的操作,可以看到 Reflect 也無(wú)法越過(guò) Proxy 的代理,同時(shí)也顯示出了 Reflect 和傳統(tǒng)方法返回值的區(qū)別。

雖然 Reflect 的好處很多,但是它也有一個(gè)問(wèn)題:JS 全局上的 Reflect 對(duì)象是可以被修改的,可以替換掉里面的方法,甚至還能把 Reflect 刪掉。

//備份原本的 Reflect.get
var originGet = Reflect.get;
//修改 Reflect.get
Reflect.get = function get(target ,property) {
	console.log("哈哈,你的 get 已經(jīng)是我的形狀了!");
	return originGet(target ,property);
};
//調(diào)用 Reflect.get
Reflect.get({a:1}, "a")
//控制臺(tái): 哈哈,你的 get 已經(jīng)是我的形狀了!
//← 1
//刪除 Reflect 變量
delete Reflect
//← true
//訪問(wèn) Reflect 變量
Reflect
//未捕獲的錯(cuò)誤: ReferenceError: Reflect is not defined

基于上面的演示,不難想到,可以通過(guò)修改 Reflect 以欺騙的方式越過(guò) Proxy 的代理。所以如果你對(duì)安全性有要求,建議在使用 Reflect 時(shí),第一時(shí)間將全局上的 Reflect 深度復(fù)制到你的閉包作用域并且只使用你的備份,或者將全局上的 Reflect 凍結(jié)并鎖定引用。

關(guān)于“Javascript的Proxy與Reflect怎么調(diào)用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Javascript的Proxy與Reflect怎么調(diào)用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI