We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
为了保证可读性,本文采用意译而非直译。
引用 ES6 规范作者 Allen Wirfs-Brock一条最近的推特:
变量提升是一个陈旧且令人困惑的术语。甚至在 ES6 之前:变量提升的意思究竟是“提升至当前作用域顶部”还是“从嵌套的代码块中提升到最近的函数或脚本作用域中”?还是两者都有?
受 Allen 启发,本文提出了一种不同的方法来描述变量声明。
可以将声明分为两个方面:
下表总结了不同声明的方式如何处理上述两个方面。
**“Duplicates”**描述是否可以在同一作用域内声明两次。
**“Global prop.”**表示一个在 script 中的声明,当全局作用域中被执行时,是否会向全局对象添加属性。
TDZ 表示暂时性死区(稍后解释)。 函数声明在严格模式下是块作用域的(例如在模块内部),但在非严格模式下是函数作用域。
TDZ
对于JavaScript,TC39 需要决定如果在声明之前访问其直接作用域中的常量会发生什么:
{ console.log(x); // 这里会发生什么? const x; }
主要有两种种情况:
打印 undefined
undefined
报错
第一种不会出现,因为 x 是一个常量,如果打印 undefined,在声明前和声明后它将拥有不同的值,x 就不是常量了。
let 和 const 都会出现第二种情况,就是会报错。进入变量作用域与执行声明之间的这段时间被称为该变量的 临时死区(TDZ):
在临时死区中,变量被认为是未初始化的(就像它有一个特殊的值一样)。
如果访问未初始化的变量,将得到ReferenceError 错误。
ReferenceError
一旦执行到变量声明,该变量将被设置为初始化器的值(通过赋值符号指定),如果没有初始化,则为undefined。
以下代码说明了临时死区:
if (true) { // 进入 `tmp` 的作用域,TDZ 开始 // `tmp` 未被初始化: assert.throws(() => (tmp = 'abc'), ReferenceError); assert.throws(() => console.log(tmp), ReferenceError); let tmp; // TDZ 结束 assert.equal(tmp, undefined); }
下一个例子表明临时死区只是 暂时的 (与时间有关):
暂时的
if (true) { // 进入 `myVar` 作用域,TDZ 开始 const func = () => { console.log(myVar); // 稍后执行 }; // 我们在 TDZ 中: // 访问 `myVar` 造成 `ReferenceError` let myVar = 3; // TDZ 结束 func(); // OK,在 TDZ 外调用 }
即使 func() 位于myVar声明之前使用 myVar 变量,但我们也可以调用func(),前提是必须等到myVar的临时死区结束。
func()
myVar
函数声明总是在进入它的作用域时执行,不管它位于作用域的什么位置。这使得能够在函数foo()声明之前调用它:
foo()
assert.equal(foo(), 123); // ok,相等 function foo() { return 123; }
提前激活 foo()意味着楼上的代码等价于
function foo() { return 123; } assert.equal(foo(), 123);
如果用 const 或 let 声明一个函数,它就不会被提前激活:在下面的例子中,你只能在 bar() 声明后调用它。
const
let
bar()
assert.throws( () => bar(), // 声明前 ReferenceError); const bar = () => { return 123; }; assert.equal(bar(), 123); // 声明后
即使函数g()没有提前激活,也可以被前面的函数 f()(在同一作用域内)调用 - 只要遵守以下规则:f() 必须在声明 g() 之后调用
g()
f()
const f = () => g(); const g = () => 123; // g() 声明后调用 f(): assert.equal(f(), 123);
模块中的函数通常在模块执行完后调用。 因此,在模块中,很少需要担心函数的顺序。
最后,注意提前激活是怎样自动执行以维持上述规则的:当进入一个作用域时,在任何函数被调用前,所有的函数声明都会被先执行。
如果依赖于提前激活机制,在函数声明之前调用函数,那么需要注意的是它不会访问未提前激活的变量。如下:
funcDecl(); const MY_STR = 'abc'; function funcDecl() { console.log(MY_STR) }
上述会报错:
如果你在 MY_STR 声明之后调用 funcDecl() 就不会有问题。
MY_STR
funcDecl()
我们已经看到提前激活有一个陷阱,你可以在不使用它的情况下获得大部分好处。因此,最好避免提前激活。但我对此说法并非十分认同,如前所述,我经常使用函数声明,因为我喜欢它们的语法。
类声明不会提前激活:
assert.throws( () => new MyClass(), ReferenceError); class MyClass {} assert.equal(new MyClass() instanceof MyClass, true);
这是为什么? 考虑以下类声明:
class MyClass extends Object {}
extends是可选的,它的操作数是一个表达式。 因此,您可以这样做:
extends
const identity = x => x; class MyClass extends identity(Object) {}
计算这样的表达式必须在它被引用的地方完成,其它行为都会使人困惑。这解释了为什么类声明不提前激活。
var是在const和let之前声明变量的一种较老的方法。考虑下面的var声明。
var
var x = 123;
这个声明包含两个部分:
声明var x:与大多数其他声明一样,var声明变量的作用域是最内层的包围函数,而不是最内层的包围块。这样的变量在其作用域的开始时就已处于活动状态,并使用undefined初始化。
var x
赋值 x = 123 :赋值总是在适当位置执行。
x = 123
以下代码演示了 var :
function f() { // 部分提前激活: assert.equal(x, undefined); if (true) { var x = 123; // 赋值已经执行 assert.equal(x, 123); } // 作用域为函数作用域,非块级作用域。 assert.equal(x, 123); }
原文:https://2ality.com/2019/05/unpacking-hoisting.html
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
https://github.com/qq449245884/xiaozhi
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
为了保证可读性,本文采用意译而非直译。
引用 ES6 规范作者 Allen Wirfs-Brock一条最近的推特:
受 Allen 启发,本文提出了一种不同的方法来描述变量声明。
1. 声明:作用域与激活
可以将声明分为两个方面:
下表总结了不同声明的方式如何处理上述两个方面。
**“Duplicates”**描述是否可以在同一作用域内声明两次。
**“Global prop.”**表示一个在 script 中的声明,当全局作用域中被执行时,是否会向全局对象添加属性。
TDZ
表示暂时性死区(稍后解释)。 函数声明在严格模式下是块作用域的(例如在模块内部),但在非严格模式下是函数作用域。2. const 和 let :暂时性死区
对于JavaScript,TC39 需要决定如果在声明之前访问其直接作用域中的常量会发生什么:
主要有两种种情况:
打印
undefined
报错
第一种不会出现,因为 x 是一个常量,如果打印
undefined
,在声明前和声明后它将拥有不同的值,x 就不是常量了。let 和 const 都会出现第二种情况,就是会报错。进入变量作用域与执行声明之间的这段时间被称为该变量的 临时死区(TDZ):
在临时死区中,变量被认为是未初始化的(就像它有一个特殊的值一样)。
如果访问未初始化的变量,将得到
ReferenceError
错误。一旦执行到变量声明,该变量将被设置为初始化器的值(通过赋值符号指定),如果没有初始化,则为
undefined
。以下代码说明了临时死区:
下一个例子表明临时死区只是
暂时的
(与时间有关):即使
func()
位于myVar
声明之前使用myVar
变量,但我们也可以调用func(),前提是必须等到myVar
的临时死区结束。函数声明与提前激活
函数声明总是在进入它的作用域时执行,不管它位于作用域的什么位置。这使得能够在函数
foo()
声明之前调用它:提前激活
foo()
意味着楼上的代码等价于如果用
const
或let
声明一个函数,它就不会被提前激活:在下面的例子中,你只能在bar()
声明后调用它。在没有提前激活的情况下提前调用
即使函数
g()
没有提前激活,也可以被前面的函数f()
(在同一作用域内)调用 - 只要遵守以下规则:f()
必须在声明g()
之后调用模块中的函数通常在模块执行完后调用。 因此,在模块中,很少需要担心函数的顺序。
最后,注意提前激活是怎样自动执行以维持上述规则的:当进入一个作用域时,在任何函数被调用前,所有的函数声明都会被先执行。
提前激活的一个陷阱
如果依赖于提前激活机制,在函数声明之前调用函数,那么需要注意的是它不会访问未提前激活的变量。如下:
上述会报错:
如果你在
MY_STR
声明之后调用funcDecl()
就不会有问题。提前激活的利弊
我们已经看到提前激活有一个陷阱,你可以在不使用它的情况下获得大部分好处。因此,最好避免提前激活。但我对此说法并非十分认同,如前所述,我经常使用函数声明,因为我喜欢它们的语法。
类声明不会提前激活
类声明不会提前激活:
这是为什么? 考虑以下类声明:
extends
是可选的,它的操作数是一个表达式。 因此,您可以这样做:计算这样的表达式必须在它被引用的地方完成,其它行为都会使人困惑。这解释了为什么类声明不提前激活。
var :变量提升(部分提前激活)
var
是在const
和let
之前声明变量的一种较老的方法。考虑下面的var
声明。这个声明包含两个部分:
声明
var x
:与大多数其他声明一样,var
声明变量的作用域是最内层的包围函数,而不是最内层的包围块。这样的变量在其作用域的开始时就已处于活动状态,并使用undefined
初始化。赋值
x = 123
:赋值总是在适当位置执行。以下代码演示了
var
:原文:https://2ality.com/2019/05/unpacking-hoisting.html
交流
干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复福利,即可看到福利,你懂的。
The text was updated successfully, but these errors were encountered: