let a=1;function foo (){console.log('a:',a);let a = 2;};foo();为什么报错?
发布于 6 年前 作者 JarvisQJ 5041 次浏览 来自 问答

如题,代码执行后报错:a is not defined 顺带研究下var和let的区别:var a=1;function foo (){console.log(‘a:’,a);var a = 2;};foo();为什么是打印a:undefined

17 回复

@captainblue2013 大佬你有时间能不能用三个字说一下为什么,而不是在这用五个字指点江山一番

变量申明提升,作用域嵌套

@xpine 如果是声明提升,应该是打印a:undefined,而不是报错吧

执行foo()时,其中的console中引用的变量a,会先在foo函数体作用域中查找定义,由于js var有变量提升的这一特点,相当于先执行var a; 再执行打印的时候找到了a的定义,再最后才给a赋值,打印的时候a时undefined

@vitozyf @IEfucker 首先感谢两位的解答。 我本人不明白的点,更具体一下是下面两行的区别: var a=1;function foo (){console.log(‘a:’,a);var a = 2;};foo();//a: undefined let a=1;function foo (){console.log(‘a:’,a);let a = 2;};foo();//ReferenceError: a is not defined 答案是,有关let有个暂时性死区(temporal dead zone)的概念。 我们都知道(如果不知道就参考《你不知道的JavaScript(上卷)》第七页),var a=2;会在编译阶段先声明a,并初始化为undefined,正因为这个JS存在变量提升问题,所以前一段代码会打印a: undefined,而不是报错。 对于let还有个不会有变量提升的说法,不过这个说法不对。第二段代码,如果完全没有提升,应该打印a:2,之所以报错是因为,let a = 2;编译阶段仍然声明了a变量(因为显然上一级作用域中是有a变量的,并且值为1),只是没有初始化。所以既没有打印a:1,也没有打印a:undefined,而是报错。 这个答案有些地方是自己推测的,分析如有不对,欢迎指正。

@JarvisQJ mdn上也有这个例子,英文版比较新

@JarvisQJ 以下链接有关于这一部分的说明: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone var和let在声明变量的时候,都有“提升”这个概念。 只不过var提升后的初始值为undefined,提前引用没什么问题;let提升后不具有初始值,提前引用会报错,只有执行到声明语句的时候才具有值。

let a=1;function foo (){console.log('a:',a);let a = 2;};foo();

改成

let a=1;function foo (){console.log('a:',a);};foo();

直接输出a:1。 反过来也可以说明一些问题。

知识点:作用域、变量提升、函数 首先函数声明产生函数作用域 然后我们在使用变量的时候会沿着作用域链去查找这个变量;作用域链的查找方向是由里到外,如果找到就结束,返回值 变量提升其实是一种说法,本质是在“词法阶段”,需要先给变量分配内存 实例:函数内部声明了一个a变量,被提升,所以查找使用a的时候会先在当前函数作用域查找a,但是此时a并没有赋值所以,是undefined,结束。

而let在其中又混入的块级作用域的概念,我觉得let没什么可说的。

@xiaobai2017666 这个帖子的核心应该是,let不会有变量提升是不对的,而且我们平常见到的暂时性死区的概念解说也不够完整。let 声明的变量仍然会提升到作用域前端,只是不会像var声明的变量提升的同时还会初始化为undefined。

@JarvisQJ var a=1; function foo (){ var a; console.log(a); a = 2; }; foo();

"对于let还有个不会有变量提升的说法,不过这个说法不对。第二段代码,如果完全没有提升,应该打印a:2,之所以报错是因为,let a = 2;编译阶段仍然声明了a变量(因为显然上一级作用域中是有a变量的,并且值为1),只是没有初始化。所以既没有打印a:1,也没有打印a:undefined,而是报错。“ 我觉得你这里分析得有问题,let不会产生变量提升,至于报错的原因是,虽然外围作用域声明了a(let a=1),但是,foo里面,console.log(a)之后有一句"let a =2”,这会产生一个暂时性死区(TDZ),导致foo作用域内部已经不接受外部的a了,整个内部作用域,a已经被锁定,由里面的声明语句(let a=2)决定,console.log(‘a:’,a)这句因为LHS找不到a,所以报错。

@iflet 这里确实是我个人的说法,就看怎么去解释变量提升了。暂时性死区的形成,根本原因是,编译阶段let a=2;会重新开辟一个变量名为a的空间,但是不会初始化,所以无法访问。这个解释你赞同吗?对于变量提升概念本身的定义,我觉不是那么重要。

作用域 提升,hosting

回到顶部