【问题标题】:Maintaining the reference to "this" in Javascript when using callbacks and closures使用回调和闭包时在 Javascript 中保持对“this”的引用
【发布时间】:2011-10-24 11:12:05
【问题描述】:

我发现自己将“this”分配给一个变量,这样我就可以轻松地在回调和闭包中使用它。

这是不好的做法吗?有没有更好的方法来引用原始函数?

这是一个典型的例子。

User.prototype.edit = function(req, res) {

  var self = this,
      db = this.app.db;

  db.User.findById('ABCD', function(err, user)) {

    // I cannot use this.foo(user)
    self.foo(user);
  });
};

User.prototype.foo = function(user) {

};

您通常使用这种方法还是找到了更清洁的解决方案?

【问题讨论】:

  • 就是这样。
  • 像 call()、apply() 甚至 ES5 的 bind() 这样的解决方案怎么样??
  • @Couto、callapply 仍然需要对 this 的引用,这会让您回到最初的问题。 bind 确实是一个很好的解决方案。
  • @davin 你确实是对的,当我写评论时我不记得了。我个人使用 bind()

标签: javascript node.js


【解决方案1】:

在回调中处理this主要有三种方式:

1。创建一个词法范围的变量,就像你目前正在做的那样

这个新变量的两个最常见的名称是thatself。我个人更喜欢使用that,因为浏览器有一个名为 self 的全局窗口属性,如果我隐藏它,我的 linter 会抱怨。

function edit(req, res) {
    var that = this,
    db.User.findById('ABCD', function(err, user){
        that.foo(user);
    });
};

这种方法的一个优点是,一旦将代码转换为使用that,您就可以根据需要添加任意数量的内部回调,并且由于词法作用域,它们都将无缝地工作。另一个优点是它非常简单,甚至可以在古老的浏览器上运行。

2。使用 .bind() 方法。

Javascript 函数有一个 .bind() 方法,可让您创建一个具有固定 this 的版本。

function edit(req, res) {
    db.User.findById('ABCD', (function(err, user){
        this.foo(user);
    }).bind(this));
};

在处理this 时,bind 方法对于必须添加包装函数会更加冗长的回调之一特别有用:

setTimeout(this.someMethod.bind(this), 500);

var that = this;
setTimeout(function(){ that.doSomething() }, 500);

bind 的主要缺点是,如果您有嵌套的回调,那么您还需要对它们调用 bind。此外,IE <= 8 and some other old browsers 本身并未实现 bind 方法,因此如果您仍然需要支持它们,您可能需要使用某种 shimming library

3。如果您需要对函数范围或参数进行更细粒度的控制,请回退到 .call() 和 .apply()

在 Javascript 中控制函数参数的更原始的方法,包括 this,是 .call().apply() 方法。它们允许您使用任何对象作为其this 和任何值作为其参数来调用函数。 apply 对于实现可变参数函数特别有用,因为它将参数列表作为数组接收。

例如,这里有一个版本的 bind,它接收绑定为字符串的方法。这让我们只写下this 一次而不是两次。

function myBind(obj, funcname){
     return function(/**/){
         return obj[funcname].apply(obj, arguments);
     };
}

setTimeout(myBind(this, 'someMethod'), 500);

【讨论】:

  • 发布了一些非常棒的答案,这个有最好的概述。我想我会坚持我的变量赋值,它简单易懂。
  • 有趣的是,我刚刚在 nodejs 中运行了一个(非常简单的)基准测试,方法 1 比方法 2 快 100 倍。
  • 跑了一个类似的基准。我没有快 100 倍,但使用闭包方法仍然快得多。
  • 很好的解释。谢谢!
  • 这应该用新的 lambdas 更新,这对于使用父“this”上下文非常有用
【解决方案2】:

不幸的是,这是一种行之有效的方法,尽管thatthis“复制”的广泛命名约定。

你也可以试试:

db.User.findById('ABCD', this.foo.bind(this));

但这种方法要求foo() 具有与findById() 预期的签名完全相同的签名(在您的示例中,您跳过了err)。

【讨论】:

  • 我们在工作中也选择了这种方法,部分原因是因为使用that 可以降低“解析”和理解的心理开销。
【解决方案3】:

您可以使用以下方法为回调创建代理:

var createProxy = function(fn, scope) {
  return function () {
    return fn.apply(scope, arguments);
  }; 
};

使用它,您可以执行以下操作:

db.User.findById('ABCD', createProxy(function(err, user)) {
  this.foo(user);
}, this));

jQuery 做了类似的事情:$.proxy

而且,正如其他人使用bind 所指出的那样,如果存在兼容性问题,请查看此处:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind#Compatibility

【讨论】:

  • 问题标记为node.js,不存在兼容性问题; bind 有效,应该是首选。
  • @davin 啊,错过了。但我想这在 node.js 之外也可能有帮助,所以我会保持原样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-01
  • 2010-09-25
  • 1970-01-01
相关资源
最近更新 更多