一、块级作用域

概念:通过 let 和 const 声明的变量拥有块级作用域 ,该变量必须声明在一个函数内部或在一个代码块(由一对花括号包裹)内部
优点:块级作用域可以解决由于过多全局变量和函数产生的命名冲突的问题

二、var 变量提升机制

2-1 变量提升

什么是变量提升?
JavaScript 引擎的工作方式是,先预解析代码, 获取所有变量声明和函数声明,然后再一行一行地运行,这就使所有变量声明和函数声明的语句,都会被提升到代码的顶部,这就是变量提升

var 变量提升
var关键字声明的变量,无论实际声明的位置在何处,都会被视为声明在函数顶部(如果声明不在任意函数内,则视为在全局作用域的顶部)

  1. function fn(){
  2. var a = 1
  3. }
  4. console.log(a) //报错
  5. -----------------分割线------------------
  6. {
  7. var b = 1;
  8. }
  9. console.log(b); //1
  10. // 等价于
  11. var b
  12. {
  13. b = 1;
  14. }
  15. -----------------分割线------------------
  16. if(true){
  17. var c = 1;
  18. }
  19. console.log(c); //2
  20. // 等价于
  21. var c
  22. if(true){
  23. c = 1;
  24. }

2-2 变量声明和函数声明优先级

结论:函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重写赋值
原因:函数每次执行,都会形成一个新的私有作用域(函数作用域)然后执行以下三个步骤

  1. 如果有形参,先给形参赋值
  2. 进行私有作用域中的预解释,函数声明优先级比变量声明高,最后后者会被前者所覆盖,但是可以重新赋值
  3. 私有作用域中的代码从上到下执行 ```javascript var a function a(){} console.log(a) // [Function a]

function a(){} var a console.log(a)// [Function a]

//当变量声明时没有赋值或初始化,函数声明优先级高于变量

—————————————分割线————————————-

var a = 1 function a(){} console.log(a) // 1 console.log(a()) // 报错

function a(){} var a = 1 console.log(a) // 1 console.log(a()) // 报错

等价于 function a(){} var a a = 1 console.log(a) // 1 console.log(a()) // 报错

  1. <a name="WhgpK"></a>
  2. #### 2-2 函数表达式和函数声明
  3. 函数声明:一个标准的函数声明,由关键字function 、函数名、形参和代码块组成。<br />函数表达式:函数没有名称,而且位于赋值语句右边,被赋给一个变量。在语句(如赋值语句)中,以这样的方式使用关键function时,创建的是函数表达式。
  4. ```javascript
  5. function test() {
  6. foo(); // 报错 Uncaught TypeError "foo is not a function"
  7. bar(); // "this will run!"
  8. var foo = function() {
  9. alert("this won't run!");
  10. };
  11. function bar() {
  12. alert("this will run!");
  13. }
  14. }
  15. test();

代码解析:变量名和函数都会上升,而遇到函数表达式 var foo = function(){ } 时,首先会将var foo上升到函数体顶部,而此时的foo的值为undefined,所以执行foo()报错。

三、let 块级作用域

  • let 和 var 都是用来定义变量,使用 let 声明的变量没有 var 那样的变量提升,let 声明的变量只在当前的块级作用域中有效
  • 禁止在同一个作用域中重复声明同一个变量,一旦相同立刻报错 ```javascript // 1、不存在变量提升,只在当前的块级作用域中有效 function person(status) { if (status) {
    1. let value = "蛙人"
    } else {
    1. console.log(value) // 报错
    } console.log(value) // 报错 } person(false)

// 2、在同一个作用域中声明同名变量会报错 var value = “蛙人” let value = “蛙人” // 报错

// 再来看一下不同作用域的情况

var value = “蛙人” // 全局作用域 if(true) { let value = “蛙人” // 代码块中声明,毫无影响 }

  1. <a name="bowMK"></a>
  2. ## 四、const 常量
  3. - const 声明指的是常量,和 let 一样,同样都是块级作用域,常量一旦定义完就不可以被修改,而且常量的定义时必须初始化值,否则会报错
  4. - const 实际上保证的不是变量的值不可以被改动,而是变量指向的那个地址不可以被改动,也就是 this 指针不可以被改动。当修改对象的属性时不会改变对象的指针,const 声明的对象是可以被修改的
  5. **基本数据类型的变量和值都是保存在栈内存中**<br />![1018967-20180718111209824-912970736.png](https://cdn.nlark.com/yuque/0/2021/png/12508812/1640003600854-4d280610-99cc-4476-854c-6298cef506ce.png#clientId=u67b541a7-eb92-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=uaa4884e5&margin=%5Bobject%20Object%5D&name=1018967-20180718111209824-912970736.png&originHeight=222&originWidth=544&originalType=binary&ratio=1&rotation=0&showTitle=false&size=6584&status=done&style=none&taskId=u78b9e110-cb7b-4f10-a334-7d56417bd46&title=)<br />**引用数据类型的变量是一个保存在栈内存中的指针,值是保存在堆内存中的对象。const声明的只是保证栈区内容不变。**<br />![1018967-20180718115535028-421997257.png](https://cdn.nlark.com/yuque/0/2021/png/12508812/1640003739175-7f53b3b7-4c30-44c9-a8d1-1456cb1239c9.png#clientId=u67b541a7-eb92-4&crop=0&crop=0&crop=1&crop=1&from=ui&id=u1f93bcca&margin=%5Bobject%20Object%5D&name=1018967-20180718115535028-421997257.png&originHeight=513&originWidth=577&originalType=binary&ratio=1&rotation=0&showTitle=false&size=81940&status=done&style=none&taskId=u521baf43-b7b4-4573-8c76-7dbe5c0dd16&title=)
  6. <a name="s3LcW"></a>
  7. ## 五、暂时性死区
  8. **只要块级作用域内存在 let 命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响**
  9. ```javascript
  10. var tmp = 123;
  11. if (true) {
  12. tmp = 'abc'; // ReferenceError
  13. let tmp;
  14. }

代码解析:上面代码中,存在全局变量 tmp ,但是块级作用域内 let 又声明了一个局部变量 tmp ,导致后者绑定这个块级作用域,所以在 let 声明变量前,对 tmp 赋值会报错。因为此时在 let 命令声明变量 tmp 之前,都属于变量tmp的“死区”

ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称TDZ)。

“暂时性死区”也意味着 typeof 不再是一个百分之百安全的操作。

  1. typeof x; // ReferenceError
  2. let x;
  3. // 变量x使用let命令声明,所以在声明之前,都属于x的“死区”,只要用到该变量就会报错。因此,typeof运行时就会抛出一个ReferenceError
  4. typeof y // "undefined"
  5. // y是一个不存在的变量名,结果返回“undefined”。所以,在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

不太容易看到的暂时性死区

  1. function bar(x = y, y = 2) {
  2. return [x, y];
  3. }
  4. bar(); // 报错

代码解析:调用bar函数之所以报错,是因为参数x默认值等于另一个参数y,而此时y还没有声明,属于”死区“。如果y的默认值是x,就不会报错,因为此时x已经声明了。