博客 / 詳情

返回

【吃透】JS代理(Proxy)

今天給大家分享如何從根本上吃透js代理,嘎嘎的😍,看下面

咱們首先得知道:

代理是 js 中的對象,它允許你創建對象的代理,同時還可以為標準對象操作定義自定義行為。這意味着,譬如,如果有人試圖從對象中獲取屬性的值,你可以定義一組自定義行為。這使代理變成了一個非常強大的工具,所以讓我們看看它們是如何工作的。get set has

一、js 代理的基礎知識✔

en... 上面聽起來很複雜,所以讓我們看一個沒有任何方法的簡單例子,首先,可以使用構造函數創建代理,該構造函數new Proxy()接受兩個參數:

  • 這是原始對象。target
  • 這是我們將添加到對象之上的方法或屬性集。handler

可以包含預定義方法的列表。例如,如果我們為 定義一個方法,它將自定義當我們嘗試從對象中獲取項目時會發生神馬?handler get

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    get: (object, prop) => {
        console.log(`Hi ${object.firstName} ${object.lastName}`)
    }
}

let proxyExample = new Proxy(target, handler);

proxyExample.age; // "Hi 月 弦"

由於我們嘗試在代理上找到的值,因此觸發了自定義處理程序 - 因此我們控制枱記錄了正如你所看到的,這可以成為一個非常強大的工具,因為當調用一個對象的標準操作時,你可以做各種各樣的事情。🥰get proxyExample.age get Hi ${object.firstName} ${object.lastName}

請注意,當我們添加到上面時,我們有一些自定義參數。可以添加到代理的每個處理程序都帶有一組自定義參數。get handler

因為使用的功能是:get(object, prop, receiver)

  • object- 原始對象。在上面的示例中,這是包含 和 的對象。firstName`lastName`age
  • prop- 試圖更改的屬性。在上面的示例中,.get`age`
  • reciever- 代理本身。

1. 處理程序方法稱為“陷阱”💭

代理中的處理程序方法通常稱為陷阱,因此,如果你看到這個術語四處亂竄,請不要感到困惑。之所以這樣稱呼它們,是因為它們“捕獲”對象操作並執行某種自定義代碼。例如,每當有人嘗試獲取代理的屬性時,控制枱都會記錄一些內容。🧐

2. 更新代理值

代理仍引用原始對象,因此對象值和代理值的引用相同。因此,如果你嘗試更新代理的值,它也會更新原始對象的值。例如,下面我嘗試更新代理,如你所見,原始對象和代理都已更新:

let target = {
    name: "月",
    age: 152
}

let handler = {
}

let proxyExample = new Proxy(target, handler);
proxyExample.name = "Dave";

console.log(proxyExample.name); // Dave
console.log(target.name); // Dave

瞭解這一點很有用 - 不要指望代理會完全創建一個單獨的對象 - 這不是複製對象的方法。

二、 js代理中的自定義處理程序

代理有許多自定義處理程序,允許我們基本上“捕獲”任何對象操作並用它做一些有趣的事情。最常用的方法是👍:

  • proxy.apply(objects, thisObject, argList)- 捕獲函數調用的方法。
  • proxy.construct(object, argList, newTarget)- 使用 constructor 關鍵字調用函數時捕獲的方法。new
  • proxy.defineProperty(object, prop, descriptor)- 使用將新屬性添加到對象時捕獲的方法。Object.defineProperty
  • proxy.deleteProperty(object, prop)- 從對象中刪除屬性時捕獲的方法。
  • proxy.get(object, prop, receiver)- 如前所述,當有人試圖從對象獲取屬性時,一種陷阱的方法。get
  • proxy.set(object, prop, value, receiver)- 當屬性被賦予值時捕獲的方法。
  • proxy.has(object, prop)- 一種誘捕操作員的方法。in

上面的方法足以完成你想用代理做的幾乎所有事情。它們很好地覆蓋了所有主要對象操作,可以根據需要進行修改和自定義。

不過,還有一些 - 因此,除了這些非常基本的對象操作外,我們還可以訪問:

  • proxy.getPrototypeOf(object)- 捕獲方法的方法。Object.getPrototypeOf
  • proxy.getOwnPropertyDescriptor(object, prop)- 捕獲 的方法,它返回特定屬性的描述符 - 例如,它是可枚舉的,等等。getOwnPropertyDescriptor
  • proxy.isExtensible(object)- 一種在觸發時捕獲的方法。Object.isExtensible()
  • proxy.preventExtensions(object)- 一種在觸發時捕獲的方法。Object.preventExtensions()
  • proxy.setPrototypeOf(object, prototype)- 一種在觸發時捕獲的方法。Object.setPrototypeOf()
  • proxy.ownKeys(object)- 一種在觸發 like 方法時捕獲的方法。Object.getOwnPropertyNames()

讓我們更詳細地看一下其中的一些,以瞭解代理的工作原理。

三、在代理中使用 in 運算符

我們已經介紹了,那麼讓我們看看。這主要在我們使用運算符時觸發。例如,如果我們想通過控制枱記錄使用時屬性不存在的事實,我們可以執行如下操作:proxy.get() has() in in

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    has: (object, prop) => {
        if(object[prop] === undefined) {
            console.log('Property not found');
        }
        return object[prop]
    }
}

let proxyExample = new Proxy(target, handler);

console.log('address' in proxyExample); 
// 'Property not found' 
// false

由於address未在target中未定義,將返回 false,正如我們在代理中定義的那樣。address target proxyExample address' in proxyExample Property not found

四、 使用代理設置值

你可能想要修改的類似有用方法,下面,我使用自定義處理程序來修改當我們嘗試更改用户年齡時發生的情況。對於每個設置操作,如果屬性是一個數字,那麼當數字更新時,我們將記錄差異。set()🧐

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    set: (object, prop, value) => {
        if(typeof object[prop] === "number" && typeof value === "number") {
            console.log(`Change in number was ${value - object[prop]}`);
        }
        return object[prop]
    }
}

let proxyExample = new Proxy(target, handler);

proxyExample['age'] = 204;
// Change in number was 52

由於 和 更新後的值都是數字,因此我們不僅將值更新 ,而且還會得到一個有用的控制枱日誌,告訴我們兩個數字之間的區別是什麼。很酷,對吧?proxyExample.age 204

雖然會針對任何設置的操作(包括向對象添加新項)觸發,但你也可以使用 實現類似的行為。例如,這也將起作用:set defineProperty

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    defineProperty: (object, prop, descriptor) => {
        console.log(`A property was set - ${prop}`);
    },
}

let proxyExample = new Proxy(target, handler);

proxyExample['age'] = "123 Fake Street";
// A property was set - address

但是請注意,如果你將 和 兩者都添加為處理程序,則在我們使用方括號或表示法設置屬性的情況下將覆蓋。 但是,如果你顯式使用,仍會觸發,如下所示:set defineProperty set defineProperty[] defineProperty Object.defineProperty

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    defineProperty: (object, prop, descriptor) => {
        console.log(`A property was set with defineProperty - ${prop}`);
        return true;
    },
    set: (object, prop, descriptor) => {
        console.log(`A property was set - ${prop}`);
        return true;
    },
}

let proxyExample = new Proxy(target, handler);

Object.defineProperty(proxyExample, 'socialMedia', {
    value: 'twitter',
    writable: false
});
proxyExample['age'] = "123 Fake Street";
// A property was set with defineProperty - socialMedia
// A property was set - address

五、使用代理刪除值

除了這些有用的方法外,我們還可以使用這些方法來處理如果用户使用關鍵字刪除某些內容時會發生什麼。例如,我們可以讓某人知道屬性正在被刪除:deleteProperty delete

let target = {
    firstName: "月",
    lastName: "弦",
    age: 152
}

let handler = {
    deleteProperty: (object, prop) => {
        console.log(`Poof! The ${prop} property was deleted`);
    },
}

let proxyExample = new Proxy(target, handler);

delete proxyExample['age'];
// Poof! The age property was deleted

六、使用代理自定義函數調用❤

代理還允許我們在想要調用函數時運行自定義代碼。這是因為函數是對象的 Javascript 怪癖。有兩種方法可以做到這一點:

  • 替換為 handler,用於捕獲標準函數調用。apply()
  • 與處理程序一起使用,該處理程序捕獲構造函數調用。construct() new

下面是一個快速示例,我們捕獲一個函數調用,並通過在其輸出末尾附加一些內容來修改它

let target = (firstName, lastName) => {
    return `Hello ${firstName} ${lastName}`
}

let handler = {
    apply: (object, thisObject, argsList) => {
        let functionCall = object(...argsList);
        return `${functionCall}. I hope you are having a nice day!`
    },
}

let proxyExample = new Proxy(target, handler);

proxyExample("月", "弦");
// Returns
// Hello 月 弦. I hope you are having a nice day!

apply接受三個參數:

  • object- 原始對象。
  • thisObject- 函數/對象的值。this
  • argsList- 傳遞給函數的參數。

上面,我們使用包含原始函數的參數調用了我們的函數。然後我們在它的末尾添加了一些文本來更改函數的輸出。再説一次,很酷,對吧?object target

我們也可以使用來做同樣的事情,它也有三個參數:construct

  • object- 原始對象。
  • argsList- 函數/對象的參數。
  • newTarget- 最初調用的構造函數 - 即代理。

下面是一個函數返回一個對象的示例,我們使用代理上的方法向其添加更多屬性:construct

function target(a, b, c) {
    return { 
        a: a,
        b: b,
        c: c
    }
}

let handler = {
    construct: (object, argsList, newTarget) => {
        let functionCall = object(...argsList);
        return { ...functionCall, d: 105, e: 45 }
    },
}

let proxyExample = new Proxy(target, handler);

new proxyExample(15, 24, 45);
// Returns
// {a: 15, b: 24, c: 45, d: 105, e: 45}

七、最後

代理是 js 武器庫中一個了不起的工具,可讓你修改對象的基本操作。這裏有大量的方法可以玩,如果你正確使用它們,它們可以大大簡化你的代碼。最後我希望jym喜歡這篇文章😂

感謝瀏覽本文,共同進步🤞,若有更好的建議,歡迎評論區討論哈🌹。

本文參與了SegmentFault 思否寫作挑戰賽活動,歡迎正在閲讀的你也加入。
user avatar 1023 頭像 laughingzhu 頭像 jidongdehai_co4lxh 頭像 tigerandflower 頭像 esunr 頭像 codepencil 頭像 flymon 頭像 _raymond 頭像 sunhengzhe 頭像 zhangxishuo 頭像 columsys 頭像 suporka 頭像
60 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.