JavaScript 中 Proxy 的理解
阿新 • • 發佈:2022-04-20
介紹
Proxy 是由 ES6 提供的一種機制,可以對外界的訪問進行過濾和改寫;可以理解成在目標物件之前架設一層 "攔截", 外界對這個物件的訪問,都必須先通過這層攔截;
Proxy 這個詞的原意是代理,用在這裡表示由它來“代理”某些操作,可以譯為“代理器”;
用法
Proxy
為建構函式,用來生成 Proxy
例項
var proxy = new Proxy(target, handler);
上面的程式碼,new Proxy()
生成一個 Proxy
例項,target
引數表示所要攔截的目標物件,handler
引數也是一個物件,用來指定攔截行為;
一個簡單的示例:
const person = { name: "趙雲", age: 21 } const proxy = new Proxy(person, { get: function(target, propKey) { console.log(target); console.log(propKey); return target[propKey]; } }); // {name: '趙雲', age: 21} // name // 趙雲 console.log(proxy.name);
上面的程式碼,建立了一個 person 物件,它有兩個屬性,分別為 name 和 age; 例項化了一個 Proxy ,將 person 作為第一個引數(所要攔截的目標物件),第二個傳入的引數是一個物件,它有一個 get 屬性,值是一個回撥函式;
Proxy 支援的攔截操作
例項化 Proxy 的第二個引數是一個物件,其中可以指定 Proxy 支援的攔截操作;對幾種常見的攔截操作進行舉例說明;
-
get(target, propKey, receiver)
get
方法用於攔截某個屬性的讀取操作;get
方法接收三個引數,目標物件、屬性名、proxy 例項本身(操作行為所針對的物件);最後一個引數是可選的;
示例:const person = { name: "趙雲" } const proxy = new Proxy(person, { get: function(target, propKey) { if (propKey in target) { return target[propKey]; } else { throw new ReferenceError(`屬性名${propKey}不存在`); } } }); console.log(proxy.name); // 趙雲 console.log(proxy.age); // Uncaught ReferenceError: 屬性名age不存在
-
set(target,propKey,value,receiver)
set
方法用來攔截某個屬性的賦值操作;set
方法接收四個引數,目標物件、屬性名、屬性值和 Proxy 例項本身;最後一個引數是可選的;
示例:
假定 Person 物件有一個 age 屬性,該屬性應該是一個不大於 200 的整數,那麼可以使用 Proxy 保證 age 的屬性值符合要求;const person = { age: 20 } // 年齡大於 200 報錯 const proxy = new Proxy(person, { set: function(target, propKey, propValue) { if (propKey === 'age') { if (!Number.isInteger(propValue)) { throw new ReferenceError(`age 不是一個整數`); }else if (propValue > 200) { throw new ReferenceError(`age 不能超過200`); } } // 滿足條件的 age 及其他屬性,直接儲存 target[propKey] = propValue; return true; } }); proxy.age = 28; console.log(proxy.age); // 28 console.log(proxy.age = "28"); // Uncaught ReferenceError: age 不是一個整數 console.log(proxy.age = 288); // Uncaught ReferenceError: age 不能超過200
-
has(target,propKey)
has()
方法用來攔截 HasProperty 操作,即判斷物件是否具有某個屬性時,這個方法會生效; 典型的操作就是in運算子;has()
方法可以接受兩個引數,分別是目標物件、需查詢的屬性名; -
deleteProperty(target,propKey)
deleteProperty()
方法用於攔截 delete 操作,如果這個方法丟擲錯誤或者返回 false,當前屬性就無法被 delete 命令刪除; -
ownKeys(target)
ownKeys()
方法用來攔截物件自身屬性的讀取操作; 具體來說,攔截以下操作;- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- for...in迴圈
-
getOwnPropertyDescriptor(target, propKey)
getOwnPropertyDescriptor()
方法攔截Object.getOwnPropertyDescriptor()
,返回一個屬性描述物件或者 undefined; -
defineProperty(target, propKey, propDesc)
defineProperty()
方法攔截了Object.defineProperty()
操作; -
preventExtensions(target)
preventExtensions()
方法攔截Object.preventExtensions()
; 該方法必須返回一個布林值,否則會被自動轉為布林值
注意:
這個方法有一個限制,只有目標物件不可擴充套件時(即 Object.isExtensible(proxy) 為 false ),proxy.preventExtensions
才能返回 true,否則會報錯; -
getPrototypeOf(target)
getPrototypeOf()
方法主要用來攔截獲取物件原型; 具體來說,攔截下面這些操作;- Object.prototype.proto
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
-
isExtensible(target)
isExtensible()
方法攔截Object.isExtensible()
操作; -
setPrototypeOf(target, proto)
setPrototypeOf()
方法主要用來攔截Object.setPrototypeOf()
方法; -
apply(target, object, args)
apply
方法攔截函式的呼叫、call
和apply
操作;apply
方法接受三個引數,目標物件、目標物件的上下文物件(this)、目標物件的引數陣列; -
construct(target, args)
construct()
方法用於攔截 new 命令,下面是攔截物件的寫法
construct() 方法可以接收三個引數const proxy = new Proxy({}, { construct: function (target, args, newTarget) { return new target(...args); } });
1. target:目標物件;
2. args:建構函式的引數陣列;
3. newTarget:創造例項物件時,new命令作用的建構函式
取消代理
Proxy.revocable(target, handler);
使用場景
- 資料校驗(校驗表單)
let data = { count: 8 }; data = new Proxy(data, { set: function(target, propKey, propValue, proxy) { if (propKey === "count" && typeof propValue !== 'number') { // 不是 number 型別丟擲錯誤 throw Error(`count 屬性只能設定 number 型別資料`); } return Reflect.set(target, propKey, propValue, proxy); } }); data.count = 88; console.log(data.count); // 88 console.log(data.count = "99"); // Uncaught Error: count 屬性只能設定 number 型別資料
- 私有化 api, 防止內部屬性被外部讀寫
let api = { _apiKey: "123abc456def", getUser: function(userId) {}, setUser: function(userId, config) {} } console.log(api._apiKey); // 此處可用 const restricted = ['_apiKey']; api = new Proxy(api, { set: function(target, propKey, propsValue, proxy) { if (restricted.indexOf(propKey) > -1) { throw Error(`${propKey} 不可訪問`); } return Reflect.set(target, propKey, propsValue, proxy); }, get: function(target, propKey, proxy) { if (restricted.indexOf(propKey) > -1) { throw Error(`${propKey} 不可訪問`); } return Reflect.get(target, propKey, proxy); } }); // 一下操作都會丟擲錯誤 api._apiKey; api._apiKey = "123";
- 預警,攔截,過濾
- 實現觀察者模式
- 服務端代理,跨域,取消請求等