日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

“睡服”面试官系列第五篇之proxy(建议收藏学习)

發布時間:2023/12/10 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 “睡服”面试官系列第五篇之proxy(建议收藏学习) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

1. 概述

2. Proxy 實例的方法

2.1get()

2.2set()

2.3apply()

2.4has()

2.5construct()

2.7deleteProperty()

2.8defineProperty()

2.9getOwnPropertyDescriptor()

2.10getPrototypeOf()

2.11isExtensible()

2.12ownKeys()

2.13preventExtensions()

2.14setPrototypeOf()

3. Proxy.revocable()

4. this 問題

5. 實例:Web 服務的客戶端


1. 概述

Proxy 用于修改某些操作的默認行為,等同于在語言層面做出修改,所以屬于一種“元編程”(meta programming),即對編程語言進行編程。
Proxy 可以理解成,在目標對象之前架設一層“攔截”,外界對該對象的訪問,都必須先通過這層攔截,因此提供了一種機制,可以對外界的訪問進行過濾
和改寫。Proxy 這個詞的原意是代理,用在這里表示由它來“代理”某些操作,可以譯為“代理器”。

var obj = new Proxy({}, { get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } })

上面代碼對一個空對象架設了一層攔截,重定義了屬性的讀取( get )和設置( set )行為。這里暫時先不解釋具體的語法,只看運行結果。對設置了攔
截行為的對象 obj ,去讀寫它的屬性,就會得到下面的結果。

obj.count = 1 // setting count! ++obj.count // getting count! // setting count! // 2

上面代碼說明,Proxy 實際上重載(overload)了點運算符,即用自己的定義覆蓋了語言的原始定義。
ES6 原生提供 Proxy 構造函數,用來生成 Proxy 實例。

var proxy = new Proxy(target, handler);

Proxy 對象的所有用法,都是上面這種形式,不同的只是 handler 參數的寫法。其中, new Proxy() 表示生成一個 Proxy 實例, target 參數表示所要攔
截的目標對象, handler 參數也是一個對象,用來定制攔截行為。
下面是另一個攔截讀取屬性行為的例子

var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); proxy.time // 35 proxy.name // 35 proxy.title // 35

上面代碼中,作為構造函數, Proxy 接受兩個參數。第一個參數是所要代理的目標對象(上例是一個空對象),即如果沒有 Proxy 的介入,操作原來要訪
問的就是這個對象;第二個參數是一個配置對象,對于每一個被代理的操作,需要提供一個對應的處理函數,該函數將攔截對應的操作。比如,上面代碼
中,配置對象有一個 get 方法,用來攔截對目標對象屬性的訪問請求。 get 方法的兩個參數分別是目標對象和所要訪問的屬性。可以看到,由于攔截函數
總是返回 35 ,所以訪問任何屬性都得到 35 。
注意,要使得 Proxy 起作用,必須針對 Proxy 實例(上例是 proxy 對象)進行操作,而不是針對目標對象(上例是空對象)進行操作。
如果 handler 沒有設置任何攔截,那就等同于直接通向原對象。

var target = {}; var handler = {}; var proxy = new Proxy(target, handler); proxy.a = 'b'; target.a // "b"

上面代碼中, handler 是一個空對象,沒有任何攔截效果,訪問 proxy 就等同于訪問 target 。
一個技巧是將 Proxy 對象,設置到 object.proxy 屬性,從而可以在 object 對象上調用。

var object = { proxy: new Proxy(target, handler) };

Proxy 實例也可以作為其他對象的原型對象

var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); let obj = Object.create(proxy); obj.time // 35

上面代碼中, proxy 對象是 obj 對象的原型, obj 對象本身并沒有 time 屬性,所以根據原型鏈,會在 proxy 對象上讀取該屬性,導致被攔截。
同一個攔截器函數,可以設置攔截多個操作。

var handler = { get: function(target, name) { if (name === 'prototype') { return Object.prototype; } return 'Hello, ' + name; }, apply: function(target, thisBinding, args) { return args[0]; }, construct: function(target, args) { return {value: args[1]}; } }; var fproxy = new Proxy(function(x, y) { return x + y; }, handler); fproxy(1, 2) // 1 new fproxy(1, 2) // {value: 2} fproxy.prototype === Object.prototype // true fproxy.foo === "Hello, foo" // true

對于可以設置、但沒有設置攔截的操作,則直接落在目標對象上,按照原先的方式產生結果。
下面是 Proxy 支持的攔截操作一覽,一共 13 種。
get(target, propKey, receiver):攔截對象屬性的讀取,比如 proxy.foo 和 proxy['foo'] 。
set(target, propKey, value, receiver):攔截對象屬性的設置,比如 proxy.foo = v 或 proxy['foo'] = v ,返回一個布爾值。
has(target, propKey):攔截 propKey in proxy 的操作,返回一個布爾值。
deleteProperty(target, propKey):攔截 delete proxy[propKey] 的操作,返回一個布爾值。
ownKeys(target):攔截 Object.getOwnPropertyNames(proxy) 、 Object.getOwnPropertySymbols(proxy) 、 Object.keys(proxy) ,返回一
個數組。該方法返回目標對象所有自身的屬性的屬性名,而 Object.keys() 的返回結果僅包括目標對象自身的可遍歷屬性。
getOwnPropertyDescriptor(target, propKey):攔截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回屬性的描述對象。
defineProperty(target, propKey, propDesc):攔截 Object.defineProperty(proxy, propKey, propDesc) 、
Object.defineProperties(proxy, propDescs) ,返回一個布爾值。
preventExtensions(target):攔截 Object.preventExtensions(proxy) ,返回一個布爾值。
getPrototypeOf(target):攔截 Object.getPrototypeOf(proxy) ,返回一個對象。
isExtensible(target):攔截 Object.isExtensible(proxy) ,返回一個布爾值。
setPrototypeOf(target, proto):攔截 Object.setPrototypeOf(proxy, proto) ,返回一個布爾值。如果目標對象是函數,那么還有兩種額
外操作可以攔截。
apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如 proxy(...args) 、 proxy.call(object, ...args) 、
proxy.apply(...) 。
construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如 new proxy(...args)?

2. Proxy 實例的方法

下面是上面這些攔截方法的詳細介紹。

2.1get()

get 方法用于攔截某個屬性的讀取操作,可以接受三個參數,依次為目標對象、屬性名和 proxy 實例本身(即 this 關鍵字指向的那個對象),其中最后
一個參數可選。
get 方法的用法,上文已經有一個例子,下面是另一個攔截讀取操作的例子

var person = { name: "張三" }; var proxy = new Proxy(person, { get: function(target, property) { if (property in target) { return target[property]; } else { throw new ReferenceError("Property \"" + property + "\" does not exist."); } } }); proxy.name // "張三" proxy.age // 拋出一個錯誤

上面代碼表示,如果訪問目標對象不存在的屬性,會拋出一個錯誤。如果沒有這個攔截函數,訪問不存在的屬性,只會返回 undefined 。
get 方法可以繼承。

let proto = new Proxy({}, { get(target, propertyKey, receiver) { console.log('GET ' + propertyKey); return target[propertyKey]; } }); let obj = Object.create(proto); obj.foo // "GET foo"

上面代碼中,攔截操作定義在 Prototype 對象上面,所以如果讀取 obj 對象繼承的屬性時,攔截會生效。
下面的例子使用 get 攔截,實現數組讀取負數的索引。

function createArray(...elements) { let handler = { get(target, propKey, receiver) { let index = Number(propKey); if (index < 0) { propKey = String(target.length + index); } return Reflect.get(target, propKey, receiver); } }; let target = []; target.push(...elements); return new Proxy(target, handler); } let arr = createArray('a', 'b', 'c'); arr[-1] // c

上面代碼中,數組的位置參數是 -1 ,就會輸出數組的倒數最后一個成員。
利用 Proxy,可以將讀取屬性的操作( get ),轉變為執行某個函數,從而實現屬性的鏈式操作

var pipe = (function () { return function (value) { var funcStack = []; var oproxy = new Proxy({} , { get : function (pipeObject, fnName) { if (fnName === 'get') { return funcStack.reduce(function (val, fn) { return fn(val); },value); } funcStack.push(window[fnName]); return oproxy; } }); return oproxy; } }()); var double = n => n * 2; var pow = n => n * n; var reverseInt = n => n.toString().split("").reverse().join("") | 0; pipe(3).double.pow.reverseInt.get; // 63

上面代碼設置 Proxy 以后,達到了將函數名鏈式使用的效果。
下面的例子則是利用 get 攔截,實現一個生成各種 DOM 節點的通用函數 dom 。

const dom = new Proxy({}, { get(target, property) { return function(attrs = {}, ...children) { const el = document.createElement(property); for (let prop of Object.keys(attrs)) { el.setAttribute(prop, attrs[prop]); } for (let child of children) { if (typeof child === 'string') { child = document.createTextNode(child); } el.appendChild(child); } return el; } } }); const el = dom.div({}, 'Hello, my name is ', dom.a({href: '//example.com'}, 'Mark'), '. I like:', dom.ul({}, dom.li({}, 'The web'), dom.li({}, 'Food'), dom.li({}, '…actually that\'s it') ) ); document.body.appendChild(el);

下面是一個 get 方法的第三個參數的例子

const proxy = new Proxy({}, { get: function(target, property, receiver) { return receiver; } }); proxy.getReceiver === proxy // true

上面代碼中, get 方法的第三個參數 receiver ,總是為當前的 Proxy 實例。
如果一個屬性不可配置(configurable)和不可寫(writable),則該屬性不能被代理,通過 Proxy 對象訪問該屬性會報錯

const target = Object.defineProperties({}, { foo: { value: 123, writable: false, configurable: false }, }); const handler = { get(target, propKey) { return 'abc'; } }; const proxy = new Proxy(target, handler); proxy.foo // TypeError: Invariant check failed

2.2set()

set 方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次為目標對象、屬性名、屬性值和 Proxy 實例本身,其中最后一個參數可選。
假定 Person 對象有一個 age 屬性,該屬性應該是一個不大于 200 的整數,那么可以使用 Proxy 保證 age 的屬性值符合要求。

let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value > 200) { throw new RangeError('The age seems invalid'); } } // 對于age以外的屬性,直接保存 obj[prop] = value; } }; let person = new Proxy({}, validator); person.age = 100; person.age // 100 person.age = 'young' // 報錯 person.age = 300 // 報錯

上面代碼中,由于設置了存值函數 set ,任何不符合要求的 age 屬性賦值,都會拋出一個錯誤,這是數據驗證的一種實現方法。利用 set 方法,還可以數
據綁定,即每當對象發生變化時,會自動更新 DOM。
有時,我們會在對象上面設置內部屬性,屬性名的第一個字符使用下劃線開頭,表示這些屬性不應該被外部使用。結合 get 和 set 方法,就可以做到防止
這些內部屬性被外部讀寫。

const handler = { get (target, key) { invariant(key, 'get'); return target[key]; }, set (target, key, value) { invariant(key, 'set'); target[key] = value; return true; } }; function invariant (key, action) { if (key[0] === '_') { throw new Error(`Invalid attempt to ${action} private "${key}" property`); } } const target = {}; const proxy = new Proxy(target, handler); proxy._prop // Error: Invalid attempt to get private "_prop" property proxy._prop = 'c' // Error: Invalid attempt to set private "_prop" property

上面代碼中,只要讀寫的屬性名的第一個字符是下劃線,一律拋錯,從而達到禁止讀寫內部屬性的目的。
下面是 set 方法第四個參數的例子。

const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; } }; const proxy = new Proxy({}, handler); proxy.foo = 'bar'; proxy.foo === proxy // true

上面代碼中, set 方法的第四個參數 receiver ,總是返回 this 關鍵字所指向的那個對象,即 proxy 實例本身。
注意,如果目標對象自身的某個屬性,不可寫也不可配置,那么 set 不得改變這個屬性的值,只能返回同樣的值,否則報錯。

2.3apply()

apply 方法攔截函數的調用、 call 和 apply 操作。
apply 方法可以接受三個參數,分別是目標對象、目標對象的上下文對象( this )和目標對象的參數數組。

var handler = { apply (target, ctx, args) { return Reflect.apply(...arguments); } };

下面是一個例子。

var target = function () { return 'I am the target'; }; var handler = { apply: function () { return 'I am the proxy'; } }; var p = new Proxy(target, handler); p() // "I am the proxy"

上面代碼中,變量 p 是 Proxy 的實例,當它作為函數調用時( p() ),就會被 apply 方法攔截,返回一個字符串。
下面是另外一個例子。?

var twice = { apply (target, ctx, args) { return Reflect.apply(...arguments) * 2; } }; function sum (left, right) { return left + right; }; var proxy = new Proxy(sum, twice); proxy(1, 2) // 6 proxy.call(null, 5, 6) // 22 proxy.apply(null, [7, 8]) // 30

上面代碼中,每當執行 proxy 函數(直接調用或 call 和 apply 調用),就會被 apply 方法攔截。
另外,直接調用 Reflect.apply 方法,也會被攔截。

Reflect.apply(proxy, null, [9, 10]) // 38

2.4has()

has 方法用來攔截 HasProperty 操作,即判斷對象是否具有某個屬性時,這個方法會生效。典型的操作就是 in 運算符。
下面的例子使用 has 方法隱藏某些屬性,不被 in 運算符發現。

var handler = { has (target, key) { if (key[0] === '_') { return false; } return key in target; } }; var target = { _prop: 'foo', prop: 'foo' }; var proxy = new Proxy(target, handler); '_prop' in proxy // false

上面代碼中,如果原對象的屬性名的第一個字符是下劃線, proxy.has 就會返回 false ,從而不會被 in 運算符發現。
如果原對象不可配置或者禁止擴展,這時 has 攔截會報錯。

var obj = { a: 10 }; Object.preventExtensions(obj); var p = new Proxy(obj, { has: function(target, prop) { return false; } }); 'a' in p // TypeError is thrown

上面代碼中, obj 對象禁止擴展,結果使用 has 攔截就會報錯。也就是說,如果某個屬性不可配置(或者目標對象不可擴展),則 has 方法就不得“隱
藏”(即返回 false )目標對象的該屬性。
值得注意的是, has 方法攔截的是 HasProperty 操作,而不是 HasOwnProperty 操作,即 has 方法不判斷一個屬性是對象自身的屬性,還是繼承的屬性。
另外,雖然 for...in 循環也用到了 in 運算符,但是 has 攔截對 for...in 循環不生效。

let stu1 = {name: '張三', score: 59}; let stu2 = {name: '李四', score: 99}; let handler = { has(target, prop) { if (prop === 'score' && target[prop] < 60) { console.log(`${target.name} 不及格`); return false; } return prop in target; } } let oproxy1 = new Proxy(stu1, handler); let oproxy2 = new Proxy(stu2, handler); 'score' in oproxy1 // 張三 不及格 // false 'score' in oproxy2 // true for (let a in oproxy1) { console.log(oproxy1[a]); } // 張三 // 59 for (let b in oproxy2) { console.log(oproxy2[b]); } // 李四 // 99

上面代碼中, has 攔截只對 in 循環生效,對 for...in 循環不生效,導致不符合要求的屬性沒有被排除在 for...in 循環之外

2.5construct()

construct 方法用于攔截 new 命令,下面是攔截對象的寫法。

var handler = { construct (target, args, newTarget) { return new target(...args); } };

construct 方法可以接受兩個參數。
target : 目標對象
args :構建函數的參數對象
下面是一個例子。

var p = new Proxy(function () {}, { construct: function(target, args) { console.log('called: ' + args.join(', ')); return { value: args[0] * 10 }; } }); (new p(1)).value // "called: 1" // 10

construct 方法返回的必須是一個對象,否則會報錯。

var p = new Proxy(function() {}, { construct: function(target, argumentsList) { return 1; } }); new p() // 報錯

2.7deleteProperty()

deleteProperty 方法用于攔截 delete 操作,如果這個方法拋出錯誤或者返回 false ,當前屬性就無法被 delete 命令刪除。

var handler = { deleteProperty (target, key) { invariant(key, 'delete'); return true; } }; function invariant (key, action) { if (key[0] === '_') { throw new Error(`Invalid attempt to ${action} private "${key}" property`); } } var target = { _prop: 'foo' }; var proxy = new Proxy(target, handler); delete proxy._prop // Error: Invalid attempt to delete private "_prop" property

上面代碼中, deleteProperty 方法攔截了 delete 操作符,刪除第一個字符為下劃線的屬性會報錯。
注意,目標對象自身的不可配置(configurable)的屬性,不能被 deleteProperty 方法刪除,否則報錯。

2.8defineProperty()

defineProperty 方法攔截了 Object.defineProperty 操作

var handler = { defineProperty (target, key, descriptor) { return false; } }; var target = {}; var proxy = new Proxy(target, handler); proxy.foo = 'bar' // TypeError: proxy defineProperty handler returned false for property '"foo"'

上面代碼中, defineProperty 方法返回 false ,導致添加新屬性會拋出錯誤。
注意,如果目標對象不可擴展(extensible),則 defineProperty 不能增加目標對象上不存在的屬性,否則會報錯。另外,如果目標對象的某個屬性不可
寫(writable)或不可配置(configurable),則 defineProperty 方法不得改變這兩個設置。

2.9getOwnPropertyDescriptor()

getOwnPropertyDescriptor 方法攔截 Object.getOwnPropertyDescriptor() ,返回一個屬性描述對象或者 undefined 。

var handler = { getOwnPropertyDescriptor (target, key) { if (key[0] === '_') { return; } return Object.getOwnPropertyDescriptor(target, key); } }; var target = { _foo: 'bar', baz: 'tar' }; var proxy = new Proxy(target, handler); Object.getOwnPropertyDescriptor(proxy, 'wat') // undefined Object.getOwnPropertyDescriptor(proxy, '_foo') // undefined Object.getOwnPropertyDescriptor(proxy, 'baz') // { value: 'tar', writable: true, enumerable: true, configurable: true }

上面代碼中, handler.getOwnPropertyDescriptor 方法對于第一個字符為下劃線的屬性名會返回 undefined 。

2.10getPrototypeOf()

getPrototypeOf 方法主要用來攔截獲取對象原型。具體來說,攔截下面這些操作。

Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()

eflect.getPrototypeOf()
instanceof
下面是一個例子

var proto = {}; var p = new Proxy({}, { getPrototypeOf(target) { return proto; } }); Object.getPrototypeOf(p) === proto // true

上面代碼中, getPrototypeOf 方法攔截 Object.getPrototypeOf() ,返回 proto 對象。
注意, getPrototypeOf 方法的返回值必須是對象或者 null ,否則報錯。另外,如果目標對象不可擴展(extensible), getPrototypeOf 方法必須返回
目標對象的原型對象。

2.11isExtensible()

isExtensible 方法攔截 Object.isExtensible 操作

var p = new Proxy({}, { isExtensible: function(target) { console.log("called"); return true; } }); Object.isExtensible(p) // "called" // true

上面代碼設置了 isExtensible 方法,在調用 Object.isExtensible 時會輸出 called 。
注意,該方法只能返回布爾值,否則返回值會被自動轉為布爾值。
這個方法有一個強限制,它的返回值必須與目標對象的 isExtensible 屬性保持一致,否則就會拋出錯誤

Object.isExtensible(proxy) === Object.isExtensible(target)

下面是一個例子。

var p = new Proxy({}, { isExtensible: function(target) { return false; } }); Object.isExtensible(p) // 報錯

2.12ownKeys()

ownKeys 方法用來攔截對象自身屬性的讀取操作。具體來說,攔截以下操作。

Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
下面是攔截 Object.keys() 的例子。

let target = { a: 1, b: 2, c: 3 }; let handler = { ownKeys(target) { return ['a']; } }; let proxy = new Proxy(target, handler); Object.keys(proxy) // [ 'a' ]

上面代碼攔截了對于 target 對象的 Object.keys() 操作,只返回 a 、 b 、 c 三個屬性之中的 a 屬性。
下面的例子是攔截第一個字符為下劃線的屬性名。

let target = { _bar: 'foo', _prop: 'bar', prop: 'baz' }; let handler = { ownKeys (target) { return Reflect.ownKeys(target).filter(key => key[0] !== '_'); } }; let proxy = new Proxy(target, handler); for (let key of Object.keys(proxy)) { console.log(target[key]); } // "baz"

注意,使用 Object.keys 方法時,有三類屬性會被 ownKeys 方法自動過濾,不會返回。
目標對象上不存在的屬性
屬性名為 Symbol 值
不可遍歷( enumerable )的屬性

let target = { a: 1, b: 2, c: 3, [Symbol.for('secret')]: '4', }; Object.defineProperty(target, 'key', { enumerable: false, configurable: true, writable: true, value: 'static' }); let handler = { ownKeys(target) { return ['a', 'd', Symbol.for('secret'), 'key']; } }; let proxy = new Proxy(target, handler); Object.keys(proxy) // ['a']

上面代碼中, ownKeys 方法之中,顯式返回不存在的屬性( d )、Symbol 值( Symbol.for('secret') )、不可遍歷的屬性( key ),結果都被自動過濾
掉。
ownKeys 方法還可以攔截 Object.getOwnPropertyNames()?

var p = new Proxy({}, { ownKeys: function(target) { return ['a', 'b', 'c']; } }); Object.getOwnPropertyNames(p) // [ 'a', 'b', 'c' ]

ownKeys 方法返回的數組成員,只能是字符串或 Symbol 值。如果有其他類型的值,或者返回的根本不是數組,就會報錯

var obj = {}; var p = new Proxy(obj, { ownKeys: function(target) { return [123, true, undefined, null, {}, []]; } }); Object.getOwnPropertyNames(p) // Uncaught TypeError: 123 is not a valid property name

上面代碼中, ownKeys 方法雖然返回一個數組,但是每一個數組成員都不是字符串或 Symbol 值,因此就報錯了。
如果目標對象自身包含不可配置的屬性,則該屬性必須被 ownKeys 方法返回,否則報錯

var obj = {}; Object.defineProperty(obj, 'a', { configurable: false, enumerable: true, value: 10 } ); var p = new Proxy(obj, { ownKeys: function(target) { return ['b']; } }); Object.getOwnPropertyNames(p) // Uncaught TypeError: 'ownKeys' on proxy: trap result did not include 'a

上面代碼中, obj 對象的 a 屬性是不可配置的,這時 ownKeys 方法返回的數組之中,必須包含 a ,否則會報錯。
另外,如果目標對象是不可擴展的(non-extensition),這時 ownKeys 方法返回的數組之中,必須包含原對象的所有屬性,且不能包含多余的屬性,否則
報錯

var obj = { a: 1 }; Object.preventExtensions(obj); var p = new Proxy(obj, { ownKeys: function(target) { return ['a', 'b']; } }); Object.getOwnPropertyNames(p) // Uncaught TypeError: 'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible

上面代碼中, obj 對象是不可擴展的,這時 ownKeys 方法返回的數組之中,包含了 obj 對象的多余屬性 b ,所以導致了報錯。

2.13preventExtensions()

preventExtensions 方法攔截 Object.preventExtensions() 。該方法必須返回一個布爾值,否則會被自動轉為布爾值。
這個方法有一個限制,只有目標對象不可擴展時(即 Object.isExtensible(proxy) 為 false ), proxy.preventExtensions 才能返回 true ,否則會報
錯。

var p = new Proxy({}, { preventExtensions: function(target) { return true; } }); Object.preventExtensions(p) // 報錯

上面代碼中, proxy.preventExtensions 方法返回 true ,但這時 Object.isExtensible(proxy) 會返回 true ,因此報錯。
為了防止出現這個問題,通常要在 proxy.preventExtensions 方法里面,調用一次 Object.preventExtensions 。

var p = new Proxy({}, { preventExtensions: function(target) { console.log('called'); Object.preventExtensions(target); return true; } }); Object.preventExtensions(p) // "called" // true

2.14setPrototypeOf()

setPrototypeOf 方法主要用來攔截 Object.setPrototypeOf 方法。
下面是一個例子。

var handler = { setPrototypeOf (target, proto) { throw new Error('Changing the prototype is forbidden'); } }; var proto = {}; var target = function () {}; var proxy = new Proxy(target, handler); Object.setPrototypeOf(proxy, proto); // Error: Changing the prototype is forbidden

上面代碼中,只要修改 target 的原型對象,就會報錯。
注意,該方法只能返回布爾值,否則會被自動轉為布爾值。另外,如果目標對象不可擴展(extensible), setPrototypeOf 方法不得改變目標對象的原
型。

3. Proxy.revocable()

Proxy.revocable 方法返回一個可取消的 Proxy 實例。

let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked

Proxy.revocable 方法返回一個對象,該對象的 proxy 屬性是 Proxy 實例, revoke 屬性是一個函數,可以取消 Proxy 實例。上面代碼中,當執行 revoke
函數之后,再訪問 Proxy 實例,就會拋出一個錯誤。
Proxy.revocable 的一個使用場景是,目標對象不允許直接訪問,必須通過代理訪問,一旦訪問結束,就收回代理權,不允許再次訪問。


4. this 問題

雖然 Proxy 可以代理針對目標對象的訪問,但它不是目標對象的透明代理,即不做任何攔截的情況下,也無法保證與目標對象的行為一致。主要原因就是
在 Proxy 代理的情況下,目標對象內部的 this 關鍵字會指向 Proxy 代理。

const target = { m: function () { console.log(this === proxy); } }; const handler = {}; const proxy = new Proxy(target, handler); target.m() // false proxy.m() // true

上面代碼中,一旦 proxy 代理 target.m ,后者內部的 this 就是指向 proxy ,而不是 target?

下面是一個例子,由于 this 指向的變化,導致 Proxy 無法代理目標對象。

const _name = new WeakMap(); class Person { constructor(name) { _name.set(this, name); } get name() { return _name.get(this); } } const jane = new Person('Jane'); jane.name // 'Jane' const proxy = new Proxy(jane, {}); proxy.name // undefined

上面代碼中,目標對象 jane 的 name 屬性,實際保存在外部 WeakMap 對象 _name 上面,通過 this 鍵區分。由于通過 proxy.name 訪問時, this 指向
proxy ,導致無法取到值,所以返回 undefined 。
此外,有些原生對象的內部屬性,只有通過正確的 this 才能拿到,所以 Proxy 也無法代理這些原生對象的屬性

const target = new Date(); const handler = {}; const proxy = new Proxy(target, handler); proxy.getDate(); // TypeError: this is not a Date object

上面代碼中, getDate 方法只能在 Date 對象實例上面拿到,如果 this 不是 Date 對象實例就會報錯。這時, this 綁定原始對象,就可以解決這個問題。

const target = new Date('2015-01-01'); const handler = { get(target, prop) { if (prop === 'getDate') { return target.getDate.bind(target); } return Reflect.get(target, prop); } }; const proxy = new Proxy(target, handler); proxy.getDate() // 1

5. 實例:Web 服務的客戶端

Proxy 對象可以攔截目標對象的任意屬性,這使得它很合適用來寫 Web 服務的客戶端

const service = createWebService('http://example.com/data'); service.employees().then(json => { const employees = JSON.parse(json); // ··· });

上面代碼新建了一個 Web 服務的接口,這個接口返回各種數據。Proxy 可以攔截這個對象的任意屬性,所以不用為每一種數據寫一個適配方法,只要寫
一個 Proxy 攔截就可以了。

function createWebService(baseUrl) { return new Proxy({}, { get(target, propKey, receiver) { return () => httpGet(baseUrl+'/' + propKey); } }); }

同理,Proxy 也可以用來實現數據庫的 ORM 層。

本博客源于本人閱讀相關書籍和視頻總結,創作不易,謝謝點贊支持。學到就是賺到。我是歌謠,勵志成為一名優秀的技術革新人員。

歡迎私信交流,一起學習,一起成長。

推薦鏈接 其他文件目錄參照

“睡服“面試官系列之各系列目錄匯總(建議學習收藏)

總結

以上是生活随笔為你收集整理的“睡服”面试官系列第五篇之proxy(建议收藏学习)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。