我怎么知道何时在JS中的函数上使用.bind()?

时间:2019-08-29 16:21:19

标签: javascript function scope this bind

(我知道this的问题,但是答案并不能完全告诉我我需要知道的内容。)

我遇到过这样的情况,我需要在JavaScript中的函数上使用.bind()才能将this或本地/类变量传递给函数。但是,我仍然不太清楚何时需要它。

确切地知道何时this或局部/类变量在函数中可用或不可用的标准是什么?您对此有何看法?

例如:

  • 创建,包装或传递新的匿名function() { }时是什么?
  • 在使用class成员函数,class getter / setter函数或老式的prototype.function“成员函数”(作为类的函数)时?
  • 在全球范围内?
  • forforEach循环中,还是在它们的任何变体中?
  • 在闭包中是外部函数还是内部函数?
  • 在各种JS操作中,例如Array.prototype.forEach.call()[].forEach.call()
  • 在各种JS库和脚本中,它们可能具有自己的事物自定义实现?

我问的主要原因是要意识到潜在的陷阱,避免不得不依靠反复试验。

3 个答案:

答案 0 :(得分:2)

Mozilla开发人员网络在this上有一些不错的文档,说明了不同的情况:

  1. Global context
  2. Function context
  3. using bind
  4. with arrow functions
  5. in object methods
  6. in an object constructor
  7. in DOM event handlers

查看链接以了解this在不同上下文中的工作原理,因此应在何时使用bind强制为函数绑定不同的this上下文。

通常,bind用于转移功能的“所有权”。具体来说,根据我的经验,在创建类以强制将对象方法绑定到所讨论的对象之前,已使用过该方法。对于使用箭头功能的情况也很有用,因为箭头功能具有不同的上下文。

答案 1 :(得分:1)

在以下情况下,您需要使用bind(或类似方法)

  • 该函数是传统的(function关键字)函数或方法(在class或对象常量中),并且
  • 该函数将以未显式设置this或将其设置为不正确的值的方式调用

原因是,对于传统的函数或方法,this的值由调用方设置,而不是函数本身的一部分。 (详细信息herehere。)

例如,考虑:

const obj = {
    method() {
        console.log(this === obj);
    }
};

现在,当我们执行obj.method()时,我们正在使用语法(调用属性访问器操作的结果)来指定this的含义,因此:

obj.method();
// => true

但是假设我们这样做:

const m = obj.method;

现在,仅调用m()会将this设置为默认的this(严格模式下的undefined,松散模式下的全局对象):

m();
// => false

我们可以通过this(及其堂兄call)为通话明确设置apply的另一种方法:

m.call(obj);
// => true

一些调用回调的函数使您可以指定要使用的thisforEach做为回调之后的参数:

[1].forEach(m, obj);
//          ^  ^^^---- the value to use as `this` in callback
//           \-------- the callback to call
// => true

这是其中的一个生动例子:

const obj = {
    method() {
        console.log(this === obj);
    }
};

obj.method();
// => true, `this` was set to `obj` because you did the call on the
// result of a property accessor
const m = obj.method;

m();
// => false, `this` was the default `this` used when `this` isn't
// specified explicitly via syntax or `call`

m.call(obj);
// => true, `this` was explicitly set via `call`

[1].forEach(m, obj);
// => true, `this` was explicitly set via `forEach`'s `thisArg` argument

因此,只要您拥有函数(例如forEach的回调或事件处理程序),就需要bind或类似的机制来确保正确的this使用。

对于其他某些类型的函数,仅是传统的function关键字)和方法(例如上述obj.method),情况并非如此。箭头函数关闭 this,而不使用调用方提供的箭头函数,绑定函数(使用bind的结果)已绑定this因此将忽略调用方提供的任何this

答案 2 :(得分:0)

T.J. CrowderZapparatus致谢,以提供答案。这4个答案/文章也很有帮助:1 2 3 4

但是,它们要么不完全完整和/或very之以鼻。因此,我决定将我的所有发现以及代码示例组合成一个答案。


确定函数中是否可以使用this或局部/类变量时,需要考虑以下因素:

  • 该函数的包含范围
  • 呼叫链的直接前辈
  • 该函数是直接调用还是间接调用

注意:还存在严格模式(产生undefined而不是window对象)和箭头函数(不会从包含范围更改this


以下是明确的规则:

  • 默认情况下,this是全局对象,在浏览器世界中是window
  • 在全局范围内的函数中,this仍将是window,不会改变。
  • class或函数类(new function() { })内的成员函数内,函数类的原型(funcClass.prototype.func = function() { })内,由相邻成员函数调用的函数内, this,或者在映射到对象({ key: function() { } })或存储在数组([ function() { } ])中的函数内部,如果直接用类/对象/数组调用该函数作为呼叫链中的直接前辈class.func()this.func()obj.func()arr[0]()),this是指类/对象/ array实例。
  • 在任何闭包的内部函数(函数内的任何函数)内部,返回的函数内部,在以普通变量引用作为调用链中直接前身的函数内(无论其实际驻留在何处!< / strong>),或者在间接调用的函数内部(即传递给函数(例如.forEach(function() { })或设置为处理事件)),this返回到window 绑定到调用者可能绑定到的任何对象(例如,事件可能会将其绑定到触发对象实例)。
    • 但是有一个例外...:如果在class的成员函数中(并且只有class,而不是函数类),如果一个函数内的内容失去其this上下文(例如通过成为闭包的内部函数),它将变为undefined,而不是window ...

这里是JSFiddle,其中包含大量代码示例:

outputBox = document.getElementById("outputBox");

function print(printMe = "") {
	outputBox.innerHTML += printMe;
}

function printLine(printMe = "") {
	outputBox.innerHTML += printMe + "<br/>";
}

var someVar = "someVar";

function func(who) {
	printLine("Outer func (" + who + "): " + this);
  var self = this;
  (function() {
  	printLine("Inner func (" + who + "): " + this);
    printLine("Inner func (" + who + ") self: " + self);
  })();
}
func("global");
printLine();
func.call(someVar, "someVar");

printLine();

function funcTwo(who) {
	printLine("Outer funcTwo (" + who + "): " + this);
  var self = this;
  return function funcThree() {
  	printLine("Inner funcThree (" + who + "): " + this);
    printLine("Inner funcThree (" + who + ") self: " + self);
  };
}
funcTwo("global")();
printLine();
f = funcTwo("global f");
f();
printLine();
funcTwo.call(someVar, "someVar")();

printLine();

object = {
  func: function(who) {
    printLine("Object outer (" + who + "): " + this);
    var self = this;
    (function() {
      printLine("Object inner (" + who + "): " + this);
      printLine("Object inner (" + who + ") self: " + self);
    })();
  }
}
object.func("good");
printLine();
bad = object.func;
bad("bad");

printLine();

function funcClass(who) {
	printLine("funcClass (" + who + "): " + this);
}
funcClass.prototype.func = function() {
	printLine("funcClass.prototype.func: " + this);
  self = this;
  (function() {
    printLine("funcClass.func inner: " + this);
    printLine("funcClass.func inner self: " + self);
  })();
}
fc = funcClass("bad");
printLine();
fc = new funcClass("good");
fc.func("good");

printLine();

class classClass {
	constructor() {
  	printLine("classClass constructor: " + this);
  }
  func() {
  	printLine("classClass.func: " + this);
    self = this;
    (function() {
    	printLine("classClass.func inner: " + this);
      printLine("classClass.func inner self: " + self);
    })();
  }
  funcTwo() {
  	this.func();
  }
}
cc = new classClass();
cc.func();
printLine();
printLine("Calling funcTwo:");
cc.funcTwo();

printLine();

[0].forEach(function(e) {
	printLine("[0].forEach: " + this);
  printLine("[0].forEach someVar: " + someVar);
});
[0].forEach(function(e) {
	printLine("[0].forEach with [0]: " + this);
}, [0]);

printLine();

arr = [
	function(who) {
  	printLine("Array (" + who + "): " + this);
  },
  1,
  10,
  100
];
arr[0]("good");
arrFunc = arr[0];
arrFunc("bad");

printLine();

var button = document.getElementById("button");
button.onclick = function() {
	printLine("button: " + this);
}
button.click();
button.onclick = func;
button.click();

setTimeout(function() {
	printLine();
	printLine("setTimeout: " + this);
  printLine("setTimeout someVar: " + someVar);
}, 0);

setTimeout(fc.func, 0);
setTimeout(cc.func, 0);
<input id="button" type="button" value="button"/>
<br/><br/>
<div id="outputBox" />


结论:是的,这很简单。