成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[譯] 實例解析 ES6 Proxy 使用場景

Rainie / 1565人閱讀

摘要:在下文中,首先我會介紹的使用方式,然后列舉具體實例解釋的使用場景。如果簡單地區(qū)分和的使用場景,可以概括為的核心作用是控制外界對被代理者內部的訪問,的核心作用是增強被裝飾者的功能。

文章永久鏈接地址:http://pinggod.com/2016/%E5%AE%9E%E4%BE%8B%E8%A7%A3%E6%9E%90-ES6-Proxy-%E4%BD%BF%E7%94%A8%E5%9C%BA%E6%99%AF/

ES6 中的箭頭函數(shù)、數(shù)組解構、rest 參數(shù)等特性一經(jīng)實現(xiàn)就廣為流傳,但類似 Proxy 這樣的特性卻很少見到有開發(fā)者在使用,一方面在于瀏覽器的兼容性,另一方面也在于要想發(fā)揮這些特性的優(yōu)勢需要開發(fā)者深入地理解其使用場景。就我個人而言是非常喜歡 ES6 的 Proxy,因為它讓我們以簡潔易懂的方式控制了外部對對象的訪問。在下文中,首先我會介紹 Proxy 的使用方式,然后列舉具體實例解釋 Proxy 的使用場景。

Proxy,見名知意,其功能非常類似于設計模式中的代理模式,該模式常用于三個方面:

攔截和監(jiān)視外部對對象的訪問

降低函數(shù)或類的復雜度

在復雜操作前對操作進行校驗或對所需資源進行管理

在支持 Proxy 的瀏覽器環(huán)境中,Proxy 是一個全局對象,可以直接使用。Proxy(target, handler) 是一個構造函數(shù),target 是被代理的對象,handlder 是聲明了各類代理操作的對象,最終返回一個代理對象。外界每次通過代理對象訪問 target 對象的屬性時,就會經(jīng)過 handler 對象,從這個流程來看,代理對象很類似 middleware(中間件)。那么 Proxy 可以攔截什么操作呢?最常見的就是 get(讀?。et(修改)對象屬性等操作,完整的可攔截操作列表請點擊這里。此外,Proxy 對象還提供了一個 revoke 方法,可以隨時注銷所有的代理操作。在我們正式介紹 Proxy 之前,建議你對 Reflect 有一定的了解,它也是一個 ES6 新增的全局對象,詳細信息請參考 MDN Reflect。

Basic
const target = {  
    name: "Billy Bob",
    age: 15
};

const handler = {  
    get(target, key, proxy) {
        const today = new Date();
        console.log(`GET request made for ${key} at ${today}`);

        return Reflect.get(target, key, proxy);
    }
};

const proxy = new Proxy(target, handler);
proxy.name;
// => "GET request made for name at Thu Jul 21 2016 15:26:20 GMT+0800 (CST)"
// => "Billy Bob"

在上面的代碼中,我們首先定義了一個被代理的目標對象 target,然后聲明了包含所有代理操作的 handler 對象,接下來使用 Proxy(target, handler) 創(chuàng)建代理對象 proxy,此后所有使用 proxytarget 屬性的訪問都會經(jīng)過 handler 的處理。

1. 抽離校驗模塊

讓我們從一個簡單的類型校驗開始做起,這個示例演示了如何使用 Proxy 保障數(shù)據(jù)類型的準確性:

let numericDataStore = {  
    count: 0,
    amount: 1234,
    total: 14
};

numericDataStore = new Proxy(numericDataStore, {  
    set(target, key, value, proxy) {
        if (typeof value !== "number") {
            throw Error("Properties in numericDataStore can only be numbers");
        }
        return Reflect.set(target, key, value, proxy);
    }
});

// 拋出錯誤,因為 "foo" 不是數(shù)值
numericDataStore.count = "foo";

// 賦值成功
numericDataStore.count = 333;

如果要直接為對象的所有屬性開發(fā)一個校驗器可能很快就會讓代碼結構變得臃腫,使用 Proxy 則可以將校驗器從核心邏輯分離出來自成一體:

function createValidator(target, validator) {  
    return new Proxy(target, {
        _validator: validator,
        set(target, key, value, proxy) {
            if (target.hasOwnProperty(key)) {
                let validator = this._validator[key];
                if (!!validator(value)) {
                    return Reflect.set(target, key, value, proxy);
                } else {
                    throw Error(`Cannot set ${key} to ${value}. Invalid.`);
                }
            } else {
                throw Error(`${key} is not a valid property`)
            }
        }
    });
}

const personValidators = {  
    name(val) {
        return typeof val === "string";
    },
    age(val) {
        return typeof age === "number" && age > 18;
    }
}
class Person {  
    constructor(name, age) {
        this.name = name;
        this.age = age;
        return createValidator(this, personValidators);
    }
}

const bill = new Person("Bill", 25);

// 以下操作都會報錯
bill.name = 0;  
bill.age = "Bill";  
bill.age = 15;  

通過校驗器和主邏輯的分離,你可以無限擴展 personValidators 校驗器的內容,而不會對相關的類或函數(shù)造成直接破壞。更復雜一點,我們還可以使用 Proxy 模擬類型檢查,檢查函數(shù)是否接收了類型和數(shù)量都正確的參數(shù):

let obj = {  
    pickyMethodOne: function(obj, str, num) { /* ... */ },
    pickyMethodTwo: function(num, obj) { /*... */ }
};

const argTypes = {  
    pickyMethodOne: ["object", "string", "number"],
    pickyMethodTwo: ["number", "object"]
};

obj = new Proxy(obj, {  
    get: function(target, key, proxy) {
        var value = target[key];
        return function(...args) {
            var checkArgs = argChecker(key, args, argTypes[key]);
            return Reflect.apply(value, target, args);
        };
    }
});

function argChecker(name, args, checkers) {  
    for (var idx = 0; idx < args.length; idx++) {
        var arg = args[idx];
        var type = checkers[idx];
        if (!arg || typeof arg !== type) {
            console.warn(`You are incorrectly implementing the signature of ${name}. Check param ${idx + 1}`);
        }
    }
}

obj.pickyMethodOne();  
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 1
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 2
// > You are incorrectly implementing the signature of pickyMethodOne. Check param 3

obj.pickyMethodTwo("wopdopadoo", {});  
// > You are incorrectly implementing the signature of pickyMethodTwo. Check param 1

// No warnings logged
obj.pickyMethodOne({}, "a little string", 123);  
obj.pickyMethodOne(123, {});
2. 私有屬性

在 JavaScript 或其他語言中,大家會約定俗成地在變量名之前添加下劃線 _ 來表明這是一個私有屬性(并不是真正的私有),但我們無法保證真的沒人會去訪問或修改它。在下面的代碼中,我們聲明了一個私有的 apiKey,便于 api 這個對象內部的方法調用,但不希望從外部也能夠訪問 api._apiKey:

var api = {  
    _apiKey: "123abc456def",
    /* mock methods that use this._apiKey */
    getUsers: function(){}, 
    getUser: function(userId){}, 
    setUser: function(userId, config){}
};

// logs "123abc456def";
console.log("An apiKey we want to keep private", api._apiKey);

// get and mutate _apiKeys as desired
var apiKey = api._apiKey;  
api._apiKey = "987654321";

很顯然,約定俗成是沒有束縛力的。使用 ES6 Proxy 我們就可以實現(xiàn)真實的私有變量了,下面針對不同的讀取方式演示兩個不同的私有化方法。第一種方法是使用 set / get 攔截讀寫請求并返回 undefined:

let api = {  
    _apiKey: "123abc456def",
    getUsers: function(){ }, 
    getUser: function(userId){ }, 
    setUser: function(userId, config){ }
};

const RESTRICTED = ["_apiKey"];
api = new Proxy(api, {  
    get(target, key, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} is restricted. Please see api documentation for further info.`);
        }
        return Reflect.get(target, key, value, proxy);
    }
});

// 以下操作都會拋出錯誤
console.log(api._apiKey);
api._apiKey = "987654321";  

第二種方法是使用 has 攔截 in 操作:

var api = {  
    _apiKey: "123abc456def",
    getUsers: function(){ }, 
    getUser: function(userId){ }, 
    setUser: function(userId, config){ }
};

const RESTRICTED = ["_apiKey"];
api = new Proxy(api, {  
    has(target, key) {
        return (RESTRICTED.indexOf(key) > -1) ?
            false :
            Reflect.has(target, key);
    }
});

// these log false, and `for in` iterators will ignore _apiKey
console.log("_apiKey" in api);

for (var key in api) {  
    if (api.hasOwnProperty(key) && key === "_apiKey") {
        console.log("This will never be logged because the proxy obscures _apiKey...")
    }
}
3. 訪問日志

對于那些調用頻繁、運行緩慢或占用執(zhí)行環(huán)境資源較多的屬性或接口,開發(fā)者會希望記錄它們的使用情況或性能表現(xiàn),這個時候就可以使用 Proxy 充當中間件的角色,輕而易舉實現(xiàn)日志功能:

let api = {  
    _apiKey: "123abc456def",
    getUsers: function() { /* ... */ },
    getUser: function(userId) { /* ... */ },
    setUser: function(userId, config) { /* ... */ }
};

function logMethodAsync(timestamp, method) {  
    setTimeout(function() {
        console.log(`${timestamp} - Logging ${method} request asynchronously.`);
    }, 0)
}

api = new Proxy(api, {  
    get: function(target, key, proxy) {
        var value = target[key];
        return function(...arguments) {
            logMethodAsync(new Date(), key);
            return Reflect.apply(value, target, arguments);
        };
    }
});

api.getUsers();
4. 預警和攔截

假設你不想讓其他開發(fā)者刪除 noDelete 屬性,還想讓調用 oldMethod 的開發(fā)者了解到這個方法已經(jīng)被廢棄了,或者告訴開發(fā)者不要修改 doNotChange 屬性,那么就可以使用 Proxy 來實現(xiàn):

let dataStore = {  
    noDelete: 1235,
    oldMethod: function() {/*...*/ },
    doNotChange: "tried and true"
};

const NODELETE = ["noDelete"];  
const NOCHANGE = ["doNotChange"];
const DEPRECATED = ["oldMethod"];  

dataStore = new Proxy(dataStore, {  
    set(target, key, value, proxy) {
        if (NOCHANGE.includes(key)) {
            throw Error(`Error! ${key} is immutable.`);
        }
        return Reflect.set(target, key, value, proxy);
    },
    deleteProperty(target, key) {
        if (NODELETE.includes(key)) {
            throw Error(`Error! ${key} cannot be deleted.`);
        }
        return Reflect.deleteProperty(target, key);

    },
    get(target, key, proxy) {
        if (DEPRECATED.includes(key)) {
            console.warn(`Warning! ${key} is deprecated.`);
        }
        var val = target[key];

        return typeof val === "function" ?
            function(...args) {
                Reflect.apply(target[key], target, args);
            } :
            val;
    }
});

// these will throw errors or log warnings, respectively
dataStore.doNotChange = "foo";  
delete dataStore.noDelete;  
dataStore.oldMethod();
5. 過濾操作

某些操作會非常占用資源,比如傳輸大文件,這個時候如果文件已經(jīng)在分塊發(fā)送了,就不需要在對新的請求作出相應(非絕對),這個時候就可以使用 Proxy 對當請求進行特征檢測,并根據(jù)特征過濾出哪些是不需要響應的,哪些是需要響應的。下面的代碼簡單演示了過濾特征的方式,并不是完整代碼,相信大家會理解其中的妙處:

let obj = {  
    getGiantFile: function(fileId) {/*...*/ }
};

obj = new Proxy(obj, {  
    get(target, key, proxy) {
        return function(...args) {
            const id = args[0];
            let isEnroute = checkEnroute(id);
            let isDownloading = checkStatus(id);      
            let cached = getCached(id);

            if (isEnroute || isDownloading) {
                return false;
            }
            if (cached) {
                return cached;
            }
            return Reflect.apply(target[key], target, args);
        }
    }
});
6. 中斷代理

Proxy 支持隨時取消對 target 的代理,這一操作常用于完全封閉對數(shù)據(jù)或接口的訪問。在下面的示例中,我們使用了 Proxy.revocable 方法創(chuàng)建了可撤銷代理的代理對象:

let sensitiveData = { username: "devbryce" };
const {sensitiveData, revokeAccess} = Proxy.revocable(sensitiveData, handler);
function handleSuspectedHack(){  
    revokeAccess();
}

// logs "devbryce"
console.log(sensitiveData.username);
handleSuspectedHack();
// TypeError: Revoked
console.log(sensitiveData.username);
Decorator

ES7 中實現(xiàn)的 Decorator,相當于設計模式中的裝飾器模式。如果簡單地區(qū)分 Proxy 和 Decorator 的使用場景,可以概括為:Proxy 的核心作用是控制外界對被代理者內部的訪問,Decorator 的核心作用是增強被裝飾者的功能。只要在它們核心的使用場景上做好區(qū)別,那么像是訪問日志這樣的功能,雖然本文使用了 Proxy 實現(xiàn),但也可以使用 Decorator 實現(xiàn),開發(fā)者可以根據(jù)項目的需求、團隊的規(guī)范、自己的偏好自由選擇。

文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/86409.html

相關文章

  • 正則表達式

    摘要:最全正則表達式總結驗證號手機號中文郵編身份證地址等是正則表達式的縮寫,作用是對字符串執(zhí)行模式匹配。學習目標了解正則表達式語法在中使用正則表達式在中使 JS高級技巧 本篇是看的《JS高級程序設計》第23章《高級技巧》做的讀書分享。本篇按照書里的思路根據(jù)自己的理解和經(jīng)驗,進行擴展延伸,同時指出書里的一些問題。將會討論安全的類型檢測、惰性載入函數(shù)、凍結對象、定時器等話題。1. 安全的類型檢測...

    yibinnn 評論0 收藏0
  • 正在暑假中的《課多周刊》(第1期)

    摘要:正在暑假中的課多周刊第期我們的微信公眾號,更多精彩內容皆在微信公眾號,歡迎關注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。原理微信熱更新方案漲知識了,熱更新是以后的標配。 正在暑假中的《課多周刊》(第1期) 我們的微信公眾號:fed-talk,更多精彩內容皆在微信公眾號,歡迎關注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的動力。 遠上寒山石徑...

    liukai90 評論0 收藏0
  • 正在暑假中的《課多周刊》(第1期)

    摘要:正在暑假中的課多周刊第期我們的微信公眾號,更多精彩內容皆在微信公眾號,歡迎關注。若有幫助,請把課多周刊推薦給你的朋友,你的支持是我們最大的動力。原理微信熱更新方案漲知識了,熱更新是以后的標配。 正在暑假中的《課多周刊》(第1期) 我們的微信公眾號:fed-talk,更多精彩內容皆在微信公眾號,歡迎關注。 若有幫助,請把 課多周刊 推薦給你的朋友,你的支持是我們最大的動力。 遠上寒山石徑...

    yintaolaowanzi 評論0 收藏0
  • 前端知識點(二)

    摘要:在給一個目標對象為構造函數(shù)的代理對象構造實例時觸發(fā)該操作,比如在執(zhí)行時。 1、元素上下垂直居中的方式有哪些? 元素水平垂直居中的方式有哪些? absolute加margin方案 fixed 加 margin 方案 display:table 方案 行內元素line-height方案 flex 彈性布局方案 transform 未知元素寬高解決方案 absolute加mar...

    zacklee 評論0 收藏0
  • 前端知識點(二)

    摘要:在給一個目標對象為構造函數(shù)的代理對象構造實例時觸發(fā)該操作,比如在執(zhí)行時。 1、元素上下垂直居中的方式有哪些? 元素水平垂直居中的方式有哪些? absolute加margin方案 fixed 加 margin 方案 display:table 方案 行內元素line-height方案 flex 彈性布局方案 transform 未知元素寬高解決方案 absolute加mar...

    lbool 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<