【问题标题】:Difference in binding of functions calls?函数调用绑定的区别?
【发布时间】:2016-10-18 11:48:02
【问题描述】:

我有一个对象“我”,并为其定义了函数。

me.prototype.f = function(args){
    console.log("f called");
};

我需要在异步函数中提供 f 作为回调。

me.prototype.operation = function (){
    var self = this;
    var client; // an io client
    //client.on("data", self.f);                --a
    //client.on("data", self.f.bind(self))      --b
}

在这种情况下,上面的 a 和 b 有什么区别吗?是否存在 a) 可能失败的情况?

在哪些情况下我们会在没有绑定的情况下遇到问题?通常,值可能会根据上下文发生变化。 (例如,使用循环索引值关闭)
那么,在不同的场景中,值可能会有所不同。


从答案中我得出以下结论。请确认:

a) 当函数作为引用传递时,范围会发生变化,并且会获取本地引用,除非上下文没有使用 bind、call 或 apply 绑定。

b) 特别是在回调/异步函数中,如果上下文发生变化(例如setTimeout,上下文会发生变化,因此在调用过程中self的含义会发生变化。)

c) 它与闭包无关,因为在闭包中会检查词法环境以评估值,但是,这里会评估执行期间的上下文

【问题讨论】:

  • 如果在f 函数中使用this,将会遇到问题。这就是你 bind this 发挥作用的原因。
  • 简单示例var a = {f: function(){console.log(this)}}; a.f(); var f = a.f; f();。您在第二次执行中丢失了 this,与使用 self.f 丢失的相同
  • @Maxx 如果我将函数调用为 client.on("data", self.f); ,在这种情况下,我的 this 也将被保留,因为 self 的上下文将与函数调用相关联(因为它是一个成员函数)。那么a)和b)有什么区别
  • 您的 self var 在您的示例中绝对没用,它什么也不做。在a)中,您将一些函数f 传递给客户端,当您的client 调用f 时,f 对其上下文一无所知。在 b) 中,您将上下文绑定到 f
  • self 这里指的是me 的实例,因此提供了与之相关的功能。这两个案例都有评论,我想讨论一下。

标签: javascript binding prototype


【解决方案1】:

self.f 是对f 函数的引用。

self.f.bind(self) 是一个新函数,调用时会调用 f,而 self 则为 this

在这种情况下,上面的a和b有什么区别吗? a) 可能失败的任何情况?

是的。具体行为取决于您拨打电话的方式:

function me() {}
me.prototype.f = function() {
  console.log(this);
};
me.prototype.operation = function() {
  client.on("this.f", this.f);
  client.on("this.f.bind(this)", this.f.bind(this));
};

var client = {
  on: function(whatever, callback) {
    var self = this;
    setTimeout(function() {
      console.log("=== " + whatever + " ===");
      self.f = callback;

      // `this` not specified
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback();

      // `this` belongs to current function (passed to setTimeout)
      //  `this.f` logs the global object, or `undefined` in strict mode
      //  `this.f.bind(this)` logs `me` instance
      callback.call(this);

      // `self` is a reference to the client object
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      callback.call(self);

      // `this` is determined by the calling object (client again)
      //  `this.f` logs `client`
      //  `this.f.bind(this)` logs `me` instance
      self.f();

    }, 1000);
  }
};


(new me()).operation();

更新

我决定在答案中添加更多内容,因为您在问题中添加了更多内容。很抱歉,结果这么长。

this如何工作

this 的值取决于函数调用,而不是函数定义。

  • 调用不是对象属性的函数时this 严格来说是undefined,或者引用全局对象 非严格模式。

    function f1() {
      console.log(this === window);
    }
    function f2() {
      'use strict';
      console.log(this === undefined);
    }
    
    f1();  // true
    f2();  // true
    
  • 当调用作为对象属性的函数时,this 将引用该对象。

    var object = {
      f: function() {
        console.log(this === object);
      }
    };
    object.f();   // true
    
  • 当调用带有new前缀的函数时,this将引用 新创建的对象。

    var backup;
    function Example() {
      backup = this;
      this.value = 0;
    }
    
    var example = new Example();
    console.log(example === backup);  // true
    console.log(example.value);       // 0
    
  • 当调用一个不是对象属性的函数时,而是 是对象原型链的一部分,this 将引用 调用对象。

    function Example() {}
    Example.prototype.f = function() {
      console.log(this === example);
    };
    
    var example = new Example();
    example.f();   // true
    

更改this

提到的行为通常不是我们想要的。考虑以下情况。

    function Counter() {
      this.count;
      this.limit;
      this.interval;
    }
    Counter.prototype.countTo = function(number) {
      this.count = 1;
      this.limit = number;
      this.interval = setInterval(update, 1000);

      function update() {
        console.log(this.count);
        if (++this.count > this.limit)
          clearInterval(this.interval);
      }
    };

    var counter = new Counter();
    counter.countTo(5);   // undefined, NaN, NaN, NaN, NaN, NaN...

this 在调用函数时获取其值,而不是在定义时。间隔函数将在稍后调用,this 将引用全局对象。在大多数情况下,这不是一个人所追求的。

隐式更改this

解决setInterval 问题的历史方法是将this 的引用分配给有问题的函数可见的变量,并改用该变量。

function Counter() {}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var self = this;

  function update() {
    console.log(self.count);
    if (++self.count > self.limit)
      clearInterval(self.interval);
  }
  this.interval = setInterval(update, 1000);
};

var counter = new Counter();
counter.countTo(5);

同样使用闭包,防止污染函数范围。

function Counter() {}

Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  this.interval = setInterval((function(self) {
    return function() {
      console.log(self.count);
      if (++self.count > self.limit)
        clearInterval(self.interval);
    };
  }(this)), 1000);
};

var counter = new Counter();
counter.countTo(5);

每当创建Counter 时创建一个新的update 函数并不是最有效的方法。假设我们想将其移动到原型中。

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(this.update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...

现在的问题是让update 了解selfs,因为它们不再定义在同一个范围内,并且所有Counters 只有一个update 函数。

我们可以创建一个中介函数,将this 的值(复制一个引用)括起来,并使用它来进行调用。

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;

  var update = (function(self) {
    return function() {
      self.update();
    };
  }(this));

  this.interval = setInterval(update, 1000);
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);

我们可以使用箭头函数,它在词法上绑定this,也就是说,它的值取决于函数定义,而不管它是如何被调用的。

function Counter() {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(() => this.update(), 1000); // neat.
};
Counter.prototype.update = function() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
};

var counter = new Counter();
counter.countTo(5);

显式更改this

假设我们的 update 函数在别处定义,我们希望使用它来代替原型函数。

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update, 1000);
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5); // undefined, NaN, NaN, NaN, NaN, NaN...

使用前面提到的方法,我们可以在对象或其原​​型中附加对update 的引用,并使用箭头函数来绑定this。如果我们不喜欢这种开销,我们可以使用三个强大的函数来显式设置this

在函数上调用bind 会生成一个新函数,其this 被设置为第一个参数。

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  this.interval = setInterval(update.bind(this), 1000); // not bad.
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}
var counter = new Counter();
counter.countTo(5);

在函数上使用callapply 将调用将this 设置为第一个参数。

function Counter(to) {
  this.count;
  this.limit;
  this.interval;
}
Counter.prototype.countTo = function(number) {
  this.count = 1;
  this.limit = number;
  var self = this;
  this.interval = setInterval(function() {
    update.call(self);
  }, 1000);
};

function update() {
  console.log(this.count);
  if (++this.count > this.limit)
    clearInterval(this.interval);
}

var counter = new Counter();
counter.countTo(5);

【讨论】:

  • 谢谢,我用上面的方法做了很多实验。上来将遵循观察。 a) 作用域将随着函数作为引用传递而改变,并且将获取本地引用,除非上下文未使用 bind、call 或 apply 绑定。 b) 特别是在回调/异步函数中,如果上下文发生变化(例如 setTimeout,上下文会发生变化,因此 self 的含义会在调用期间发生变化。) c) 它与闭包无关,因为闭包中的词法环境检查评估值,但是,这里评估执行期间的上下文
  • 你的观察是正确的。关键是this 是根据函数调用而不是函数定义确定的。
  • 在许多情况下,这不是您想要的,为了规避它,您使用bindcallapply 或一个简单的函数显式设置this 值使用来自其环境的 this 的存储引用。
  • 有一点我忘了说的是ES6 arrow functions,它在词法上绑定this,也就是说,它是由函数定义决定的。
  • 最后两点很有趣。在simple function using the stored reference of this 中 - 您的意思是使用存储的引用作为参数之一调用函数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-16
  • 2013-06-05
  • 2020-03-17
  • 2013-07-20
  • 1970-01-01
  • 2013-09-20
  • 1970-01-01
相关资源
最近更新 更多