我怎么知道什么时候在 JS 函数上使用 .bind()?

How do I know when to use .bind() on a function in JS?

(我知道 this 问题,但答案并没有告诉我我需要知道的内容。)

我遇到过需要在 JavaScript 中的函数上使用 .bind() 以便将 this 或 local/class 变量传递给函数的情况。但是,我仍然不知道什么时候需要它。

判断 this 或 local/class 变量何时在函数中可用或不可用的标准是什么?你怎么看这个?

例如:

我问的主要原因是要注意潜在的陷阱,避免依赖反复试验。

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

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

原因是对于传统的函数或方法,this的值是由调用者设置的,不是函数本身的一部分。 (详情here and here。)

例如,考虑:

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

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用于传递'ownership'一个函数。具体来说,根据我的经验,在创建 类 之前使用它来强制将对象方法绑定到相关对象。它在使用箭头函数时也很有用,因为箭头函数有不同的上下文。

归功于 and for their answers, which provided helpful info. Also helpful were these 4 answers/articles: 1 2 3 4

然而,这些要么不完全and/or很啰嗦。因此,我决定将我的所有发现与代码示例结合到一个答案中。


在确定 this 或 local/class 变量在函数中是否可用时,需要考虑几个因素:

  • 函数的包含范围
  • 调用链中的直接前导
  • 函数是直接调用还是间接调用

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


这里是明确的规则:

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


结论:是的,这很简单。