问题
(I'm aware of this question, but the answers don't quite tell me what I need to know.)
I've come across cases where I need to use .bind() on a function in JavaScript in order to pass this
or local/class variables to a function. However, I still don't really know when it's needed.
What is the criteria for knowing when this
or local/class variables will or will not be available in a function, exactly? How do you reason about this?
For instance:
- When a new anonymous
function() { }
is created, wrapped, or passed around? - When using a
class
member function, aclass
getter/setter function, or an oldschoolprototype.function
"member function" (of a function acting as a class)? - In the global scope?
- In a
for
orforEach
loop, or any of their variants? - In a closure outer or inner function?
- In various JS operations like
Array.prototype.forEach.call()
or[].forEach.call()
? - In various JS libraries and scripts, which may have their own custom implementation of things?
The main reason I ask is to be aware of potential pitfalls and to avoid having to rely on trial and error.
回答1:
The Mozilla Developer Network has some great documentation on this specifying the different cases:
- Global context
- Function context
- using bind
- with arrow functions
- in object methods
- in an object constructor
- in DOM event handlers
Check out the links to get an idea of how this
works in the different contexts and therefore when bind
should be used to force bind a different this
context for functions.
Usually, bind
is used to transfer 'ownership' of a function. Specifically, in my experience, it was used back before classes were created to force an object method to be tied to the object in question. It is also useful for when using arrow functions because arrow functions have different contexts.
回答2:
You need to use bind
(or a similar approach) when:
- The function is a traditional (
function
keyword) function or method (in aclass
or object literal), and - That function will be called either in a way that doesn't set
this
explicitly or that sets it to an incorrect value
The reason is that with a traditional function or method, the value of this
is set by the caller, not a part of the function itself. (Details here and here.)
For example, consider:
const obj = {
method() {
console.log(this === obj);
}
};
Now, when we do obj.method()
, we're using syntax (calling the result of a property accessor operation) to specify what this
will be, so:
obj.method();
// => true
But suppose we do this:
const m = obj.method;
Now, just calling m()
will set this
to the default this
(undefined
in strict mode, the global object in loose mode):
m();
// => false
Another way we can explicitly set this
for the call is via call
(and its cousin apply
):
m.call(obj);
// => true
Some functions that call callbacks let you specify what this
to use. forEach
does, as an argument just after the callback:
[1].forEach(m, obj);
// ^ ^^^---- the value to use as `this` in callback
// \-------- the callback to call
// => true
Here's a live example of those:
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
So any time you have a function (such as the callback of a forEach
, or an event handler), you need bind
or a similar mechanism to ensure that the correct this
is used.
This isn't true for some other kinds of functions, just traditional (function
keyword) functions and methods (such as obj.method
above). An arrow function closes over this
instead of using the one supplied by the caller, and a bound function (result of using bind
) has this
bound to it and so ignores any this
supplied by the caller.
回答3:
Credit to T.J. Crowder and Zapparatus for their answers, which provided helpful info. Also helpful were these 4 answers/articles: 1 2 3 4
However, these were either not entirely complete and/or very long-winded. So I have decided to combine all of my findings into one answer, along with code examples.
There are several considerations to factor in when determining whether this
or local/class variables will be available in a function:
- The function's containing scope
- The immediate predecessor in the call chain
- Whether the function is called directly or indirectly
Note: there is also strict mode (which yields undefined
's rather than window
objects) and arrow functions (which do not change this
from the containing scope).
Here are the explicit rules:
- By default,
this
is the global object, which in browser world iswindow
. - Inside a function in the global scope,
this
will still bewindow
, it does not change. - Inside a member function within a
class
or function-class (new function() { }
), inside a function-class's prototype (funcClass.prototype.func = function() { }
), inside a function called by a neighboring member function withthis
, or inside a function mapped in an object ({ key: function() { } }
) or stored in an array ([ function() { } ]
), if the function is called directly with the class/object/array as the immediate predecessor in the call chain (class.func()
,this.func()
,obj.func()
, orarr[0]()
),this
refers to the class/object/array instance. - Inside any closure's inner function (any function within a function), inside a returned function, inside a function called with a plain variable reference as its immediate predecessor in the call chain (regardless of where it actually lives!), or inside a function called indirectly (i.e. passed to a function (e.g.
.forEach(function() { })
) or set to handle an event),this
reverts back towindow
or to whatever the caller may have bound it to (e.g. events may bind it to the triggering object instance).- There is... however... one exception...: if inside a
class
's member function (and only aclass
, not a function-class), if a function within that loses itsthis
context (e.g. by being a closure's inner function), it becomesundefined
, rather thanwindow
...
- There is... however... one exception...: if inside a
Here is a JSFiddle with a bunch of code examples:
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" />
Conclusion: So yeah that's pretty simple.
来源:https://stackoverflow.com/questions/57714243/how-do-i-know-when-to-use-bind-on-a-function-in-js