我怎么知道什么时候在 JS 函数上使用 .bind()?
How do I know when to use .bind() on a function in JS?
(我知道 this 问题,但答案并没有告诉我我需要知道的内容。)
我遇到过需要在 JavaScript 中的函数上使用 .bind()
以便将 this
或 local/class 变量传递给函数的情况。但是,我仍然不知道什么时候需要它。
判断 this
或 local/class 变量何时在函数中可用或不可用的标准是什么?你怎么看这个?
例如:
- 何时创建、包装或传递新的匿名
function() { }
?
- 当使用
class
成员函数、class
getter/setter 函数或老派 prototype.function
"member function"(作为 class)?
- 在全局范围内?
- 在
for
或 forEach
循环中,或它们的任何变体?
- 在闭包外部或内部函数中?
- 在各种 JS 操作中,例如
Array.prototype.forEach.call()
或 [].forEach.call()
?
- 在各种JS库和脚本中,哪些可能有自己自定义实现的东西?
我问的主要原因是要注意潜在的陷阱,避免依赖反复试验。
您需要在以下情况下使用 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
一些调用回调的函数允许您指定要使用的 this
。 forEach
确实如此,作为回调后的参数:
[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 指定不同情况的重要文档:
- Global context
- Function context
- using
bind
- with arrow functions
- in object methods
- in an object constructor
- 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" />
结论:是的,这很简单。
(我知道 this 问题,但答案并没有告诉我我需要知道的内容。)
我遇到过需要在 JavaScript 中的函数上使用 .bind()
以便将 this
或 local/class 变量传递给函数的情况。但是,我仍然不知道什么时候需要它。
判断 this
或 local/class 变量何时在函数中可用或不可用的标准是什么?你怎么看这个?
例如:
- 何时创建、包装或传递新的匿名
function() { }
? - 当使用
class
成员函数、class
getter/setter 函数或老派prototype.function
"member function"(作为 class)? - 在全局范围内?
- 在
for
或forEach
循环中,或它们的任何变体? - 在闭包外部或内部函数中?
- 在各种 JS 操作中,例如
Array.prototype.forEach.call()
或[].forEach.call()
? - 在各种JS库和脚本中,哪些可能有自己自定义实现的东西?
我问的主要原因是要注意潜在的陷阱,避免依赖反复试验。
您需要在以下情况下使用 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
一些调用回调的函数允许您指定要使用的 this
。 forEach
确实如此,作为回调后的参数:
[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 指定不同情况的重要文档:
- Global context
- Function context
- using
bind
- with arrow functions
- in object methods
- in an object constructor
- in DOM event handlers
查看链接以了解 this
在不同上下文中的工作方式,以及何时应使用 bind
强制绑定不同的 this
函数上下文。
通常,bind
用于传递'ownership'一个函数。具体来说,根据我的经验,在创建 类 之前使用它来强制将对象方法绑定到相关对象。它在使用箭头函数时也很有用,因为箭头函数有不同的上下文。
归功于
然而,这些要么不完全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" />
结论:是的,这很简单。