JavaScript:理解闭包和提升
JavaScript: Understanding closures and hoisting
我知道函数声明被提升到其作用域的顶部。这允许我们在 JavaScript:
中实际声明之前使用这些函数
sayHello(); // It works!
function sayHello() {
console.log('Hello');
}
我还了解到闭包使函数能够保留对在同一范围内声明的变量的引用:
function outerFxn() {
let num = 0;
function innerFxn() {
console.log(num);
num++;
}
return innerFxn;
}
const logNum = outerFxn();
logNum(); // 0
logNum(); // 1
logNum(); // 2
到目前为止一切顺利。但这里有一些奇怪的地方,我希望有人能准确解释发生了什么......
场景 1:可以理解的闭包
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
let txt = 'this is a test message';
function log() {
console.log(txt);
}
zero(log);
}
在上面的示例中,log
函数保留了对其创建范围的引用,持有 txt
变量。然后,稍后在 setTimeout
中执行时,它成功记录了 txt
变量的值。伟大的。然后就是这个...
场景 2:发生了什么?
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
function log() {
console.log(txt);
}
let txt = 'this is a test message';
zero(log);
}
我已将 log
函数声明移动到范围的顶部(无论如何它都会被提升到那里,对吧?),然后 我是在它下面声明 txt
变量。这一切仍然有效,我不确定为什么。当 let
和 const
未被提升时,log
如何保留对 txt
变量的引用?是否将闭包作用域 作为一个整体 进行了分析?我可以稍微清楚地了解 JavaScript 引擎在此处逐步执行的操作。谢谢大地!
它是您离开 test1
函数后作用域的一部分。此时它是否与 var
、let
或 const
一起使用并不重要。由于对整个 body 进行了评估,因此该变量存在于作用域中。
如果您在评估 let
声明之前尝试使用 log
,则会出现错误。
Edit:从技术上讲,用 let
和 const
声明的变量在范围内,但它们被单元化,如果您尝试访问它们。只有在您到达声明时它们才会被初始化并且您可以访问它们。所以它们总是在范围内,只是在评估声明之前不可用。
"Are closure scopes analyzed as-a-whole?" - 是的。闭包保留范围,因为它是在你(词法)离开它的那一刻。在您的示例中,当您到达 test1
中的结尾 }
时,txt
确实存在,因此它在范围内,并且 log
可以毫无问题地访问它。
注意 "lexically" 上面:绑定是在运行时之前完成的,此时唯一重要的是您的块结构。所以即使这样也行得通,尽管从 "dynamic" 的角度来看不应该:
function test1() {
function log() {
console.log(txt);
}
zero(log);
let txt = 'this is a test message';
}
在场景 2 中,您正在做:
let txt = 'this is a test message'
这意味着 txt
将成为 test1()
范围的一部分。
- 同时您声明
log()
可以访问
其父级 test1()
的范围。
那么 运行 时间会发生什么? test1()
将被评估,因此 log()
将有权访问 test1()
的范围。这意味着 txt
将可供 log()
立即使用。
提示:调试它,放一些断点,看看会发生什么。
编辑: 您还可以考虑在 log()
内,txt
未定义,因此其值应该未定义...对吧? console.log(txt)
工作输出 this is a test message
的事实是由于上述关于范围界定的解释。在函数作用域的顶部声明你的变量,在作用域的底部声明你的函数总是一个好习惯,因为它们将首先被评估。考虑到这种情况下的人为因素,最佳实践还可以意味着:you/anyone 仅通过阅读就可以理解代码的作用。
这是 timing/execution 订单。想想就好
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();
}
在您的代码中与未提升变量 txt
相同。在执行 log
时,let txt
将在适当的函数上下文中声明。即使不吊装也可用。函数 log
不存储对变量本身的引用,而是对整个周围执行上下文的引用,并且此上下文存储变量。
我知道函数声明被提升到其作用域的顶部。这允许我们在 JavaScript:
中实际声明之前使用这些函数sayHello(); // It works!
function sayHello() {
console.log('Hello');
}
我还了解到闭包使函数能够保留对在同一范围内声明的变量的引用:
function outerFxn() {
let num = 0;
function innerFxn() {
console.log(num);
num++;
}
return innerFxn;
}
const logNum = outerFxn();
logNum(); // 0
logNum(); // 1
logNum(); // 2
到目前为止一切顺利。但这里有一些奇怪的地方,我希望有人能准确解释发生了什么......
场景 1:可以理解的闭包
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
let txt = 'this is a test message';
function log() {
console.log(txt);
}
zero(log);
}
在上面的示例中,log
函数保留了对其创建范围的引用,持有 txt
变量。然后,稍后在 setTimeout
中执行时,它成功记录了 txt
变量的值。伟大的。然后就是这个...
场景 2:发生了什么?
function zero(cb) {
return setTimeout(cb, 0);
}
function test1() {
function log() {
console.log(txt);
}
let txt = 'this is a test message';
zero(log);
}
我已将 log
函数声明移动到范围的顶部(无论如何它都会被提升到那里,对吧?),然后 我是在它下面声明 txt
变量。这一切仍然有效,我不确定为什么。当 let
和 const
未被提升时,log
如何保留对 txt
变量的引用?是否将闭包作用域 作为一个整体 进行了分析?我可以稍微清楚地了解 JavaScript 引擎在此处逐步执行的操作。谢谢大地!
它是您离开 test1
函数后作用域的一部分。此时它是否与 var
、let
或 const
一起使用并不重要。由于对整个 body 进行了评估,因此该变量存在于作用域中。
如果您在评估 let
声明之前尝试使用 log
,则会出现错误。
Edit:从技术上讲,用 let
和 const
声明的变量在范围内,但它们被单元化,如果您尝试访问它们。只有在您到达声明时它们才会被初始化并且您可以访问它们。所以它们总是在范围内,只是在评估声明之前不可用。
"Are closure scopes analyzed as-a-whole?" - 是的。闭包保留范围,因为它是在你(词法)离开它的那一刻。在您的示例中,当您到达 test1
中的结尾 }
时,txt
确实存在,因此它在范围内,并且 log
可以毫无问题地访问它。
注意 "lexically" 上面:绑定是在运行时之前完成的,此时唯一重要的是您的块结构。所以即使这样也行得通,尽管从 "dynamic" 的角度来看不应该:
function test1() {
function log() {
console.log(txt);
}
zero(log);
let txt = 'this is a test message';
}
在场景 2 中,您正在做:
let txt = 'this is a test message'
这意味着txt
将成为test1()
范围的一部分。- 同时您声明
log()
可以访问 其父级test1()
的范围。
那么 运行 时间会发生什么? test1()
将被评估,因此 log()
将有权访问 test1()
的范围。这意味着 txt
将可供 log()
立即使用。
提示:调试它,放一些断点,看看会发生什么。
编辑: 您还可以考虑在 log()
内,txt
未定义,因此其值应该未定义...对吧? console.log(txt)
工作输出 this is a test message
的事实是由于上述关于范围界定的解释。在函数作用域的顶部声明你的变量,在作用域的底部声明你的函数总是一个好习惯,因为它们将首先被评估。考虑到这种情况下的人为因素,最佳实践还可以意味着:you/anyone 仅通过阅读就可以理解代码的作用。
这是 timing/execution 订单。想想就好
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();
}
在您的代码中与未提升变量 txt
相同。在执行 log
时,let txt
将在适当的函数上下文中声明。即使不吊装也可用。函数 log
不存储对变量本身的引用,而是对整个周围执行上下文的引用,并且此上下文存储变量。