var
执行前会在全局 / 函数作用域中作预编译
GO | AO | |
---|---|---|
1 | 形参赋值 | |
2 | 找 var 变量 / function = undefined | |
3 | function 赋值函数体 | |
4 | 执行 |
所以 var 会声明提升
var a = b = 3
let 与 const
let 是变量定义,const 是常量定义
const 一旦定义必需赋值,值不能被更改
const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。 所以对于引用类型是能修改其中的对象属性/方法和数组的元素。
块级作用域
if(1) { /* 块级作用域 */ }
for(; 1;) { /* 块级作用域 */ }
{ /* 块级作用域 */ }
- let / const 在同一作用域(全局、函数、块级)下不能重复声明
- let / cons 不会被提升,会产生一个暂时性死区 TDZ Temporal Dead Zone
- let / cons 只能在当前作用域下生效
在块级作用域中函数声明
在ES5中函数声明只能在顶层作用域或函数作用域,在块级作用域函数声明是不合法,但浏览器都能解析,在ES6中做了妥协,认为合法但并不推荐。不然在预编译中不同浏览器有着不同的表现形式,应该使用函数表达的方式替代函数声明的方式,以得到标准的预编译结果。
var a = 1;
if (true) {
a = 2;
console.log(a, window.a); //2 1
function a() { }
console.log(a, window.a); //2 2
a = 21;
console.log(a, window.a); //21 2
}
console.log(a, window.a); //2 2
所以这段代码在现代浏览器与 IE 浏览器会有不同的结果
# 现代浏览器
2 1
2 2
21 2
2 2
# IE
2 function a() { }
2 function a() { }
21 function a() { }
function a() { } function a() { }
:::info
虽然在 ES3 没有块级作用域的概念,但是浏览器却有其行为。现代浏览器在预编译过程中,function a()
的声明会提升到 if 代码块中的最顶端。此时在 if 代码块中,存在两个 a
变量。分别在栈内存的全局变量 a
和在 if 代码块的 function a
。因为 function a
为引用值,所以实际值保存在堆内存中:
:::
:::info
当执行到 line3 a=2
时,因为函数声明的提升,在 if 代码块已存在局部变量 a, 所以是将栈内存中本指向 a -> function a()
的变量 a 的值改为 2,不影响外部的变量 a。
当执行到 line5 function a(){}
时,理论上预编译过程中函数声明提升,function a
的声明已经提升了,此处不执行。但是 JS 引擎执行到此处时,会看到这里有一个 function a
为引用值,会去找 a 实现映射关系,当看到栈内存有两个 a 时,会将两个 a 形成映射关系,即 window.a 会与 a 作映射。window.a = a = 2(此处为浏览器机制问题)
当执行到 line7 a=21
时,改变的是内部的 a,与 window.a 无关。
在 if 块外面时,a 就是 window.a,相互作映射。 :::
根据 ES5/ ES6 推荐的要求,把 函数声明 更改为 函数表达式
var a = 1;
if (true) {
a = 2;
console.log(a, window.a); //2 1
var a = function () { };
console.log(a, window.a); //2 2
a = 21;
console.log(a, window.a); //21 2
}
console.log(a, window.a); //2 2
其结果就与正常的预编译行为一致
2 2
function a() { } function a() { }
21 21
21 21