众所周知, 在 ES6 之前, JavaScript 是没有块级作用域的, 如下图所示:
学过其他语言的同学肯定有点诧异, 为什么会这样呢? 因为 js 还是不同于其他语言的, 在 ES5 中, 只有全局作用域和函数作用域, 并没有块作用域, 当然我们可以实现块作用域的功能看下面代码:
在这段段代码中, 我们使用立即执行函数 (IIFE) 创建了一个局部函数来模仿块级作用域在 ES5 时代, JavaScript 的作用域只有用全局作用域和局部作用域的说法到了 ES6 时代, 块级作用域的登场
一关于 ES5 时代
1. 变量提升
说到 js 的变量提升, 就不得不说一下 js 的词法分析
总所周知 js 代码自上而下执行, 但
是在
js
代码执行前, 会先进行词法分析所以 js 运行要分为词法分析和程序执行两个阶段
js 词法分析主要分为 3 个步骤:
1. 分析形参: 如果函数有形参, 则给当前活动对象增加形参属性, 默认为 undefined
2. 分析变量声明: 如果有类似 var a 之类的声明, 若没有该属性则增加属性, 若已存在则不做操作默认为 undefined 变量的赋值在执行阶段才进行, 即执行到该变量的时候才有 a = 11
3. 分析函数声明: 类似 function a(){}, 若当前活动对象没有该属性则新增否则重写该属性为方法 a
如图所示, 在这段代码中, 按照一般的逻辑, 第一个 console.log 会报错为 a is not defined
但是事实上, 根据 js 词法分析的第二步, var a 这个声明会被提前到代码的顶部但是 a=1 这个赋值却不会, 所以这段函数正确的步骤为:
这就是所谓的变量提升
2. 函数提升
在 js 中, 我们常见的常见函数的创建方式有三种函数构造式(不推荐使用, 此处不做分析), 函数声明式和函数表达式下面第一行的代码为函数声明式, 第二个为函数表达式
- function fn1(){}
- var fn2=function(){};
在以上两种创建方式中, 函数表达式的常见方式与普通变量 var a=1 的创建方式相同, 因此它也会受变量提升的影响而另一种, 函数声明式会存在函数提升的情况, 并且函数提升比变量提升优先级高! 因此分别在创建前打印上述 fn1 和 fn2 得到以下结果:
根据变量提升和函数提升的分析得:
因此, fn1 是作为函数声明被提升到最前面, 而 fn2 先被作为变量创建并提到顶部, 然后在相应位置被赋值的
3. 作用域链
我们在上面说到 js 在 ES5 时代没有块级作用域, 只有局部作用域和全局作用域作用域链用于保证对执行环境有权访问的所有变量和函数的有序访问看下图, 按照一般的思路, b 输出为 3 这里就用到了作用域链的知识
当函数在执行的过程中, 先从自己内部找变量如果找不到, 再从创建当前函数所在的作用域去找, 以此往上因此我们分析调整函数得到下图在这个函数中给 fn 赋值为 fn1(), 即 fn1 的返回值 fn2 在运行 fn 时, 即运行 fn2 此时 fn2 的内部是没用 b 的, 因此我们要去 fn2 的创建环境中找 b=2 所以此处输出为 2.
二 ES6 时代
1.let 和 const 的来临
首先 let 和
const 的作用和 var 是相同的, 但是
都是不存在提升, 声明的都是块级标识符大括号内部即形成块级作用域, 此时 let 声明的 a 在块级作用域外是访问调用不到的
- {
- let a=1;
- }
- console.log(a)// 报错
并且 let 和 const 都禁止重复声明的:
- var a = 30;
- var message = 2;
- // 这两条都会抛出语法错误
- let a = 40;
- const message = 1;
每个 const 声明的常量必须进行初始化, const 定义的常量不能修改, 但是用 const 声明的对象可以修改值, 即
- const a; // 语法错误: 常量未初始化
- const b = {
- name: 'a'
- };
- b.name = 'b'; // 可以修改
- // SyntaxError: "person" is read-only
- b = {
- name: 'c
- }
let 和 const 声明不会像 var 一样提升到作用域顶部, 如果在声明之前访问这些变量, 会形成所谓的临时死区 (Temporal Dead Zone) 即使是相对安全的 typeof 操作符也会触发引用错误用 let 来举例(const 也一样):
- console.log(typeof value);
- let value = 1;
在上面的代码中, let 无变量提升的作用, 即在 let value=1 之前的代码出现临时死区(Temporal Dead Zone), 即报错 value is not defined 而不是 undefined
- console.log(typeof a);
- if(1){
- let a=1;
- }
而在上述的代码中, let 在块级作用域中, 因此在全局作用域中不存在所谓的死区, 因此此处打印出 undefined
2. 全局块作用域绑定
在全局作用域中, var 声明的变量会成为全局对象 (浏览器环境中的 window) 的属性这意味着 var 很可能会无意中覆盖一个已经存在的全局变量
- var Test=1;
- window.Test === Test; // true
如果在全局作用域中使用 let 或 const, 会在全局作用域下创建一个新的绑定, 但该绑定不会添加为全局对象的属性换句话说, 用 let 或 const 不能覆盖全局变量, 而只能遮蔽它
- const foo = 1;
- window.foo = 2;
- console.log(foo); // 1
- console.log(window.foo); // 2
在实际开发中, let 实际上与我们所用的的
var 的用法是一样的
, 直接替换符合逻辑对于需要些保护的变量, 我们要使用 const 默认使用 const, 只有确实需要改变变量的值时使用 let
来源: https://www.cnblogs.com/lunlunshiwo/p/8461426.html