1. ES6之前,只有使用`var`来进行变量的声明,而在ES6 不仅增加了`let``const`两个关键字,而且还让这两个关键字压倒性地超越`var`成为首选。

6.1 var

1. 使用 var 的函数作用域声明

在使用 var 声明变量时,变量会被自动添加到最接近的上下文。在函数中,最接近的上下文就是函数的局部上下文。在 with 语句中,最接近的上下文也是函数上下文。如果变量未经声明就被初始化了,那么它就会自动被添加到全局上下文

  1. function add(num1, num2) {
  2. var sum = num1 + num2; //加上var声明,变量为局部变量
  3. return sum
  4. }
  5. let result = add(10, 20); //30
  6. console.log(sum); // 报错:sum 在这里不是有效变量
  • 在此区域内,var声明的变量根据所在的{}自动分配为该范围内变量,即局部变量
  • 局部变量在外部时销毁,无法使用
  • var所谓的局部概率仅限于函数!!!

当去掉var时,定义的变量虽然在函数内,但会转换成全局变量,可以在全局访问

  1. function add(num1, num2) {
  2. sum = num1 + num2;
  3. return sum;
  4. }
  5. let result = add(10, 20); // 正常输出的结果,由sum返回给函数在外部
  6. console.log(sum); // 30 去除var后变为全局变量

2. var声明变量提升

  • var 声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫做“提升(hoisting”
  • 提升让同一作用域中的代码不必考虑变量是否已经声明就可以直接使用。
  • 可是在实践中,提升也会导致合法却奇怪的现象,即在变量声明之前使用变量。

    1. console.log(nun);
    2. var num = 10;
    3. // 变量提升情况下
    4. var num;
    5. console.log(num);
    6. num = 10;
  • 虽然声明的变量提升到了当前范围内的最顶上,但是变量赋值依然在原处

  • 此时在执行到console.log(num);时,打印输出的时 undefined
  • 通过在声明致歉打印变量,可以验证变量会被提升。声明的提升意味着会输出undefined而不是Reference Error

    6.2 let

    1. 使用 let 的块级作用域声明

  • let是ES6的新增的关键字,它和var有些类似,但是它不是在函数区域作用,而是在会计作用区域

  • 所谓的块级作用区域即是此声明变量最近的一对{}来界定,换句话说,if 块、while 块、function 快,甚至连单独的快也是 let 声明变量的作用域。
    1. if(true) {
    2. let a; // a 只在if里面生效
    3. }
    4. console.log(a); // ReferenError: a 在外部没有声明
    5. while(true) {
    6. let b; // b 旨在while里面生效
    7. }
    8. consloe.log(b); // ReferenError: b 在while外部没有被定义
    9. function foo() {
    10. let c; // c仅在函数内定义,和var在函数内相同
    11. }
    12. console.log(c); // ReferenError: c 没有定义
    13. // JavaScript 解释器会根据其中内容识别出它来
    14. {
    15. let d; // 就算只有花括号,也只在内部生效
    16. }
    17. console.log(d); // ReferenError: d 没有定义

let 与 var 的另一个不同之处是在同一作用域内并不能声明两次。重复的 var 声明会被忽略,而重复的 let 声明会抛出SyntaxError

  1. var a;
  2. var a;
  3. // 不会报错 但后面相同的变量会覆盖前面的变量
  4. {
  5. let b;
  6. let b;
  7. }
  8. // SyntaxError: 标识符 b 已经声明过了

let 的行为非常适合在循环中声明迭代变量。使用 var 声明的迭代变量会泄漏到循环外部,这种情况应该避免。

  1. for(var i = 0; i < 10; i++) {}
  2. console.log(i); // 10 内部定义可在外部使用
  3. for(let j = 0; j < 10; j++) {}
  4. console.log(j); // ReferenceError: j 没有定义仅在for内生效
  1. 严格来讲,let JavaScript 运行时也会被提升,但由于“暂时性死区”(temporal dead zone)的缘故,实际上不能在声明之前使用 let 变量。因此,从写 JavaScript 代码的角度说,let 的提升跟 var 是不一样的

6.3 const

1. 使用const常量声明

const也是在ES6出现的新声明,在进行const变量时必须同时对变量进行赋值初始化操作,并且在其声明周期内无法再重新赋值。

  1. const a; // 错误! 常量声明时没有初始化
  2. const b = 3;
  3. console.log(b); // 3
  4. b = 4; // 错误! 给给常量赋值
  • 使用const时必须在声明时赋值
  • const在其活动周期内无法改变
  • 其他方面与let相同

由于 const 声明暗示变量的值时单一类型且不可修改,JavaScrupt 运行时编译器可以将其所有实例都替换成实际的值,而不会通过查询表进行变量查找。谷歌的 V8 引擎就执行这种优化。

注意

  • 开发实践表明,如果开发流程并不会因此而受很大影响,就应该尽可能地多使用 const 声明
  • 除非确实需要一个将来会重新赋值的变量。这样可以从根本上保证提前发现 重新赋值导致的 bug

6.4 作用域

内部函数可以调用外部函数的变量,优先最近的函数

  1. function f1() {
  2. var num = 123;
  3. function f2() {
  4. console.log(num);
  5. }
  6. f2();
  7. }
  8. var num = 456;
  9. f1();
  • 值为123,由与f2没有num变量,只能调用就近函数此变量,按照从小到大,从上到下的顺序
  • 有限向外层函数寻找,层层递进

6.5 预解析

JavaScript中,JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行。

  • 预解析:在当前作用域下,JS 代码执行之前,浏览器会默认把带有 var 和 function 声明的变量在内存中进行提前声明或者定义。(声明时函数排在变量的前面)
  • 代码执行:在解析完后从上到下执行代码
  • 预解析将在代码执行前把变量和函数声明完

1. 变量预解析

在使用var声明时,变量的声明会被提到当前作用域的最前端,但变量赋值不会

  1. console.log(num); //为undefined
  2. var num = 10;

解析完为:

  1. var num'
  2. console.log(num);
  3. num = 10;

2. 函数预解析

函数的声明会被提到作用域的最顶端,但不会调用函数

  1. f1();
  2. function f1() {
  3. console.log('能显示吗')
  4. }
  5. f1();

函数声明被提到最顶端,然后按照代码执行顺序正确执行

单当使用函数表达式声明函数时,由于此时只是将函数结果赋值给变量,即使函数未声明,所以只有变量提升,函数报错

  1. fn();
  2. var fn = function() {
  3. console.log('想不到吧');

  1. var fn; //只有变量声明被提到顶端
  2. fn();
  3. function() { //函数名为空,直接报错
  4. console.log('想不到吧');
  5. }
  • 函数表达式只是将函数返回值赋予变量,没有函数声明提升