I understand that function declarations are hoisted to the top of their scope. This allows us to use those functions before they're actually declared in JavaScript:
sayHello(); // It works!
function sayHello() {
console.log('Hello');
}
I also understand that closures enable functions to retain references to variables declared within the same scope:
function outerFxn() {
let num = 0;
function innerFxn() {
console.log(num);
num++;
}
return innerFxn;
}
const logNum = outerFxn();
logNum(); // 0
logNum(); // 1
logNum(); // 2
So far so good. But here's some weirdness that I'm hoping someone can explain exactly what's going on...
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
let txt = 'this is a test message';
function log() {
console.log(txt);
}
zero(log);
}
In the example above, the log
function retains a reference to the scope in which it was created, holding on the the txt
variable. Then, when executed later on in the setTimeout
, it successfully log's the txt
variable's value. Great. Then there's this...
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
function log() {
console.log(txt);
}
let txt = 'this is a test message';
zero(log);
}
I've moved the log
function declaration to the top of the scope (it would have been hoisted there anyway, right?), then I'm declaring the txt
variable below it. This all still works and I'm not sure why. How is log
retaining a reference to the txt
variable when let
's and const
's aren't hoisted up? Are closure scopes analyzed as-a-whole ? I could use a bit of clarity on what the JavaScript engine is doing step by step here. Thank you SO land!
It's part of the scope after you leave the test1
function. It doesn't matter if it's being used with var
, let
or const
at that point. Since the whole body has been evaluated, the variable exists on the scope.
Had you tried to use log
before the let
declaration is evaluated, you'd gotten an error.
Edit : Technically, the variables declared with let
and const
are in scope, but they are unitialized which results in an error if you try to access them. It's only until you get to the declarations that they are initialized and you can access them. So they are always in scope, just not available until declarations are evaluated.
"Are closure scopes analyzed as-a-whole?" - yes. Closures retain the scope as it is at the moment you (lexically) leave it. In your example, txt
does exist when you reach the closing }
in test1
, so it's in the scope, and log
has no problems accessing it.
Note "lexically" above: bindings are done prior to runtime, when only thing that matters is your block structure. So even this would work, although it shouldn't from the "dynamic" point of view:
function test1() {
function log() {
console.log(txt);
}
zero(log);
let txt = 'this is a test message';
}
In Scenario 2 you are doing:
let txt = 'this is a test message'
which means that txt
will be part of the scope of test1()
. log()
which will have access to the scope of its parent test1()
. So what happens on run-time? test1()
will be evaluated and as such log()
will have access to the scope of test1()
. This means that txt
will be available for log()
to use immediately.
Tip: debug it, put some break points and see what happens.
Edit: You can also consider that within log()
, txt
is not defined and as such its value should be undefined... right? The fact that console.log(txt)
works outputs this is a test message
is due to the above explanation on scoping. It's always good practice to declare your variables at the top of the function scope, and your functions at the bottom of the scope since they will be evaluated first anyways. Consider the human factor in this situation, best practice can also mean: for you/anyone to understand what the code does just by reading it.
this is a timing/execution order thing. Think about it like
function test1(){
var context = { };
function log(){
if(context.hasOwnProperty("txt")){
console.log(context.txt);
}else{
throw new Error("there is no value 'txt' declared in this context");
}
}
context.txt = 'this is a test message';
log();
}
Same in your code with the not hoisted variable txt
. At the time, log
is executed, let txt
will have been declared in the proper function context. It is available even if it ain't hoisted. The function log
doesn't store a reference to the variable itself, but to the whole surrounding execution context, and this context stores the variables.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.