JavaScript >> Javascript 文檔 >  >> JavaScript

ES6 中的 JavaScript 代理介紹

簡介

在本文中,我們將討論 JavaScript 代理 JavaScript 版本 ECMAScript 6 引入的 (ES6)。我們將使用一些現有的 ES6 語法,包括本文中的擴展運算符。所以如果你有一些 ES6 的基礎知識會很有幫助。

什麼是代理?

JavaScript 代理能夠改變對象和函數的基本行為。我們可以擴展語言以更好地滿足我們的要求,或者簡單地將其用於屬性的驗證和訪問控制等事情。

在引入代理之前,我們沒有本地級別的訪問權限來更改對像或函數的基本行為。但是有了它們,我們就有能力充當中間層,改變對象的訪問方式,生成函數被調用的次數等信息。

屬性代理示例

讓我們從一個簡單的例子開始,看看代理的作用。首先,讓我們使用 firstName 創建一個人對象 , lastName , 和 age 屬性:

const person = {
    firstName: 'John',
    lastName: 'Doe',
    age: 21
};

現在讓我們通過將它傳遞給 Proxy 來創建一個簡單的代理 構造函數。它接受稱為 target 的參數 和 handler .稍後將詳細說明這兩者。

我們先創建一個處理程序對象:

const handler = {
    get(target, property) {
        console.log(`you have read the property ${property}`);
        return target[property];
    }
};

這是創建簡單代理的方法:

const proxyPerson = new Proxy(person, handler);

console.log(proxyPerson.firstName);
console.log(proxyPerson.lastName);
console.log(proxyPerson.age);

運行這段代碼應該會產生:

you have read the property firstName
John
you have read the property lastName
Doe
you have read the property age
21

每次訪問該代理對象的屬性時,您都會收到帶有屬性名稱的控制台消息。這是一個非常簡單的 JavaScript 代理示例。因此,使用該示例,讓我們熟悉一些術語。

代理目標

第一個參數,target , 是您已將代理附加到的對象。這個對象會被代理用來存儲數據,也就是說如果你改變了目標對象的值,代理對象的值也會隨之改變。

如果你想避免這種情況,你可以通過 target 直接作為匿名對像傳遞給代理,或者您可以使用某種封裝方法通過創建立即調用函數表達式 (IIFE) 或單例來保護原始對象。

只是不要將您的對象暴露在將使用代理的外部,一切都應該沒問題。

原始目標對象的變化仍然反映在代理中:

console.log(proxyPerson.age);
person.age = 20;
console.log(proxyPerson.age);
you have read the property age
21
you have read the property age
20

代理處理程序

Proxy的第二個參數 構造函數是 handler ,它應該是一個對象,其中包含描述您想要控制 target 的方式的方法 的行為。此處理程序中的方法,例如 get() 方法,稱為陷阱 .

通過定義一個處理程序,例如我們在前面示例中定義的處理程序,我們可以為一個不會實現它的對象編寫自定義邏輯。

例如,您可以創建一個代理,在目標對象的屬性更新時更新緩存或數據庫。

代理陷阱

get() 陷阱

get() 當有人試圖訪問特定屬性時,陷阱會觸發。在前面的示例中,我們使用它在訪問屬性時打印了一個句子。

您可能已經知道,JavaScript 不支持私有屬性。所以有時作為慣例,開發人員使用下劃線 (_ ) 在屬性名稱前,例如 _securityNumber , 將其標識為私有財產。

但是,這實際上並沒有在代碼級別強制執行任何操作。開發人員只知道他們不應該直接訪問以 _ 開頭的屬性 .使用代理,我們可以改變這一點。

讓我們更新我們的 person 在名為 _ssn 的屬性中具有社會安全號碼的對象 :

const person = {
    firstName: 'John',
    lastName: 'Doe',
    age: 21,
    _ssn: '123-45-6789'
};

現在讓我們編輯 get() 如果有人試圖訪問以下劃線開頭的屬性,則陷阱拋出異常:

const handler = {
    get(target, property) {
        if (property[0] === '_') {
            throw new Error(`${property} is a private property`);
        }

        return target[property];
    }
}

const proxyPerson = new Proxy(person, handler);

console.log(proxyPerson._ssn);

如果您運行此代碼,您應該會在控制台上看到以下錯誤消息:

Error: _ssn is a private property

set() 陷阱

現在,我們來看看set() 陷阱,它控制在目標對象的屬性上設置值時的行為。為了給你一個清晰的例子,讓我們假設當你定義一個 person 對象 age 的值 應該在 0 的範圍內 到 150 .

您可能已經知道,JavaScript 是一種動態類型語言,這意味著變量可以在任何給定時間保存任何類型的值(字符串、數字、布爾值等)。所以通常很難執行 age 屬性只保存整數。但是,使用代理,我們可以控制設置屬性值的方式:

const handler = {
    set(target, property, value) {
        if (property === 'age') {
            if (!(typeof value === 'number')) {
                throw new Error('Age should be a number');
            }

            if (value < 0 || value > 150) {
                throw new Error("Age value should be in between 0 and 150");
            }
        }

        target[property] = value;
    }
};

const proxyPerson = new Proxy(person, handler);
proxyPerson.age = 170;

正如您在這段代碼中看到的,set() trap 接受三個參數,分別是:

  • target :代理附加到的目標對象
  • property :正在設置的屬性的名稱
  • value :分配給屬性的值

在這個陷阱中,我們檢查了屬性名稱是否為 age , 如果是,如果它也是一個數字並且值在 0 到 150 之間 - 如果不是,則拋出錯誤。

運行此代碼時,您應該會在控制台上看到以下錯誤消息:

Error: Age value should be in between 0 and 150

免費電子書:Git Essentials

查看我們的 Git 學習實踐指南,其中包含最佳實踐、行業認可的標準以及隨附的備忘單。停止谷歌搜索 Git 命令並真正學習 它!

另外,您可以嘗試分配一個字符串值,看看它是否會引發錯誤。

deleteProperty() 陷阱

現在讓我們繼續deleteProperty() 當您嘗試從對像中刪除屬性時將觸發的陷阱:

const handler = {
    deleteProperty(target, property) {
        console.log('You have deleted', property);
        delete target[property];
    }
};

const proxyPerson = new Proxy(person, handler);

delete proxyPerson.age;

如您所見,deleteProperty() 陷阱也接受 targetproperty 參數。

如果您運行此代碼,您應該會看到以下輸出:

You have deleted age

在函數中使用代理

apply() 陷阱

apply() 陷阱用於識別何時在代理對像上發生函數調用。首先,讓我們創建一個有名字和姓氏的人:

const person = {
    firstName: 'Sherlock',
    lastName: 'Holmes'
};

然後一個獲取全名的方法:

const getFullName = (person) => {
    return person.firstName + ' ' + person.lastName;
};

現在,讓我們創建一個代理方法,通過提供 apply() 將函數輸出轉換為大寫字母 陷阱在我們的處理程序中:

const getFullNameProxy = new Proxy(getFullName, {
    apply(target, thisArg, args) {
        return target(...args).toUpperCase();
    }
});

console.log(getFullNameProxy(person));

如您在此代碼示例中所見,apply() 調用函數時會調用陷阱。它接受三個參數 - target , thisArg (即 this 調用的參數)和 args ,這是傳遞給函數的參數列表。

我們使用了 apply() trap 使用 ES6 擴展語法使用給定的參數執行目標函數,並將結果轉換為大寫。所以你應該看到大寫的全名:

SHERLOCK HOLMES

使用代理的計算屬性

計算屬性是通過對其他現有屬性執行操作來計算的屬性。例如,假設我們有一個 person 具有 firstName 屬性的對象 和 lastName .有了這個,全名可以是這些屬性的組合,就像我們上一個例子一樣。因此,全名是一個計算屬性 .

首先,讓我們再次創建一個 person 有名字和姓氏的對象:

const person = {
    firstName: 'John',
    lastName: 'Doe'
};

然後我們可以使用 get() 創建一個處理程序 陷阱返回計算出來的全名,這是通過創建person的代理來實現的 :

const handler = {
    get(target, property) {
        if (property === 'fullName') {
            return target.firstName + ' ' + target.lastName;
        }

        return target[property];
    }
};

const proxyPerson = new Proxy(person, handler);

現在讓我們嘗試訪問代理人的全名:

console.log(proxyPerson.fullName);
John Doe

僅使用代理,我們在 person 上創建了一個“getter”方法 對象,而不必實際更改原始對象本身。

現在,讓我們看另一個比我們迄今為止遇到的更具動態性的示例。這次我們將返回一個基於給定函數名動態創建的函數,而不是只返回一個屬性。

考慮一組人,其中每個對像都有一個 id 該人的姓名、該人的姓名及該人的年齡。我們需要通過 id 查詢一個人 , name , 或 age .所以我們可以簡單地創建幾個方法,getById , getByName , 和 getByAge .但這一次我們要更進一步。

我們想創建一個處理程序,它可以為可能具有任何屬性的數組執行此操作。例如,如果我們有一個書籍數組,並且每本書都有一個屬性 isbn ,我們也應該能夠使用 getByIsbn 查詢這個數組 並且方法應該在運行時動態生成。

但現在讓我們創建一個人數組。

const people = [
    {
        id: 1,
        name: 'John Doe',
        age: 21
    },
    {
        id: 2,
        name: 'Ann Clair',
        age: 24
    },
    {
        id: 3,
        name: 'Sherlock Holmes',
        age: 35
    }
];

現在讓我們創建一個 get trap 根據函數名生成動態函數。

const proxyPeople = new Proxy(people, {
    get(target, property) {
        if (property.startsWith('getBy')) {
            let prop = property.replace('getBy', '')
                               .toLowerCase();

            return function(value) {
                for (let i of target) {
                    if (i[prop] === value) {
                        return i;
                    }
                }
            }
        }

        return target[property];
    }
});

在此代碼中,我們首先檢查屬性名稱是否以“getBy”開頭,然後我們從屬性名稱中刪除“getBy”,因此我們最終得到要用於查詢項目的實際屬性名稱。因此,例如,如果屬性名稱是 getById ,我們最終得到 id 作為要查詢的屬性。

現在我們有了要查詢的屬性名稱,因此我們可以返回一個接受值的函數並遍歷數組以在給定屬性上找到具有該值的對象。

您可以通過運行以下命令來嘗試:

console.log(proxyPeople.getById(1));
console.log(proxyPeople.getByName('Ann Clair'));
console.log(proxyPeople.getByAge(35));

每個呼叫的相關人員對象應顯示在控制台上:

{ id: 1, name: 'John Doe', age: 21 }
{ id: 2, name: 'Ann Clair', age: 24 }
{ id: 3, name: 'Sherlock Holmes', age: 35 }

在第一行中,我們使用了 proxyPeople.getById(1) ,然後返回用戶 id 1. 在第二行我們使用了 proxyPeople.getByName('Ann Clair') ,返回名為“Ann Clair”的人,以此類推。

作為讀者練習,嘗試使用屬性 isbn 創建您自己的書籍數組 , title , 和 author .然後,使用與上麵類似的代碼,看看如何使用 getByIsbn , getByTitle , 和 getByAuthor 從列表中檢索項目。

為簡單起見,在此實現中,我們假設每個屬性只有一個具有特定值的對象。但在某些情況下可能並非如此,然後您可以編輯該方法以返回與給定查詢匹配的對像數組。

結論

本文的源代碼照常在 GitHub 上提供。如果您在教程中遇到問題,請使用它來比較您的代碼。


Tutorial JavaScript 教程
  1. CSS Glitchy Text 在 3 分鐘內揭曉😎

  2. 使用 Node.js 動態生成 SQL 查詢

  3. 使用 Rough Notation 在您的 HTML 文檔中創建自然註釋。 📝

  4. 使用依賴注入使您的代碼可測試

  5. 用 Javascript 創建一個權重轉換器

  6. 為什麼基本面很重要?

  7. 反應路由器私有路由

  1. Colocated Fragments:如何在 React 中組織查詢

  2. GraphQL 錯誤字段類型必須是輸入類型,但得到:

  3. 使用 LitElement 構建 Story Web 組件

  4. 讓我們探索 JavaScript 中的 Slice()、Splice() 和傳播語法(...)

  5. 控制台.timeLog

  6. 我可以用 Vue 和 Firebase 構建什麼?

  7. Jest Mock + Reactjs + SVG

  1. 適用於 Visual Studio 代碼的 P42

  2. 編碼挑戰

  3. Next.js 12 中的新功能

  4. 使用 iframe 獲取鼠標在頁面上的點擊位置