关闭

如何使用Proxy 来代理Js中的类?

时间: 2019-09-17阅读: 1036标签: 代理

Proxy 对象(Proxy)是 ES6 的一个非常酷却鲜为人知的特性。虽然这个特性存在已久,但是我还是想在本文中对其稍作解释,并用一个例子说明一下它的用法。


什么是 Proxy

正如 MDN 上简单而枯燥的定义:

Proxy 对象用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。

虽然这是一个不错的总结,但是我却并没有从中搞清楚 Proxy 能做什么,以及它能帮我们实现什么。

首先,Proxy 的概念来源于元编程。简单的说,元编程是允许我们运行我们编写的应用程序(或核心)代码代码。例如,臭名昭著的 eval 函数允许我们将字符串代码当做可执行代码来执行,它是就属于元编程领域。

Proxy API 允许我们在对象和其消费实体中创建中间层,这种特性为我们提供了控制该对象的能力,比如可以决定怎样去进行它的 get 和 set,甚至可以自定义当访问这个对象上不存在的属性的时候我们可以做些什么。


Proxy 的 API

var p = new Proxy(target, handler);

Proxy 构造函数获取一个 target 对象,和一个用来拦截 target 对象不同行为的 handler对象。你可以设置下面这些拦截项:

  • has — 拦截 in 操作。比如,你可以用它来隐藏对象上某些属性。

  • get — 用来拦截读取操作。比如当试图读取不存在的属性时,你可以用它来返回默认值。

  • set — 用来拦截赋值操作。比如给属性赋值的时候你可以增加验证的逻辑,如果验证不通过可以抛出错误。

  • apply — 用来拦截函数调用操作。比如,你可以把所有的函数调用都包裹在 try/catch 语句块中。

这只是一部分拦截项,你可以在 MDN 上找到完整的列表。

下面是将 Proxy 用在验证上的一个简单的例子:

const Car = {
 maker: 'BMW',
 year: 2018,
};

const proxyCar = new Proxy(Car, {
 set(obj, prop, value) {
   if (prop === 'maker' && value.length < 1) {
     throw new Error('Invalid maker');
   }
   if (prop === 'year' && typeof value !== 'number') {
     throw new Error('Invalid year');
   }
   obj[prop] = value;
   return true;
 }
});
proxyCar.maker = ''; // throw exception
proxyCar.year = '1999'; // throw exception

可以看到,我们可以用 Proxy 来验证赋给被代理对象的值。


使用 Proxy 来调试

为了在实践中展示 Proxy 的能力,我创建了一个简单的监测库,用来监测给定的对象或类,监测项如下:

  • 函数执行时间

  • 函数的调用者或属性的访问者

  • 统计每个函数或属性的被访问次数。

这是通过在访问任意对象、类、甚至是函数时,调用一个名为 proxyTrack 的函数来完成的。

如果你希望监测是谁给一个对象的属性赋的值,或者一个函数执行了多久、执行了多少次、谁执行的,这个库将非常有用。我知道可能还有其他更好的工具来实现上面的功能,但是在这里我创建这个库就是为了用一用这个 API。


使用 proxyTrack

首先,我们看看怎么用:

function MyClass() {}
MyClass.prototype = {
   isPrime: function() {
       const num = this.num;
       for(var i = 2; i < num; i++)
           if(num % i === 0) return false;
       return num !== 1 && num !== 0;
   },
   num: null,
};
MyClass.prototype.constructor = MyClass;
const trackedClass = proxyTrack(MyClass);
function start() {
   const my = new trackedClass();
   my.num = 573723653;
   if (!my.isPrime()) {
       return `${my.num} is not prime`;
   }
}
function main() {
   start();
}
main();

如果我们运行这段代码,控制台将会输出:

MyClass.num is being set by start for the 1 time
MyClass.num is being get by isPrime for the 1 time
MyClass.isPrime was called by start for the 1 time and took 0 mils.
MyClass.num is being get by start for the 2 time

proxyTrack 接受 2 个参数:第一个是要监测的对象/类,第二个是一个配置项对象,如果没传递的话将被置为默认值。我们看看这个配置项默认值长啥样:

const defaultOptions = {
   trackFunctions: true,
   trackProps: true,
   trackTime: true,
   trackCaller: true,
   trackCount: true,
   stdout: null,
   filter: null,
};

可以看到,你可以通过配置你关心的监测项来监测你的目标。比如你希望将结果输出出来,那么你可以将 console.log 赋给 stdout。

还可以通过赋给 filter 的回调函数来自定义地控制输出哪些信息。你将会得到一个包括有监测信息的对象,并且如果你希望保留这个信息就返回 true,反之返回 false。


react 中使用 proxyTrack

因为 react 的组件实际上也是类,所以你可以通过 proxyTrack 来实时监控它。比如:

class MyComponent extends Component{...}
export default connect(mapStateToProps)(proxyTrack(MyComponent, {
   trackFunctions: true,
   trackProps: true,
   trackTime: true,
   trackCaller: true,
   trackCount: true,
   filter: (data) => {
       if( data.type === 'get' && data.prop === 'componentDidUpdate') return false;
       return true;
   }
}));

可以看到,你可以将你不关心的信息过滤掉,否则输出将会变得杂乱无章。


实现 proxyTrack

我们来看看 proxyTrack 的实现。

首先是这个函数本身:

export function proxyTrack(entity, options = defaultOptions) {
   if (typeof entity === 'function') return trackClass(entity, options);
   return trackObject(entity, options);
}

没什么特别的嘛,这里只是调用相关函数。

再看看 trackObject:

function trackObject(obj, options = {}) {
   const { trackFunctions, trackProps } = options;
   let resultObj = obj;
   if (trackFunctions) {
       proxyFunctions(resultObj, options);
   }
   if (trackProps) {
       resultObj = new Proxy(resultObj, {
           get: trackPropertyGet(options),
           set: trackPropertySet(options),
       });
   }
   return resultObj;
}
function proxyFunctions(trackedEntity, options) {
   if (typeof trackedEntity === 'function') return;
   Object.getOwnPropertyNames(trackedEntity).forEach((name) => {
       if (typeof trackedEntity[name] === 'function') {
           trackedEntity[name] = new Proxy(trackedEntity[name], {
               apply: trackFunctionCall(options),
           });
       }
   });
}

可以看到,假如我们希望监测对象的属性,我们创建了一个带有 get 和 set 拦截器的被监测对象。下面是 set 拦截器的实现:

function trackPropertySet(options = {}) {
   return function set(target, prop, value, receiver) {
       const { trackCaller, trackCount, stdout, filter } = options;
       const error = trackCaller && new Error();
       const caller = getCaller(error);
       const contextName = target.constructor.name === 'Object' ? '' : `${target.constructor.name}.`;
       const name = `${contextName}${prop}`;
       const hashKey = `set_${name}`;
       if (trackCount) {
           if (!callerMap[hashKey]) {
               callerMap[hashKey] = 1;
           } else {
               callerMap[hashKey]++;
           }
       }
       let output = `${name} is being set`;
       if (trackCaller) {
           output += ` by ${caller.name}`;
       }
       if (trackCount) {
           output += ` for the ${callerMap[hashKey]} time`;
       }
       let canReport = true;
       if (filter) {
           canReport = filter({
               type: 'get',
               prop,
               name,
               caller,
               count: callerMap[hashKey],
               value,
           });
       }
       if (canReport) {
           if (stdout) {
               stdout(output);
           } else {
               console.log(output);
           }
       }
       return Reflect.set(target, prop, value, receiver);
   };
}

更有趣的是 trackClass 函数(至少对我来说是这样):

function trackClass(cls, options = {}) {
   cls.prototype = trackObject(cls.prototype, options);
   cls.prototype.constructor = cls;
   return new Proxy(cls, {
       construct(target, args) {
           const obj = new target(...args);
           return new Proxy(obj, {
               get: trackPropertyGet(options),
               set: trackPropertySet(options),
           });
       },
       apply: trackFunctionCall(options),
   });
}

在这个案例中,因为我们希望拦截这个类上不属于原型上的属性,所以我们给这个类的原型创建了个代理,并且创建了个构造函数拦截器。

别忘了,即使你在原型上定义了一个属性,但如果你再给这个对象赋值一个同名属性,JavaScript 将会创建一个这个属性的本地副本,所以赋值的改动并不会改变这个类其他实例的行为。这就是为何只对原型做代理并不能满足要求的原因。



站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/5992

关闭

通过nginx反向代理来调试代码

现在公司项目都是前后端分离的方式开发,有些时候由于某些新需求开发或者 bug 修改,想要让前端直接连到我本地开发环境进行调试,而前端代码我并没有,只能通过前端部署的测试环境进行测试

centos7下搭建高匿HTTP代理

一般适用情况:1、两台都有外网IP,一台服务器请求资源通过另外一个服务器,本文重点讲第一种。2、两台服务器,其中一台服务器只有内网IP,另外一台服务器有公网和内网IP。

Nginx反向代理之动静分离

我们已经知道了什么是正向代理与反向代理,这次我们就讲一下Nginx的动静分离的案例,其实质运用的就是反向代理,专门用一台服务器代理服务器上的图片资源。

Nginx 反向代理返回结果为空的问题

现在有一个 Web 项目,前端是使用 Vue.js 开发的,整个前端需要部署到 K8S 上,后端和前端分开,同样也需要部署到 K8S 上,因此二者需要打包为 Docker 镜像。对前端来说,打包 Docker 就遇到了一个问题:跨域访问问题。

node.js代理访问

本地开发,代理访问,防止跨域(一般通过webpack配置代理即可),特殊情况如携带一些自定义的登录cookie则需要通过自己写node,作为一种server中间层,单线程异步可以缓解服务器压力

vue proxy代理跨域

changeOrigin的属性值为一个布尔值,如果设置为true,那么本地会虚拟一个NODE服务端接收你的请求并代你发送该请求(中间件)。[本质上是本地开了一个服务器dev-server,所有的请求都通过这里转发出去。]

使用Nginx反向代理google,做谷歌搜索镜像

梯子有点慢。如果用VPS直接转发给谷歌,应该会快一些。实验结果也确实是如此,尽管我用的是同一个服务器。Nginx 需要支持 sub_module ,也就是编译时有 --with-http_sub_module 。

node怎么做反向代理?

在实际工程开发中,会有前后端分离的需求。使用node.js反向代理的目的:实现前后端分离,前端减少路径请求的所需的路由文件。

什么是在线代理ip网页代理

当我们需要大量IP进行快节奏完成业绩的时候,很多人都会想到去IP代理服务商那里购买IP代理,所以我相信很多人对于IP代理这个词已经有一定的认识了,那么还有一个词叫做:在线代理ip网页代理

.Net Core/Framework之Nginx反向代理后获取客户端IP等数据探索

公司项目最近出现获取访问域名、端口、IP错误现象,通过排查发现, 之前项目一直通过Nginx自定义Headers信息来获取,但最近运维人员失误操作造成自定义Header信息丢失,造成项目拿不到对应的数据。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!