利用任何方法链的功能

时间:2014-02-01 12:17:11

标签: javascript jquery

我喜欢Ruby的.tap方法的工作原理。它可以让您轻松进入任何方法链而不会破坏链条。我让你操作一个对象然后返回对象,以便方法链可以正常继续。例如,如果您有foo = "foobar".upcase.reverse,则可以执行以下操作:

"foo = foobar".upcase.tap{|s| print s}.reverse

它将打印上行(但不反转)字符串,并像原始行一样继续进行反转和赋值。

我想在JS中有一个类似的功能,可以用于一个目的:将对象记录到控制台。

我试过了:

Object.prototype.foo = function() {
  console.log(this);
  return this;
};

通常,它有效(尽管它为数字而不是数字值输出Number对象)。

但是当我使用一些jQuery时,它会破坏jQuery并停止页面上所有进一步的代码执行。

错误是这样的:

  • 未捕获的TypeError:对象foo没有方法'push'
  • Uncaught TypeError:Object function(){window.runnerWindow.proxyConsole.log(“foo”);没有方法'exec'

这是一个测试用例:http://jsbin.com/oFUvehAR/2/edit(取消注释第一行以查看它是否中断)。

所以我想混淆对象的原型是不安全的。

然后,做我想要的正确方法是什么?将当前对象记录到控制台并返回对象以便链可以继续的函数。对于基元,它应该记录它们的值而不仅仅是对象。

1 个答案:

答案 0 :(得分:7)

您正确地弄清楚如何在链中的任何位置安全地添加方法,但是将其添加到Object.prototype是侵入性的并且可以轻松地破坏代码。看起来jQuery代码就是为你打破的。

更安全的方式是:

Object.defineProperty(Object.prototype, 'foo', {
  value : function() {  console.log( "foo" );  return this; },
  enumerable : false
});

DEMO:http://jsbin.com/oFUvehAR/7/edit

最后,泛型可能看起来像这样:

Object.defineProperty(Object.prototype, 'tap', {
  value : function(intercept) {  
    intercept.call(this);  
    return this; 
  },
  enumerable : false
});

// Usage:
var x  = { a:1 };
x.tap(function(){ console.log(this); });

至于问题的原语部分,这有点棘手。在基元上调用tap方法时,会创建一个Object包装器并在其上调用tap方法。通过该Object包装器的valueOf()方法,原始值仍然可用,因此您可以记录它。棘手的部分是你无法知道你想要调用tap方法的“东西”最初是原始的还是Object包装器。假设您从不想使用Object包装器(这是非常合理的),您可以使用下面发布的更好的tap方法。

Object.defineProperty(Object.prototype, 'tap', {
  value : function(intercept) {  
    var val = (this instanceof Number || this instanceof String || this instanceof Boolean) ? this.valueOf() : this;
    intercept(val);  
    return val; 
  },
  enumerable : false
});

var log = console.log.bind(console);

var x  = { a : 1 };

x.tap(log);
2.0.tap(log);

请注意,在tap函数的第一个版本中,传递给它的函数在this中有有用的信息,在第二个版本中,必须将它作为参数传递。

如果你想要一个专门的记录器,你可以这样做:

Object.defineProperty(Object.prototype, 'log', {
  value : function(){
    return this.tap(console.log.bind(console));
  },
  enumerable : false,
  writable : true /* You want to allow objects to overwrite the log method */
});