數組問題
數組原length為m,當重新設置數組的length為 n,會自動移除數組的最末m-n個元素,只保留起始處的n個元素。
舉個例子,如下:
在proxy之前,我們無法去模擬數組的這種行為。
什么是Prxoy(代理)和Reflect(反射)?
proxy 是一種封裝,能去攔截並改變js引擎的底層操作,比如一些不可枚舉、不可寫入的屬性。
通過調用new Proxy(),可以創建一個代理去替代另一個對象(目標對象),
這時,代理對目標對象進行了虛擬,因此,該代理和目標對象在表面上可以當做統一對象來看。
Proxy代理允許攔截目標對象的底層操作,而這本來是js引擎的內部操作。
攔截的行為是個函數,可以修改js對象的內置行為,用於響應攔截的特定操作,我們稱為陷阱。
Reflect對象是給底層操作提供默認行為的方法的集合,這些操作可以被proxy代理重寫。
每個代理陷阱都有一個對應的反射方法,每個方法與對應的陷阱函數同名,接受的參數也類似。
如果要是使用原先的內置行為,則可以使用對應的反射接口方法。
將代理陷阱和反射方法做了個統一表格,如下:
代理陷阱Proxy |
被重寫的行為 |
默認行為Reflect |
get |
讀取一個屬性的値 |
Reflect.get() |
set |
寫入一個屬性 |
Reflect.set() |
has |
in運算符 |
Reflect.has() |
deleteProperty |
delete 運算符 |
Reflect.deleteProperty() |
getPrototypeOf |
Object.getPrototypeOf() |
Reflect.getPrototypeOf() |
setPrototypeOf |
Object.setPrototypeOf() |
Reflect.setPrototypeOf() |
isExtensible |
Object.isExtensible() |
Reflect.isExtensible() |
preventExtensions |
Object.preventExtensions() |
Reflect.preventExtensions() |
getOwnPropertyDescriptor |
Object.getOwnPropertyDescriptor() |
Reflect.getOwnPropertyDescriptor() |
defineProperty |
Object.defineProperty() |
Reflect.defineProperty() |
ownKeys |
Object.keys() Object.getOwnPropertyNames()與 Object.getOwnPropertySymbols() |
Reflect.ownKeys() |
apply |
調用一個函數 |
Reflect.apply() |
construct |
使用new調用一個函數 |
Reflect.construct() |
創建代理
var proxy = new Proxy(target, handler);
參數:target參數表示所要攔截的目標對象,
handler參數也是一個或多個陷阱函數的對象,用來定制攔截行為。若沒有提供陷阱函數,則代理采取默認行為操作。
new Proxy( ) 表示生成一個Proxy實例
let target = {} let proxyObj = new Proxy(target, {}) proxyObj.name = "proxyName" console.log(proxyObj.name) // Proxy {name: "proxyName"} console.log(target.name) // {name: "proxyName"} console.log(proxyObj.name) //proxyName console.log(target.name) //proxyName target.name = "targeName" console.log(proxyObj.name) //targeName console.log(target.name) //targeName
proxy對象將屬性賦值的操作傳遞給target對象;
為target.name設置屬性值,也會在proxy.name上有相同的改變。
get陷阱函數
let target = {} console.log(target.name) // undefined
get陷阱函數在讀取屬性時被調用,即使對象不存在此屬性,也可以接受參數。
get陷阱函數有三,分別為
- trapTarget:被讀取屬性的對象(代理的目標對象)
- key:被讀取屬性的鍵
- receiver:操作發生的對象(代理的對象)
注:1)Reflect.get()方法 接受參數和get陷阱函數相同。
2)set陷阱函數的參數有四個(trapTarget、key、value、receiver), 而get陷阱函數沒有使用value參數,是因為get陷阱函數不需要設置屬性值。
舉個例子來具體說明下:
eg: 讀取目標屬性不存在的情況下,報錯
var target = { name : 'targetName' } let proxy = new Proxy(target, { get(trapTarget, key, receiver) { if (!(key in trapTarget)) { throw new TypeError("屬性" + key + " doesn't exist."); } return Reflect.get(trapTarget, key, receiver); } }) console.log(proxy.name) // "targetName" // 添加屬性的功能正常 proxy.place = "北京"; console.log(proxy.place) // "北京" // 讀取不存在屬性會報錯 console.log(proxy. sex) // 報錯
由於我們是讀取對象的屬性,只需要使用get陷阱函數。
在本例中,通過in運算符來判斷receiver對象上是否存在已有的屬性,從而進行攔截操作。
以上看出,可以添加屬性且能夠讀取存在的屬性,而讀取不存在屬性會報錯。
set陷阱函數
set陷阱函數有四個參數,分別為
- trapTarget:被接受屬性的對象(代理的目標對象)
- key:被寫入屬性的鍵
- value:被寫入屬性的值
- receiver:操作發生的對象(代理的對象)
set()在寫入屬性成功返回true,否則返回false。
同樣的,Reflect.set()參數和set陷阱函數一致,且Reflect.set()依據操作的不同返回相應的結果。
舉個例子來說明下,
eg: 創建對象,且屬性值只能是num類型,若類型不符,則報錯。需要用set陷阱函數去重新屬性值的默認行為。
let target = { name: "target" } let proxy = new Proxy(target, { set(trapTarget, key, value, receiver) { console.log(trapTarget, key, value, receiver) // proxy.count = 1 打印結果為 {name: "target"} "count" 1 Proxy {name: "target"} // proxy.name = "proxyName” 打印結果為 {name: "target", count: 1} "name" "proxyName" Proxy {name: "target", count: 1} // 忽略已有屬性,避免影響它們 if (!trapTarget.hasOwnProperty(key)) { if (isNaN(value)) { throw new TypeError("Property must be a number."); } } // 添加屬性 return Reflect.set(trapTarget, key, value, receiver); } }) // 添加一個新屬性 proxy.count = l console.log(proxy.count) // l console.log(target.count) // l // 你可以為name 賦一個非數值類型的值,因為該屬性已存在 proxy.name = "proxy" console.log(proxy.name) // "proxyName" console.log(target.name) // "proxyName" // 拋出錯誤 proxy.anotherName = "proxyOtherName"
當執行 proxy.count = 1時,set陷阱函數被調用,此時trapTarget的値等於target對象,key的値是字符串"count" ’,value的値是1 。
target對象上尚不存在名為count的屬性,因此代理將 value參數傳遞給isNaN()方法進行驗證;
如果驗證結果是NaN ,表示傳入的屬性値不是 一個數値,需要拋出錯誤;
但由於這段代碼將count參數設置為1 ,驗證通過,代理使用一致的四個參數去調用Reflect.set()方法,從而創建了一個新的屬性。
當proxy.name被賦值為字符串時,操作成功完成。這是因為target對象已經擁有一個 name屬性,
因此驗證時通過調用trapTarget.hasOwnProperty()會忽略該屬性,這就確保允 許在該對象的已有屬性上使用非數値的屬性値。
當proxy.anotherName被紙値為字符串時,拋出了一個錯誤。這是因為該對象上並不存在 anotherName屬性,因此該屬性的値必須被驗證,
而因為提供的値不是一個數値,驗證過程 就會拋出錯誤。
has陷阱函數
in 運算符判斷對象是否存在某個屬性,無論該屬性是對象自身屬性,還是其原型屬性
val是自身屬性,toString是 原型屬性,
has陷阱函數參數有兩個,分別為
- trapTarget:需要讀取屬性的對象(代理的目標對象)
- key:需要檢查的屬性的鍵
Reflect.has()方法接受與之相同的參數,並向in運算符返回默認響應結果。
使用has 陷阱函數以及Reflect.has()方法,允許你修改部分屬性在接受in檢測時的行為,但保留其他屬性的默認行為。
舉個例子來說明:
eg: 只想要隱藏value屬性
let target = { name: "target", value: 42 } let proxy = new Proxy(target, { has(trapTarget, key) { if (key === "value") { return false } else { return Reflect.has(trapTarget, key); } } }) console.log("value" in proxy); // false console.log("name" in proxy); // true console.log("tost ring" in proxy); // true
使用了 has陷牌函數,用於檢查key値是否為"value"。如果是,則 返回false,否則通過調用Reflect.has()方法來返回默認的結果。