【重溫基礎】14.元編程

本文是 重溫基礎 系列文章的第十四篇。
這是第一個基礎系列的最後一篇,後面會開始複習一些中級的知識了,歡迎持續關注呀! 接下來會統一整理到個人【Cute-JavaScript】JavaScript基礎系列中。html

今日感覺:獨樂樂不如衆樂樂。前端

系列目錄:git

本章節複習的是JS中的元編程,涉及的更多的是ES6的新特性。es6

1. 概述

元編程,其實我是這麼理解的:讓代碼自動寫代碼,能夠更改源碼底層的功能
元,是指程序自己。
有理解不到位,還請指點,具體詳細的介紹,能夠查看維基百科 元編程
從ES6開始,JavaScrip添加了對ProxyReflect對象的支持,容許咱們鏈接並定義基本語言操做的自定義行爲(如屬性查找,賦值,枚舉和函數調用等),從而實現JavaScrip的元級別編程。github

  • Reflect: 用於替換直接調用Object的方法,並非一個函數對象,也沒有constructor方法,因此不能用new操做符。
  • Proxy: 用於自定義對象的行爲,如修改setget方法,能夠說是ES5中Object.defineProperty()方法的ES6升級版。
  • 二者聯繫: API徹底一致,但Reflect通常在Proxy須要處理默認行爲的時候使用。

參考資料正則表達式

名稱 地址
Reflect MDN Reflect
Proxy MDN Proxy
元編程 MDN 元編程

本文主要從Proxy介紹,還會有幾個案例,實際看下怎麼使用。編程

2. Proxy介紹

proxy 用於修改某些操做的默認行爲,能夠理解爲一種攔截外界對目標對象訪問的一種機制,從而對外界的訪問進行過濾和修改,即代理某些操做,也稱「代理器」。json

2.1 基礎使用

基本語法:數組

let p = new Proxy(target, handler);
複製代碼

proxy實例化須要傳入兩個參數,target參數表示所要攔截的目標對象,handler參數也是一個對象,用來定製攔截行爲。微信

let p = new Proxy({}, {
    get: function (target, handler){
        return 'leo';
    }
})
p.name; // leo
p.age;  // leo
p.abcd; // leo
複製代碼

上述a實例中,在第二個參數中定義了get方法,來攔截外界訪問,而且get方法接收兩個參數,分別是目標對象所要訪問的屬性,因此無論外部訪問對象中任何屬性都會執行get方法返回leo

注意

  • 只能使用Proxy實例的對象才能使用這些操做。
  • 若是handler沒有設置攔截,則直接返回原對象。
let target = {};
let handler = {};
let p = new Proxy(target, handler);
p.a = 'leo'; 
target.a;  // 'leo'
複製代碼

同個攔截器函數,設置多個攔截操做

let p = new Proxy(function(a, b){
    return a + b;
},{
    get:function(){
        return 'get方法';
    },
    apply:function(){
        return 'apply方法';
    }
})
複製代碼

這裏還有一個簡單的案例:

let handler = {
    get : function (target, name){
        return name in target ? target[name] : 16;
    }
}

let p = new Proxy ({}, handler);
p.a = 1;
console.log(p.a , p.b);
// 1 16
複製代碼

這裏由於 p.a = 1 定義了p中的a屬性,值爲1,而沒有定義b屬性,因此p.a會獲得1,而p.b會獲得undefined從而使用name in target ? target[name] : 16;返回的默認值16

Proxy支持的13種攔截操做
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)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而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.2 取消Proxy實例

使用Proxy.revocable方法取消Proxy實例。

let a = {};
let b = {
    get: function(target, name) {
        return "[[" + name + "]]";
    }
};
let revoke = Proxy.revocable(a, b);
let proxy = revoke.proxy;

proxy.age;           // "[[age]]"
revoke.revoke();
proxy.age;           // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked

proxy.age = 10;      // Uncaught TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.age;    // Uncaught TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
typeof proxy;        // "object"
複製代碼

2.3 實現 Web服務的客戶端

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

function createWebService(url){
    return new Proxy({}, {
        get(target, propKey, receiver{
            return () => httpGet(url+'/'+propKey);
        })
    })
}
複製代碼

3. Proxy實踐

3.1 數據攔截驗證

經過Proxy代理對象的setget方法來進行攔截數據,像Vue就是用數據攔截來實現數據綁定。

let handler = {
    // 攔截並處理get方法
    // 能夠理解爲設置get方法返回的默認值
    get : function (target, key){
        return key in target ? target[key] : 30;
    },
    
    // 攔截並處理set方法
    // 能夠理解爲設置set方法的默認行爲
    set : function (target, key, value){
        if(key === "age"){
            if (!Number.isInteger(value)){
                throw new TypeError("age不是一個整數!");
            }
            if (value > 200){
                throw new TypeError("age不能大於200!");
            }
        }
        // 保存默認行爲
        target[key] = value;
    }
}

let p = new Proxy({}, handler);
p.a = 10;         // p.a => 10
p.b = undefined;  // p.b => undefined
p.c;              // 默認值 30
p.age = 100;      // p.age => 100
p.age = 300;      // Uncaught TypeError: age不能大於200!
p.age = "leo";    // Uncaught TypeError: age不是一個整數!
複製代碼

3.2 函數節流

經過攔截handler.apply()方法的調用,實現函數只能在1秒以後才能再次被調用,常常能夠用在防止重複事件的觸發。

let p = (fun, time) => {
    // 獲取最後點擊時間
    let last = Date.now() - time;
    return new Proxy (fun, {
        apply(target, context, args){
            if(Date.now() - last >= time){
                fun.bind(target)(args);
                // 重複設置當前時間
                last = Date.now();
            }
        }
    })
}

let p1 = () => {
    console.log("點擊觸發");
}
let time = 1000; // 設置時間
let proxyObj = p(p1, time);
// 監聽滾動事件
document.addEventListener('scroll', proxyObj);
複製代碼

3.3 實現單例模式

經過攔截construct方法,讓不一樣實例指向相同的constructer,實現單例模式。

let p = function(fun){
    let instance;
    let handler = {
        // 攔截construct方法
        construct: function(targer, args){
            if(!instance){
                instance = new fun();
            }
            return instance;
        }
    }
    return new Proxy(fun, handler);
}

// 建立一個construct案例
function Cons (){
    this.value = 0;
}

// 建立實例
let p1 = new Cons();
let p2 = new Cons();

// 操做
p1.value = 100; 
// p1.value => 100 , p2.value => 0
// 由於不是相同實例

// 經過Proxy實現單例
let singleton = p(Cons);
let p3 = new singleton();
let p4 = new singleton();
p3.value = 130; 
// p1.value => 130 , p2.value => 130
// 如今是相同實例
複製代碼

參考資料

1. MDN 元編程
2. ES6中的元編程-Proxy & Reflect
本部份內容到這結束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 github.com/pingan8787/…
JS小冊 js.pingan8787.com

歡迎關注個人微信公衆號【前端自習課】

相關文章
相關標籤/搜索