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...

相关文章
相关标签/搜索