Day17 - let是否会造成变量提升
su37josephxia opened this issue · 28 comments
总结:
let不会造成变量提升。
解释:
1.代码中,在定义之前使用变量x,输出的是错误,而我们知道,var定义变量导致的变量提升此时会输出‘x is undefined’.
2.let定义的变量只是在局部作用域形成暂时性死区,若在该局部作用域定义了y变量,局部作用域中使用访问都是该作用域中定义的这个变量y.
let 不会造成变量提升
javascrpit 变量作用域一般都是词法作用域,也就是在函数写好之后,在编译器的第一个阶段词法阶段就已经确定好了作用域。let写在了函数内部,也就是说在代码写好后,函数内部的作用域就已经被确定了。变量在函数内部被定义,所以变量的作用域是在函数内部的。根据JS作用域链的原则,函数在执行的时候会现在当前作用域下寻找变量,其实这个变量是在作用域下被定义的,所以默认会认为当前作用域下的变量是函数访问的变量。但是函数的定义又在使用之后,所以导致了找不到该变量,这也是造成暂时性死区的原因。总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”
首先上述代码不会输出 Uncaught ReferenceError: x is not defined
而是Uncaught ReferenceError: Cannot access 'x' before initialization
。正常情况下,我们使用一个未被声明的变量,会报错提示该变量未定义,而且我们通常在代码中也是通过typeof(x)检测一个未被声明的变量,如果其未被声明,则结果为undefind,并不会报错,但是如果这个变量用let或者const声明并且在其声明其使用,则会引发暂时性死区问题。
暂时性死区
在词法分析阶段,就已经知道未来存在某某变量,比如说这里的x,而在x变量定义之前去使用它就不允许,就报错:Uncaught ReferenceError: Cannot access 'x' before initialization
。这是因为本质上浏览器去解析这些JS代码时,这些代码都是文本(字符串),最开始是声明了JS的格式是【Content-Type:application/javascript】,浏览器会按照这个格式解析代码。
- “词法解析”阶段:目标生成“AST词法解析树”。在这个阶段基于let或const等声明的变量,已经明确了,未来此上下文中,必然会存在这些变量,但是并没有声明;
- 代码执行阶段,如果出现在具体声明之前的代码使用这些变量,就会报出错误。
结论
不会
原因
变量的声明需要经过binding和initialize两步,完成后才能正常使用。
对于let声明,binding结束并没有赋予默认值。 所以binding完成了,并不代表initial完成,initial出错并不代表,binding会回滚。
但对于var声明,默认是赋予undefine 的值
对于变量表达式,规范有要求变量必须初始化才能使用。
结论,会变量提升,只是和var的表现不一样,因为TDZ的作用,只是抛出了ReferenceError错误
当程序的控制流程在新的作用域(module, function或block作用域)进行实例化时,在此作用域中的用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,也就是对声明语句进行求值运算,所以是不能被访问的,访问就会抛出错误。所以在这运行流程一进入作用域创建变量,到变量开始可被访问之间的一段时间,就称之为TDZ(暂时死区)。
以上面解说来看,以let/const声明的变量,的确也是有提升(hoist)的作用。这个是很容易被误解的地方,实际上以let/const声明的变量也是会有提升(hoist)的作用。提升是JS语言中对于变量声明的基本特性,只是因为TDZ的作用,并不会像使用var来声明变量,只是会得到undefined而已,现在则是会直接抛出ReferenceError错误,而且很明显的这是一个在运行期间才会出现的错误。
let是会造成变量提升的
-
不定义x
(function() {
console.log(x);//ReferenceError: x is not defined
})()
报错提示:x压根没被定义。 -
定义x但不赋值
(function() {
let x;
console.log(x);//undefined
})()
运行正常,但x的值被赋undefined -
定义x但放在使用后边
(function() {
console.log(x);//ReferenceError: Cannot access 'x' before initialization
let x = 3;
})()
报错提示:x在初始化前无法访问,这里注意与1中报错的区别不是x没有被定义,这里只是说在x被初始化赋值之前是无法使用的,这就说明console.log(x)时是可以访问到x的,只不过我们没有权利访问而已,所以let依然是将变量提升了,只不过在let定义之前一切访问该变量的行为是不被允许的。
回答
不会造成变量提升
展开
let
声明会在当前作用域中创建一个暂时性死区,如果在当前作用域中提前访问变量,会报错
暂时性死区
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
参考
https://es6.ruanyifeng.com/#docs/let#%E6%9A%82%E6%97%B6%E6%80%A7%E6%AD%BB%E5%8C%BA
答:看定义的角度
从变量能否使用的角度:不存在。
从语言解析的角度:存在。
由于TDZ,我们常规使用let定义变量不能在定义前使用,输出报错是
VM539:1 Uncaught ReferenceError: Cannot access 'a' before initialization
不能在定义前使用变量
而直接输出不定义,则提示
VM552:1 Uncaught ReferenceError: a is not defined
a未定义,因此实际上,在编译的时候,代码其实是知道已经存在a这个变量的,因此是会提升。
会造成变量提升。变量提升是 JavaScript 的设计造成的,是语言的特性。let 和 const 只是增加了暂时性死区,并没有改变变量提升的本质。这里变量提升理解为在代码执行到声明语句 let ,const ,var 之前,这个变量就已经存在了。变量分为创建,初始化,赋值三个阶段,变量会被收集到词法环境里,var 会在创建的时候就初始化为undefined,而 let 在创建的时候没有进行初始化undefined,而是执行到赋值的时候同时进行初始化和赋值。而没有初始化的变量读取的时候会报错 "无法在初始化之前使用" , 这个阶段就是 let 的暂时性死区。
var k = 1
;(function () {
k = 2 // ReferenceError: Cannot access 'k' before initialization
console.log(k)
let k
console.log(k)
})()
console.log(k)
- let不会造成变量提升
- 不能在let声明前去使用变量
let 的创建过程被提升了,但是初始化没有提升
生命周期中的声明阶段和我们通常所说的变量声明是不同的。简单来说,引擎处理变量声明需要经历三个步骤:注册,初始化,赋值
而let的生命周期如下
注册阶段
引用变量跑抛出错误 未初始化状态
let a 初始化阶段
a===undefined。 初始化状态
a===‘value’ 赋值状态
所以,声明提升是变量在作用域顶层处注册和初始化耦合的结果。然而let的注册和初始化阶段是解耦的,这就使得声明提升对let无效。这种解耦造成了变量无法被引用的时间死区
-
其实,let和const是会被提升的,准确的说是创建被提升了,但是初始化没有被提升。
-
let声明变量分为三部分:1.创建,2.初始化,3.赋值。
-
所以没有初始化时,赋值就会报错
if (true) {
a = 2; // ReferenceError;
let a = 3;
}
为了理解方便,可以将上述代码拆分成如下几步:
if (true) {
// 此时a的创建已经被提升到了if代码块内的顶部(TDZ的开始)
a = 2; // 此时对a进行赋值,由于a仅仅被创建,还没有初始化,所以会报错ReferenceError
let a; // 完成a的初始化(TDZ的结束)
a = 3; // 完成对a的赋值操作。
}
而let从创建被提升到初始化这中间的部分,就是我们平常所说的暂时性死区(TDZ),即在暂时性死区中该变量都是不可用的。
不会被提升,let声明的变量会存在暂时性死区,在声明之前调用会抛出错误,如果外层作用域中有同名变量,也不会去访问,因为js 使用的是词法作用域,在词法分析阶段会绑定访问的变量
如果变量提升意味着预解析(pre parser), let
是有变量提升的,不然不会存在暂时性死区。
如果变量提升意味着把变量提升到作用域顶部,let
是没有变量提升的,因为我们无法在 let
声明前使用该变量。
let关键字跟var很相似,但他的作用域是块级的,严格来讲,let在JavaScript运行时中也会被提升,但由于暂时性死区的缘故,实际上不能在声明之前使用let变量,因此,let的提升跟var是不一样的
要搞清楚提升的本质,JS 变量的「创建create、初始化initialize 和赋值assign」
let 声明的 创建、初始化 和赋值 过程
{ let s = 1 s = 2 }
找到所有用 let 声明的变量,在环境中「创建」这些变量
开始执行代码此时没有初始化
执行 s = 1,将 s 「初始化」为 1 如果代码是 let s,就将 s 初始化为 undefined
执行 s = 2,对 s 进行「赋值」
在let声明前执行 log 时 s 还没「初始化」,所以不能使用(也就是所谓的暂时死区)
let 的「创建」过程被提升了,但是初始化没有提升。
let会造成变量提升。
在ECMA262文档里面对Let 和 Const 的介绍中明确的指出,let 会有变量的提升,只是没有进行initial赋值,直到运行到 声明语句的地方才会进行赋值操作。所以在赋值语句之前虽然被声明却不能进行访问,强行访问会报错,这也就形成了我们常说的暂时性死区。
- let,const若定义为变量提升,则表明他预解析提升,但并未赋值,而var赋值了undefined,所以let在定义之前使用会报错,var则会输出undefined
- 若定义为狭义的是否在作用域顶端可用,则未提升,在顶部未定义let的地方使用会报错
let会造成变量提升。
一个比较从分的理由是,从块顶部到该变量的初始化语句,这块区域叫做 TDZ(临时死区),如果你在 TDZ 内使用该变量,JS 就会报 ReferenceError的错误;
let造成的暂时性死区的原因是因为:
- let 的「创建」过程被提升了,但是初始化没有提升。
- var 的「创建」和「初始化」都被提升了。
- function 的「创建」「初始化」和「赋值」都被提升了。
- const 和 let 只有一个区别,那就是 const 只有「创建」和「初始化」,没有「赋值」过程。
- 暂时性死区,就是不能在初始化之前,使用变量。
然后就导致了上面那个问题
var 与 let 的 提升是不一样的。严格来讲 let 的提升只是提升了创建阶段,此时还不能被访问。如果冒然的访问会抛出错误 ReferenceError。而 var 的提升由于var的创建阶段与初始化阶段合二为一了直接进入赋值阶段可是访问的。
对于js引擎预编译来说,let声明会提升声明,用来开辟内存空间,用来判断是否重名或者是否未初始化调用该变量, 还有用来实现暂时性死区
对于我们来使用来说,let不会造成变量提升,正因为js引擎实现了暂时性死区,所以不会被提升
是否提升是对提升在哪里阶段的不同理解
是有提升的,准确来说,
let 的注册过程被提升了,但是初始化没有提升,所以在声明前使用let 的变量会报错 is not defined。这也就形成了暂时性死区。
我们声明函数s
function s( ) {
let a=1;
let a=2;
}
声明函数s的时候,即使没有执行函数内部表达式,依旧会报错重复声明。
说明js引擎在let a执行时就已经注意到了let ,不然不会在没执行的情况下报出错误,这也是注册过程提升的一种表现。
但虽然提升,实际是不能在声明前使用该变量的。
let会造成变量提升
let
if(true) { console.log(name); // Uncaught ReferenceError: Cannot access 'name' before initialization; let name = 'gsk'; console.log(name); // gsk } console.log(name); // undefined;
不加let
if(true) { console.log(name); // undefined; if块中没有会向上找到全局,默认未声明的的变量属于window,所以是undefined;
-
Javascript引擎在执行预编的时候也会注意到在if块中的let声明,但是在声明之前不能以任何方式来引用该变量,这个不能引用的瞬间,即console.log到let name这个执行瞬间被称为暂时性死区;
-
虽然有暂时性死区的存在导致我们访问不了let声明之前的变量,但是我们比较let和不加let两块,let块中未声明之前报的错误是不能得到一个未初始化的值,那说明已经有了变量声明的提升,只是没有初始化而已;
let会造成变量提升
首先明确一下变量提升的概念:变量提升是指当我们在执行到定义变量的这行代码之前,该变量就已经在内存中存在了
如果访问一个未定义的变量时,所报的错是 变量未定义
,说明在内存中找不到该变量
function a () {
console.log(c)
} // Uncaught ReferenceError: c is not defined
at a (<anonymous>:1:28)
at <anonymous>:1:1
如果在 let 定义变量之前访问该变量,所报的错是 不能在初始化前访问该变量
,说明此时该变量已经存在内存中了
function b () {
console.log(c);
let c = 1
} // Uncaught ReferenceError: Cannot access 'c' before initialization
at b (<anonymous>:1:28)
at <anonymous>:1:1
所以,从结果来看,在使用 let 定义变量确实存在变量提升,只是由于暂时性死区的原因,我们无法访问到当前的变量
let不会造成变量提升,但是js引擎在let声明语句之前是能感知到它的存在的,只是无法正常访问而已。
首先我们得明确变量提升的含义。变量提升是指,变量在声明之前可以访问并且不报错。对于var申明的变量来说,这个过程是因为执行上下文在创建的过程中会扫描变量,var创建的变量放到变量环境并给一个初始化值undefined,所以我们可以在申明语句之前访问这个变量,并且值为undefined。
console.log(a); undefined
var a = 0;
那么对于let和const来说,在执行上下文创建阶段,也会被扫描到,但是会被放入词法环境,并且不会初始化。而且这时候很重的一点,从块级作用域的开始到这条申明语句的区域,会成为一个“暂时性死区”(temporal dead zone),如果在这个区域内访问该变量,会报referrenceError。这也就导致我们不能在申明语句之前去正常访问一个let和const申明的变量。
console.log(a); // referrenceError: can't access xxx before initialization
let a = 0;
但是这里很有趣的一点就是,这里报的错是referrenceError: can't access xxx before initialization而不是 referrenceError: xx is not defined。这说明此时js引擎是能直到这个变量的存在的,但由于该变量未初始化,导致这里是暂时性死区,所以报了这个不一样的错。
通常意义上,我们所说的提升指的是这个变量是否被初始化了;基于这种理解,var 定义的变量存在提升,而let 定义的变量不存在提升,所以在let 给变量赋值之前,变量都是不能够被使用的,这个位置也被称为let定义变量的暂时性死区;
但是在从变量的创建到赋值再到被引用的过程中,var 定义的变量的创建和赋值都会被提升到其被引用位置的前面,所以在未执行到其被创建的代码行的位置时,打印的变量的值是undefined;而let定义的变量只有创建过程被提升到其被引用的位置的前面,而赋值过程没有同时被提升,因此在未执行到其被创建的代码行的位置时,打印变量的值会报语法错误,并表示其在被初始化之前是不能够被引用的,这也就是暂时性死区的由来;但是let 定义的变量的创建过程的提升,只存在于块级作用域之内,当其在被定义的块级作用域之外引用的话,就会报未定义;
es6 文档中所提到的 let 不存在变量提升,也只是基于变量的初始化,而不是变量的创建。