65.9K
CodeProject 正在变化。 阅读更多。
Home

JavaScript 中的代理模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (4投票s)

2013年10月16日

CPOL

3分钟阅读

viewsIcon

38589

downloadIcon

243

只需一个函数调用,即可在 Javascript 中使用代理包装一个类。 这是在 node.js 中使用 mocha 轻松进行错误模拟的前身

引言

这是一个非常简单的脚本,允许您为 JavaScript 中的类创建一个代理。假设我们有一个类(将用作构造函数的函数)Math,它定义了一些函数,例如 addsubtractmultiplydivide

function Math() {
  this.IsMath = true;
}
Math.prototype = {
  add: function(a, b) {
    return a + b;
  },
  subtract: function(a, b) {
    return this.add(a, -b);
  },
  multiply: function(a, b) {
    return a * b;
  },
  divide: function(a, b) {
    return a / b;
  }
} 

我们希望能够修改它的行为,以便我们可以使用 beforeFunction 事件和 afterFunction 事件来拦截此类中的每个函数调用。

我们可以决定对这两个函数做任何我们想做的事情。您可以访问此处的技巧,它利用此通用代理脚本允许您在 mocha (node.js) 中非常容易地模拟错误。 对于本文,我将定义一个简单的代理来记录函数调用(这是在 node.js 中使用 console.log 完成的,但可以很容易地针对浏览器代码进行修改)。

Using the Code

您只需要一个名为 createProxyDefinition 的函数。 此函数在 proxy.js 中定义,您可以从本文附加的文件中下载。 该函数采用四个参数,prefixbeforeFunctionafterFunctionmore

beforeFunctionafterFunction 的含义非常明显。 每当调用目标类的函数时,这两个事件将相应地触发。 prefix 是您要创建的代理的唯一名称标识符,这将允许您将多个代理“添加”到同一个类。

beforeFunction 将被赋予三个参数,第一个是被调用的函数 name,第二个是传递给该函数的 arguments 数组,第三个是参数名称的数组。 如果您从事件中返回值,则不会调用原始函数,而将使用您的返回值代替。

afterFunction 将被赋予四个参数,第一个是被调用的函数 name,第二个是原始函数的返回值,第三个是传递给该函数的 arguments 数组,第四个是参数名称的数组。

more 是任何额外的对象,它将扩展传入的类的原型。

一个例子最能解释如何使用这个类。 我想创建一个简单的代理定义,它只打印出被调用的函数名称及其参数,这对于跟踪函数调用很有用。 另一个更有用的例子,也是我首先想写这篇文章的原因,是为了让我能够在编写 node.js 项目时进行完整的代码覆盖,请参考此处的技巧。

创建一个记录文件的代理定义 (node.js 模块)

var beforeFunction = function(name, args, paramsArray) {
  if(!this.__levels) {
    this.__levels = 0;
  }
  for(var i = 0; i < this.__levels; i++) {
    process.stdout.write("\t");
  }
  console.log(" Entering: >> ", name, " : ", args);
  this.__levels++;
};
var afterFunction = function(name, ret, args, paramsArray) {
  this.__levels--;
  for(var i = 0; i < this.__levels; i++) {
    process.stdout.write("\t");
  }
  console.log(" Exiting: >> ", name)
}
var createCallsLogger = createProxyDefinition("callslogger", beforeFunction, afterFunction);

createCallsLogger 现在是一个接受任何构造函数并根据需要修改它的函数。

一个示例用法是

createCallsLogger(Math);
var math = new Math();
math.add(1, 2);
math.subtract(10, 3);
math.multiply(5, 5);
math.divide(16, 4);

现在对 math 进行的任何函数调用都将被记录到控制台,一个示例输出如下

usr/bin/node index.js
 Entering: >> add  :  { '0': 1, '1': 2 }
 Exiting:  >> add
 Entering: >>  subtract  :  { '0': 10, '1': 3 }
	 Entering: >> add  :  { '0': 10, '1': -3 }
	 Exiting:  >> add
 Exiting:  >> subtract
 Entering: >> multiply  :  { '0': 5, '1': 5 }
 Exiting:  >> multiply
 Entering: >> divide  :  { '0': 16, '1': 4 }
 Exiting:  >> divide
Process finished with exit code 0 

幕后

createProxyDefinition 函数接受一个类定义(构造函数),并遍历其所有原型函数,用一个新函数替换每个函数,该函数执行所需的调用和事件的委托。 此函数在附加的文件 proxy.js 中。 粗略地说,这就是它在伪代码中所做的事情。

function __createProxy(constructor, proxyInstanceDefinition, defPrefix) 
{
  if (proxy already added)
    return;
  for each function in constructor.prototype
  {
      var newName = __ + functionname;
      copy original function reference to a new function with name "NewName"
      create a new function to replace the current. 
      The new function will call original function)      
  }
}

关注点

此代理创建方法并非旨在在生产代码中使用,理想情况下,它用于快速模拟或利用其他类,例如使用它来模拟 node.js 中的错误以实现完全覆盖的情况。 不建议在生产代码中使用它的原因是,它基本上为每个要覆盖的函数创建新的函数实例,因此理想情况下,在生产环境中,您应该有自定义编写的代码来做这样的事情,以实现您想要的确切目标,而无需额外的重定向和内存使用开销。

历史

  • 2013 年 11 月 21 日:初始版本
© . All rights reserved.