使用回调和闭包时在 Javascript 中维护对“this”的引用

IT技术 javascript node.js
2021-03-15 14:01:48

我发现自己将“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) {

};

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

3个回答

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

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

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

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

这种方法的一个优点是,一旦将代码转换为 using,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 和其他一些旧的浏览器,不会在本地实现该bind方法,因此如果您仍然需要支持它们,您可能需要使用某种填充库

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);
发布了一些非常棒的答案,这个答案是最好的概述。我想我会坚持我的变量赋值,它简单易懂。
2021-04-22 14:01:48
很好的解释。谢谢!
2021-04-25 14:01:48
有趣的是,我刚刚在 nodejs 中运行了一个(非常简单的)基准测试,方法 1 比方法 2 快 100 倍。
2021-05-07 14:01:48
这应该使用新的 lambdas 进行更新,这些 lambdas 非常适合使用父“this”上下文
2021-05-11 14:01:48
运行了一个类似的基准测试。我没有快 100 倍,但使用闭包方法仍然明显更快。
2021-05-12 14:01:48

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

你也可以试试:

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

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

我们也在我的工作中选择了这种方法,部分是因为这个,部分是因为 usingthat具有较低的“解析”和理解心理开销。
2021-04-26 14:01:48

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

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

@davin 啊,错过了。但我想这在 node.js 之外也可能有用,所以我将保持原样。
2021-04-26 14:01:48
问题被标记为node.js,不存在兼容性问题;bind工作,应该是首选。
2021-05-20 14:01:48