var

执行前会在全局 / 函数作用域中作预编译

GO AO
1 形参赋值
2 找 var 变量 / function = undefined
3 function 赋值函数体
4 执行

所以 var 会声明提升

var a = b = 3

b = 3;
var a = b;

let 与 const

let 是变量定义,const 是常量定义
const 一旦定义必需赋值,值不能被更改

const 实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。 所以对于引用类型是能修改其中的对象属性/方法和数组的元素。

块级作用域

  1. if(1) { /* 块级作用域 */ }
  2. for(; 1;) { /* 块级作用域 */ }
  3. { /* 块级作用域 */ }
  1. let / const 在同一作用域(全局、函数、块级)下不能重复声明
  2. let / cons 不会被提升,会产生一个暂时性死区 TDZ Temporal Dead Zone
  3. let / cons 只能在当前作用域下生效

    在块级作用域中函数声明

    在ES5中函数声明只能在顶层作用域或函数作用域,在块级作用域函数声明是不合法,但浏览器都能解析,在ES6中做了妥协,认为合法但并不推荐。不然在预编译中不同浏览器有着不同的表现形式,应该使用函数表达的方式替代函数声明的方式,以得到标准的预编译结果。

  1. var a = 1;
  2. if (true) {
  3. a = 2;
  4. console.log(a, window.a); //2 1
  5. function a() { }
  6. console.log(a, window.a); //2 2
  7. a = 21;
  8. console.log(a, window.a); //21 2
  9. }
  10. console.log(a, window.a); //2 2

所以这段代码在现代浏览器与 IE 浏览器会有不同的结果

  1. # 现代浏览器
  2. 2 1
  3. 2 2
  4. 21 2
  5. 2 2
  6. # IE
  7. 2 function a() { }
  8. 2 function a() { }
  9. 21 function a() { }
  10. 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