var ,未定义,let
- var
console.log(a)
var a = 10
结果:
原因:上面代码在编译阶段变成了下面代码,可以看到var a = 10在编译阶段变成了两句话,一句为声明变量:var a,一句为给变量赋值: a=10。而声明变量提升到了当前作用域(全局作用域)的最前面,赋值变量的语句留在原地。由于变量a在输出前只声明而未定义,所以会输出undefined,而不是报错,也不是输出10。
var a
console.log(a)
a = 10
- 未定义
console.log(a)
a = 10
结果:
原因:没有使用 var 关键字定义的全局变量没有预编译过程,所以如果把变量的使用放在定义之前则会报错
如果声明变量时没有使用 var 关键字,则该变量一定为全局变量。到那时在调用的时候有以下区别:
- 如果时声明在方法外的全局变量,在方法内可以直接使用;
- 如果该全局变量时声明在方法内部的,则在方法外使用该变量时需要先调用方法,告知系统声明了全局变量才能使用。
把 a 的顺序掉一下就可以运行正确
a = 10
console.log(a)
- let
console.log(a)
let a = 10
原因:let/const不会发生发生变量提升,在解析代码时,JavaScript引擎也会注意出现在块后面的let声明,只不过在此之前不饿能以任何方法来引用未声明的变量。在let声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出ReferenceError错误。(JavaScript高级程序设计第四版P26)
要注意的是let与const不仅不会变量提升,而且还会把变量绑定到块作用域。例如
if(true){
let a = 10;
}
console.log(a) //报错,形成块作用域
通过let声明的变量,不经没有变量提升,而且还形成了一个if{}块作用域。全局作用域访问不到a的值
词法作用域
var a = 10
function foo() {
console.log(a)
}
foo(a) // foo运行在全局作用域 输出10
function foo2() {
var a = 100
foo() // foo运行在foo2包裹的作用域,输出10,并不会输出离自己近的100
}
foo2()
在代码运行前(也就是编译阶段),作用域都已经确定好了。foo永远在全局作用域下,不管他在哪里运行,foo都会输出全局作用域下的a
函数提升
foo()
function foo() {
console.log(1) //输出1,而不是报错
}
编译后
function foo() {
console.log(1)
}
foo()
整个函数都会提升
但要注意的是下面这个例子
foo()
var foo = function() {
console.log(1)//报错:Uncaught TypeError: foo is not a function
}
编译后
var foo
foo()
foo = function(){
console.log(1)
}
这一部分和变量提升的规则一样,可以看到由于将函数赋值给了一个变量,所以不会发生函数提升,而是发生变量提升。
重复声明/函数优先
foo()
function foo() {
console.log(1) //输出1
}
var foo
这个例子中,变量foo被声明了两次,一次被声明为函数,一次被声明为未定义的变量。且这两次声明都是在全局作用域下,在同一个作用域下,变量名与函数名都相同的情况下(重复声明),会执行函数优先原则,忽略变量。
所以实际上编译的情况是这样的
function foo(){
console.log(1)
}
// var foo 函数优先原则,这一句在编译的时候被注释
foo() // 输出1
小结
- 编译阶段确定作用域并发生变量提升
- 变量会发生变量提升,但变量的赋值会留在原地
- let/const不会发生变量提升,而且还会形成块作用域
- 重复声明下,遵循函数有优先原则
参考文章:https://juejin.cn/post/6987661731858219016