Typescript 与 Javascript Proxy 和 Reflect 搭配

Typescript 与 Javascript Proxy 和 Reflect 搭配

2019-10-15

Javascript 的 Proxy 物件 & Reflect API

下面表格是 Proxy 物件在各种浏览器版本中的支援程度, 资料参考来源从 caniuse 网站取得

BrowserSupport VersionIE6 - 10, 11Edge12-18, 76Firefox18-68, 69Chrome49-76, 77Safari10-12.1, 13Opera36-60, 62iOS Safari10-12.3, 13.1Opera MiniAll Not supportAndroid Browser76Opera Mobile46Chrome for Android76Firefox for Android68UC Browser for Android12.12Samsung Internet5-9.2, 10.1QQ Browser1.2Baidu Browser7.12KaiOS Browser2.5

以上版本号码有删除的样式, 表示不支援

Proxy 和Reflect 是ES6 新增的API, Proxy 是一个函式物件, 它提供一个机会让你能介入一般物件的基本操作行为, 很像 interceptor 会做的事情一样.

使用的方法如下

const proxyObj = new Proxy(target, handler);
target 就是你想要代理的对象handler 则是一个物件, 定义了所有你想替 target 代为管理的操作定义

以下是一个典型的 Proxy 範例, 示範了 cat 物件被代理成 catProxy 物件, 然后不能存取 cat 私有变数的方法.

let cat = {  _secret: 'I am Mr.Brain',  name: 'Flash'};let catProxy = new Proxy(cat, {  get: function (target, prop) {     if( prop.startsWith('_') ) {        console.log('不能存取私有变数');        return false;     }     return target[prop];  },  set: function (target, prop, value) {    if (prop.startsWith('_')) {      console.log('不能修改私有变数');      return false;    }    target[prop] = value;  },  has: function (target, prop) {    return prop.startsWith('_') ? false : (prop in target);  }});

不是什么都可以被代理的

如果你的物件拥有 configurable: false 与 writable: false 的属性,那该物件就无法被 proxy 代理, 如下示範

const target = Object.defineProperties({}, {  Cat: {    writable: false,    configurable: false  },});const handler = {  get(target, propKey) {    return '???';  }};const proxy = new Proxy(target, handler);proxy.FooBar

一旦你按照上面的程式码执行, 就会喷出这个例外错误

Uncaught TypeError: 'get' on proxy: property 'FooBar' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'undefined' but got '???')

Reflect 与 Proxy 的搭配

我们在 Proxy 中, 如果需要 target 物件的预设操作, 使用 Reflect 会更清楚, 如下示範

const loggedObj = new Proxy(obj, {  get: function(target, name) {    console.log("get", target, name);    return Reflect.get(target, name);  }});

以上是property 的proxy, 下面是method proxy 示範

let cat = {name: "Kitty",method1: function(msg){console.log("cat: " + this.name + " say " + msg);},};var catProxy = new Proxy(cat, {get: function(target, propKey, receiver){//我只要拦截方法(method calls), 不要属性(property access)var propValue = target[propKey];if (typeof propValue != "function"){return propValue;}else{return function() {console.log("intercepting call to " + propKey + " in cat " + target.name);//"this" 指向 proxy, 就像"receiver"return propValue.apply(this, arguments);}}}});

以上Proxy 的概念性, 在没有ES6 语法之前, 使用Typescript 撰写 "装饰者模式" 就可以做到, 如下所示

interface ICat {    sayHello(msg: string): void;}class RealCat implements ICar {   public sayHello(msg: string): void {      console.log('RealCat: ' + msg);   }}class CatProxy implements ICat {   private _realCat: RealCat;   constructor(realCat: RealCat) {      this._realCat = realCat;   }   public sayHello(msg: string): void {      console.log("Hey! I am Kitty");      this._realCat.sayHello(msg);   }}let catProxy = new CatProxy(new RealCat());catProxy.sayHello("Hello Proxy World");

Typescript 与 Proxy & Reflect 搭配

使用上述的装饰者模式时候, 假如 ICat 介面中的方法和属性加起来有100 个, 你就得在 CatProxy 一一实作包装并呼叫 _realCat.

但你又是个懒人不想一一手动去实作, 你可以用 Proxy 和 Reflect 来帮忙实作每一个 ICat 的公开方法和属性, 如下示範Typescript 程式码是如何做到这件事情

function fakeBaseClass<T>() : new() => Pick<T, keyof T> {    return class {} as any;}class CatProxy extends fakeBaseClass<RealCat>() {    private _realCat: RealCat;   constructor(cat: RealCat) {      super();      this._realCat = cat;      let handler = {         get: function(target: CatProxy, prop: keyof RealCat, receiver: any) {            if(RealCat.prototype[prop] !== null) {               return target._realCat[prop];            }            return Reflect.get(target, prop, receiver);         }      };      return new Proxy(this, handler);   }}

如此一来你可以开始只複写(override) ICat 介面其中一部分的方法或属性.


关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章