深入浅出ES6的标准内置对象Proxy
Proxy是ES6規(guī)范定義的標準內(nèi)置對象,可以對目標對象的讀取、函數(shù)調(diào)用等操作進行攔截。一般來說,通過Proxy可以讓目標對象“可控”,比如是否能調(diào)用對象的某個方法,能否往對象添加屬性等等。
const originalObj = {name: 'xialei' }; const publicObj = new Proxy(originalObj, {set(target, prop, value) {// 將屬性值轉(zhuǎn)化為大寫target[prop] = value.toString().toUpperCase();} });publicObj.name = 'xialei'; console.log(publicObj.name); // XIALEI從上例可以看出Proxy不需要更改目標對象(也就是originalObj),就可以對originalObj的行為進行攔截。
語法
let obj = new Proxy(target, handler);
target Object 目標對象。可以是任何類型的對象,甚至包括原生對象(比如數(shù)組,函數(shù),另一個Proxy對象)
handler Object 代理行為對象。訪問目標對象時會自動觸發(fā)該handler的對應屬性。
和大多數(shù)開發(fā)者一樣,剛開始學習Proxy的時候,這個handler不知道是個什么東西,什么時候能觸發(fā)。
先看一個簡單的例子,看看handler到底是什么東西。假設我們開發(fā)一個游戲腳本,由于內(nèi)存修改器的盛行,我們需要對內(nèi)存中的數(shù)據(jù)進行安全處理,但是不能影響外部使用,我們使用Proxy完成這個功能。
const gameObj = {hp: 0};const publicGameObj = new Proxy(gameObj, {get(target, prop) {return target[prop]/2;},set(target, prop, value) {target[prop] = value*2;}});publicGameObj.hp = 10000;console.log(publicGameObj.hp, gameObj); // 10000 {hp: 20000}gameObj是我們在內(nèi)存中真實存儲數(shù)據(jù)的地方,可以看到HP是20000,但是publicGameObj.hp是10000,外部使用publicGameObj.hp即可(比如UI層展示HP為10000);
例程handler使用到的屬性
get publicGameObj.hp進行了讀取操作,因此會自動觸發(fā)handler的get方法
set publicGameObj.hp=10000進行了賦值操作,因此為自動觸發(fā)handler的set方法
handler對象
handler對象是Proxy的核心基礎,所有對目標對象的操作都需要通過handler來處理,雖然內(nèi)容有點多,但是常用的不多。
說明
目標對象 被代理的對象。外部不能直接訪問(當然語法上是可以訪問的,你都用上Proxy了,還訪問他干嘛?)
代理對象 new Proxy的返回值。外部直接訪問。目標對象會作為handler對應函數(shù)的參數(shù)傳入
帶*號的是常用屬性
所有訪問都是對代理對象訪問才會觸發(fā),直接訪問目標對象不會觸發(fā)(代理對象壓根沒參與進來,如果觸發(fā)那一定是出bug了)
handler.getPrototypeOf(target)
說明:獲取目標對象的原型
觸發(fā)時機:Object.getPrototypeOf(代理對象)
參數(shù):
target Object 目標對象
返回值:
Object 對象的原型
const array = []; const myArray = new Proxy(array, {getPrototypeOf(target) {console.log('讀取對象原型');return Object.getPrototypeOf(array);} }); console.log(Object.getPrototypeOf(myArray)); // 輸出 // 讀取對象原型 // []handler.setPrototypeOf(target, prototype)
說明:設置對象的原型
觸發(fā)時機:Object.setPrototypeOf(代理對象, 原型)
參數(shù):
target Object 目標對象
prototype Object 原型對象或null
返回值:
boolean 設置成功返回true,否則返回false(比如不讓設置)
const array = []; const myArray = new Proxy(array, {setPrototypeOf(target, prototype) {Object.setPrototypeOf(target, prototype);return true;} }); Object.setPrototypeOf(myArray, Object.prototype); console.log(Object.getPrototypeOf(myArray)); // {}handler.isExtensible(target)
說明:檢測對象是否可擴展(個人理解:也就是能否添加屬性、函數(shù))
觸發(fā)時機:Object.isExtensible(代理對象)
參數(shù):
target Object 目標對象
返回參數(shù):
boolean
handler.preventExtensions(target)
說明:設置目標對象為不可擴展
觸發(fā)時機:Object.preventExtensions(代理對象)
參數(shù):
target Object 目標對象
返回值:
boolean 設置目標對象不可擴展后返回true,不管是否設置目標對象不可擴展,返回false都會報錯
const obj = {}; const p = new Proxy(obj, {preventExtensions(target) {Object.preventExtensions(target);return true;} });Object.preventExtensions(p);p.a = 1; console.log(p); // {} 目標對象不可擴展,所以a屬性設置不進去*handler.getOwnPropertyDescriptor(target, prop)
說明:獲取目標對象屬性的描述符
觸發(fā)時機:Object.getOwnPropertyDescriptor(代理對象)
參數(shù):
target Object 目標對象
prop <string|Symbol|number> 屬性名
返回參數(shù):
Object 屬性描述符
const obj = {a: 1 }; const p = new Proxy(obj, {getOwnPropertyDescriptor(target, prop) {return target[prop];} });console.log(Object.getOwnPropertyDescriptor(obj, 'a')); // { value: 1, writable: true, enumerable: true, configurable: true }*handler.defineProperty(target,prop,properties)
說明:在對象上定義屬性(添加新屬性或修改現(xiàn)有屬性)
觸發(fā)時機:Object.defineProperty(代理對象,屬性名,屬性描述對象)
參數(shù):
target Object 目標對象
prop string|number|Symbol 屬性名
properties Object 屬性描述對象
返回值:
boolean 定義成功返回true,定義失敗或不允許定義返回false
const obj = {name: 'xialei',name2: 'xx' }; const p = new Proxy(obj, {defineProperty(target, prop, properties) {if (prop === 'name') {Object.defineProperty(target, prop, properties);return true;}return false;} });Object.defineProperty(p, 'name', {value: 'aaa', });Object.defineProperty(p, 'name2', { // TypeErrorvalue: 'aaa', });*handler.has(target, prop)
說明:判斷對象是否有指定屬性
觸發(fā)時機:prop in 代理對象
參數(shù):
target Object 目標對象
prop string|number|Symbol 屬性名
返回值:
boolean
*handler.get(target, prop, receiver)
說明: 屬性讀取器
觸發(fā)時機:讀取代理對象的屬性時觸發(fā)
參數(shù):
target Object 目標對象
prop string|number|Symbol 屬性名
receiver Proxy 代理對象或者原型鏈上的代理對象
返回值:
任何值
const obj = {name: 'xialei' }; const p = new Proxy(obj, {get(target, prop) {if(prop in target) {return target[prop].toUpperCase();}return undefined;} });console.log(p.name); // XIALEI*handler.set(target, prop, value, receiver)
說明:屬性寫入訪問器
觸發(fā)時機:對代理對象的屬性進行賦值時觸發(fā)
參數(shù):
target Object 目標對象
prop string|number|Symbol 屬性名
value any 屬性值
receiver Proxy 代理對象或原型鏈上的代理對象
返回值:
boolean 賦值成功返回true,否則返回false。嚴格模式下返回false會拋出TypeError
*handler.deleteProperty(target, prop)
說明:刪除對象屬性
觸發(fā)時機:對代理對象的屬性進行delete時觸發(fā)
參數(shù):
target Object 目標對象
prop string|number|Symbol 屬性名
返回值:
boolean 刪除成功返回true,否則返回false。嚴格模式下返回false會拋出TypeError
*handler.apply(target, thisArg, args)
說明:攔截函數(shù)調(diào)用,被代理對象必須是函數(shù)
觸發(fā)時機:
直接調(diào)用函數(shù) obj.xxx()或xxx()
apply/call
參數(shù):
target Object 目標函數(shù)
thisArg Object this對象
args Array 參數(shù)列表
返回值:
any 任何值
function sum(a, b) {return a b; } const absSum = new Proxy(sum, {apply(target, thisArg, args) {const value = target.apply(thisArg, args);return value < 0 ? -value : value;} });console.log(absSum(-1, -2)); // 3,因為被攔截了*handler.construct(target,args)
說明:攔截構造過程
觸發(fā)時機:new 目標構造函數(shù)(…args)
參數(shù):
target Function 構造函數(shù)
args Array 構造函數(shù)的參數(shù)
返回值:
Object 對象
function Person(name) {this.name = name; }const P = new Proxy(Person, {construct(target, args, newTarget) {console.log(newTarget, newTarget === P, newTarget === Person); // [Function: Person] true falsereturn new target(args[0].toUpperCase());} });const p = new P('xialei');console.log(p); // Person { name: 'XIALEI' }使用場景
保護目標對象(通過鉤子方法進行攔截)
數(shù)據(jù)轉(zhuǎn)換(上文有一個內(nèi)存保護的例子)
數(shù)據(jù)驗證(不符合規(guī)則的值不允許設置)
const privateUser = {name: 'xialei',phone: '13888888888' };const user = new Proxy(privateUser, {set(target, prop, value) {if (prop === 'phone' && !/^(\ 86)?1[\d]{10}$/.test(value)) {return false;}target[prop] = value;return true;} });user.phone = '13666666666'; console.log(user.phone); // 13666666666 user.phone = ' 8613777777777'; console.log(user.phone); // 8613777777777 user.phone = '1'; console.log(user.phone); // 8613777777777數(shù)據(jù)修正(也做數(shù)據(jù)標準化)
const privateUser = {name: 'xialei',phone: '13888888888' };const user = new Proxy(privateUser, {set(target, prop, value) {if (prop === 'phone' && !/^(\ 86)?1[\d]{10}$/.test(value)) {return false;}target[prop] = value.replace(' 86','');return true;} });user.phone = ' 8613666666666'; console.log(user.phone); // 13666666666添加實用方法
const list = [{ name: 'xialei', phone: '13888888888' },{ name: 'xialei1', phone: '13899999999' }, ];const mList = new Proxy(list, {get(target, prop) {if (prop in target) {return target[prop];}for (const item of target) {if (item.name === prop) {return item;}}return undefined;} });const firstItem = mList[0]; const itemXialei = mList["xialei"];console.log(firstItem); // { name: 'xialei', phone: '13888888888' } console.log(itemXialei);// { name: 'xialei', phone: '13888888888' }提供易用API
// 本例對localStorage做了封裝,可以像普通對象一樣操作localStorage,而不需要調(diào)用方法 const storage = new Proxy(localStorage, {get(target, prop) {return localStorage.getItem(prop);},set(target, prop, value) {localStorage.setItem(prop, value);return true;},deleteProperty(target, prop) {localStorage.removeItem(prop);},has(target, prop) {return localStorage.getItem(prop) !== null;} });storage.aaa = '1'; console.log(storage.aaa); // '1' delete storage.aaa; console.log(storage.aaa); // undefined總結
Proxy的語法雖然簡單,但是可謂前途無量,比如開了一個Virtual Dom框架也是可以的。
更多原創(chuàng)文章,盡在每天進步一點點
總結
以上是生活随笔為你收集整理的深入浅出ES6的标准内置对象Proxy的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 职场进阶之七种武器
- 下一篇: 百度快照劫持是什么意思?如何解决百度快照