Proxy、Reflect真的頗有用

Proxy

Proxy是什麼

這裏我想簡單來講,Proxy是對象的代理器,很好理解,咱們有一個很重要的對象,咱們不但願別人隨便獲取修改該對象,咱們要保護該對象,用另一個對象代理它,對真正要操做的對象是一種數據保護和過濾。javascript

Proxy並非簡單的經過咱們常見的代碼形式如if else對對象進行保護,它是在代碼編程層面對對象進行保護,屬於「元編程」。好比,在讀取或設置對象的屬性時,咱們能夠利用get() set()進行代理,執行函數時(函數原本就是對象)可使用apply(),函數看成構造器時,可使用constructor進行攔截。java

Proxy的攔截操做有哪些

雖然是代理器,可是也不能任由程序員「自由發揮」,因此ES6中對Proxy作了一些限制,Proxy支持的攔截操做有下面這些:程序員

  • get(target, propKey, receiver): 攔截對象屬性的讀取
  • set(target, propKey, value, receiver): 攔截對象屬性的設置
  • 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)。
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的應用

Proxy提供的攔截器不少種,能夠根據具體的需求,組合這些攔截器達到本身的目的es6

  • 防止對象的內部屬性(私有屬性)被外部讀寫,達到類型安全

JS對象中的私有屬性沒有規定,咱們都是約定使用前綴是'_'表明私有屬性,但仍是並不「私有」。可使用Proxy作到私有屬性,在get/set屬性時,判斷首字符是不是'_',若是是,則剖出錯誤。數據庫

const handle = {
    get(target, p, receiver) {
        inver(p, 'get')
        return target[p]
    },
    set(target, p, value, receiver) {
        inver(p, 'set')
        target[p] = value
        return true;
    }
}

function inver(key, action) {
    if (key[0] === '_') {
        throw new Error(`Invalid attempt to ${action} ${key}`)
    }
}

try {
    const target = {}
    const proxy = new Proxy(target, handle)
    proxy._prop
    proxy._prop = 'c'
} catch (e) {
    console.log(e) // Error: Invalid attempt to get private "_prop" property
}
  • 使用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.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實例,就會拋出一個錯誤。segmentfault

Proxy.revocable的一個使用場景是,目標對象不容許直接訪問,必須經過代理訪問,一旦訪問結束,就收回代理權,不容許再次訪問。api

  • 抽離校驗模塊,類型檢查

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" 不是數值
numericDataStore.count = "foo";

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

能夠查看個人另外一篇翻譯文章:https://segmentfault.com/a/11...
Proxy能夠作到動態的類型檢查數組

  • 訪問日誌

對於那些調用頻繁、運行緩慢或佔用執行環境資源較多的屬性或接口,開發者會但願記錄它們的使用狀況或性能表現,這個時候就可使用 Proxy 充當中間件的角色,垂手可得實現日誌功能:瀏覽器

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();
  • 預警和攔截

假設你不想讓其餘開發者刪除 noDelete 屬性,還想讓調用 oldMethod 的開發者瞭解到這個方法已經被廢棄了,或者告訴開發者不要修改 doNotChange 屬性,那麼就可使用 Proxy 來實現:

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();
  • 過略操做

某些操做會很是佔用資源,好比傳輸大文件,這個時候若是文件已經在分塊發送了,就不須要在對新的請求做出相應(非絕對),這個時候就可使用 Proxy 對當請求進行特徵檢測,並根據特徵過濾出哪些是不須要響應的,哪些是須要響應的。下面的代碼簡單演示了過濾特徵的方式,並非完整代碼,相信你們會理解其中的妙處:

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);
        }
    }
});

以上咱們能夠看出,Proxy對於對象的代理做用很大,能夠只對外展現咱們容許展現的內容,好比某些屬性、某些方法了。
Proxys屬於元編程了,在框架編寫中會用,當框架複雜度很高,封裝的對象中確定有一些內容是做爲私有的,不能對外暴露,因此使用Proxy能夠保證封裝的對象的安全性和獨立性。

即使不在框架中,在咱們平時的開發任務中也能夠用。好比封裝數據庫ORM,代理網絡請求等等。

思考:Proxy和TypeScript的關聯和區別

像是set()攔截,咱們能夠攔截值的類型是否符合咱們的要求,好比必須是數值,纔會set()操做成功。ProxyTypeScript一樣均可以作到。
那他們的區別是什麼呢?
Proxy是相似「元編程」,而TypeScript是JavaScript類型的超集,它能夠編譯成JS。他們解決問題的層面不同,
TypeScript是靜態類型檢查,在代碼編譯階段就能夠檢測出來,IDE能夠爲咱們報錯;而Proxy能夠提供動態類型檢查,在運行時也能作到類型檢查。

Reflect

Reflect 是一個內置的對象,它提供攔截 JavaScript 操做的方法。這些方法與proxy handlers的方法相同。Reflect不是一個函數對象,所以它是不可構造的。

描述

與大多數全局對象不一樣,Reflect不是一個構造函數。你不能將其與一個new運算符一塊兒使用,或者將Reflect對象做爲一個函數來調用。Reflect的全部屬性和方法都是靜態的(就像Math對象)。

方法

Reflect是內置對象,在瀏覽器控制檯中輸入Reflect查看:
Reflect

Reflect對象提供如下靜態函數,它們具備與處理器對象(也就是Proxy handle)方法相同的名稱。這些方法中的一些與 Object 上的對應方法相同。

  • Reflect.apply()

對一個函數進行調用操做,同時能夠傳入一個數組做爲調用參數。和 Function.prototype.apply() 功能相似。

  • Reflect.construct()

對構造函數進行 new 操做,至關於執行 new target(...args)。

  • Reflect.defineProperty()

和 Object.defineProperty() 相似。

  • Reflect.deleteProperty()

做爲函數的delete操做符,至關於執行 delete target[name]。

  • Reflect.enumerate()

該方法會返回一個包含有目標對象身上全部可枚舉的自身字符串屬性以及繼承字符串屬性的迭代器,for...in 操做遍歷到的正是這些屬性。

  • Reflect.get()

獲取對象身上某個屬性的值,相似於 target[name]。

  • Reflect.getOwnPropertyDescriptor()

相似於 Object.getOwnPropertyDescriptor()。
Reflect.getPrototypeOf()
相似於 Object.getPrototypeOf()。
Reflect.has()
判斷一個對象是否存在某個屬性,和 in 運算符 的功能徹底相同。
Reflect.isExtensible()
相似於 Object.isExtensible().
Reflect.ownKeys()
返回一個包含全部自身屬性(不包含繼承屬性)的數組。(相似於 Object.keys(), 但不會受enumerable影響).
Reflect.preventExtensions()
相似於 Object.preventExtensions()。返回一個Boolean。
Reflect.set()
將值分配給屬性的函數。返回一個Boolean,若是更新成功,則返回true。
Reflect.setPrototypeOf()
相似於 Object.setPrototypeOf()。

參考連接:

https://developer.mozilla.org...

https://medium.com/@SylvainPV...

相關文章
相關標籤/搜索