问题
- I did a lot research online, read many posts including MDN and so on.
- I understand that for traditional defined functions, "this" within functions is defined by objects calling/invoking them (and several different cases, object literal, new constructor, event handler, etc.).
- I understand that for arrow functions, "this" is defined lexically, by the enclosing context/scope, not by objects invoking them (though we can use a traditionally defined function (say, A) to wrap arrow functions (say, B) and thus pass the object reference to A first, and then to B)
Here come the questions:
what exactly is an enclosing context? This is further convoluted since ES6 allows {} to be a block/scope/context. Are {} as delimiters good enough to define an "enclosing context" or it has to be within a function scope.
One specific example:
let EventEmitter = require('events').EventEmitter; class Person extends EventEmitter { constructor(name) { super(); this.name = name; } } let mary = new Person('mary'); mary.on('speak', (said) => { console.log(
${this.name}: ${said}
); }); mary.emit('speak', 'you may delay, but time will not');
It simply setups a custom event and adds a callback function when the custom event is triggered. Why the arrow function here doesn't work?
"mary" is the object that calls the "on" function, which should set "this" within "on" to be "mary". The most important thing is, the arrow function is defined in "on" function in its parameter position (lexically enough, right?), why the arrow function can't get "this" value from its "enclosing context", that is, the "on" function here???
the same example, with conventional function definition:
let EventEmitter = require('events').EventEmitter; class Person extends EventEmitter { constructor(name) { super(); this.name = name; } } let mary = new Person('mary'); mary.on('speak', function(s) { console.log(this); }); mary.emit('speak', 'you may delay, but time will not');
Now it works. I understand that given the old way of function definition, console.log(this) can now be dynamically bound to the object calling it. But wait, "mary" is the object and "on" is the immediate function being called. Shouldn't "on" form a closure for the anonymous function within it? And I remember "this" within the nested function cannot access "this" of its closure (the enclosing context, again, huh) and thus should not get the "mary" refernce. Why it works here?
When we talk about something (say, A) within a function, does it mean A have to be in the {} of the function, or A could be in the parameter/argument area as well? That is, function(){A} vs. function(A) {}.
similarly, if an arrow function is passed as a parameter like function(()=>()) {}, does the outer function considered its enclosing scope? Or the enclosing scope in this case would be the outer of outer function?
The above might sound very stupid. Thanks a lot for your help.
回答1:
I'm probably not using the word scope precisely here, but basically just think of a scope as a map of variable names to what locations in memory they refer to; a nested scope's name/variable pairs shadow (override) associations with the same name in the enclosing (aka parent) scope.
function foo() { // this is the "enclosing scope" of bar
var a = 4 <-----------+
|
var b = a // refers to --+
function bar() {
var a = 7 <-----------+
|
var c = a // refers to --+
}
}
this
behaves exactly the same way as a
does in the above example.
function
scopes implicitly define a reference for this
but ES2015 arrow function scopes and block scopes do not. Here is what those definitions would look like if they were explicit:
function foo() { // this is the enclosing scope of baz and the block below
var this = ... <-----------+--+
| |
var b = this // refers to --+ |
|
{ |
var q = this // refers to ---+
}
function bar() { // this is the enclosing scope of baz
var this = ... <-----------+--+
| |
var c = this // refers to --+ |
|
var baz = () => { |
var d = this // refers to ---+
}
}
}
The actual value at the memory location that this
refers to in a particular scope is not lexically defined; it is set at runtime to the (memory location of the) object a function is called upon. But the shadowing of one reference by another is always defined lexically.
回答2:
The this
of arrow functions is the nearest function scope, meaning the scope of the function defined using the function
keyword in which it occurs.
This applies even if arrow functions are nested somehow, as in
function scope() {
const arrow = a => b => this.foo(a, b);
}
Here the this
in question is the this
of scope
.
"mary" is the object that calls the "on" function, which should set "this" within "on" to be "mary". The most important thing is, the arrow function is defined in "on" function in its parameter position (lexically enough, right?), why the arrow function can't get "this" value from its "enclosing context", that is, the "on" function here???
Yes, this
within on
is mary
. However, that does not mean that the implementation of on
calls its callback with mary
as this
, or any other particular this
--that's defined by the implementation of on
. Furthermore, the "context" of the arrow function, which governs its 'this', is not on
, nor the entire mary.on
statement, nor some enclosing {}
block, but rather the function scope within which is occurs.
回答3:
I had hard times decrypting this concept, but recently wrapped my head over it.
So to understand this, we should start by understanding what is scope ? Scope is a set of rules the engine play by when it does a lookup for a variable. So in practice it is described very nice from the book You don't know JS as a conversation between the engine and scope. If you think about a simple left hand assignment such as let a = 1;
the conversation would be something like :
- Engine : Hey scope, is there anything named "a" ?
- Scope : Nope
- Engine : Cool than, can you please remember that from now on there is a member called "a" and do me a favor, please assign it's value "1"';
- Scope : Ay ay compiler
Now imagine a scenario like : console.log(a)
- Engine : Hey scope, ever heard about console
- Scope : Why yes I have, it's an object
- Engine : Cool, but have you also heard about it's property log
- Scope : Yes, yes I did
- Engine : Well thanks scope, I see that it is a function. I have another question, do you know this member called "a" and what about it's value
- Scope : I know that as well, and it's value is 1
- Engine : Okay scope, I'm all set. Invoking log now and I'm passing that
In JavaScript there are different ways of defining scope. But for the simplicity of this answer let's just talk about function scopes. (You can also blocks to define scope)
// global scope
function foo(a) {
//foo scope
var b = a * 2;
function bar(c) {
// bar scope
console.log( a, b, c );
}
bar(b * 3);
}
foo( 2 ); // 2 4 12
From the example above I think it's quite easy to spot 2 scopes, the foo
scope and the bar
scope.
As you can see bar is invoked within foo and it's prints 3 values, a
and b
which are in the scope of foo
and c
which is in scope of bar
. It can do that because a scope lookup does not happen just within the current scope, it continues to check out it's parent scope (enclosing scope) until it reaches the global scope.
So in hierarchy we would see something like this
- global scope
- --> foo scope
- ||--> bar scope
In this case foo
scope is the enclosing scope of bar
, because it literally encloses bar within.
回答4:
The functions passed to the callback of an event emitter are not bound to the emitter's scope unless you do it, regardless of whether it's a traditional function or fat arrow function.
When they say a fat arrow function is bound to the lexical scope they mean it doesn't create its own new scope.
Consider:
function Bob(){
this.a = 5;
function getA() { return this.a;}
this.getAReal = () => this.a
}
const bob = new Bob();
bob.getA(); // undefined
bob.getAReal(); // 5
来源:https://stackoverflow.com/questions/42847683/really-confused-by-enclosing-scope-of-javascript-es6-arrow-function