let、const

65 阅读5分钟

块级作用域

两个{ }之间的区域我们就称之为块级作用域

为什么需要块级作用域

在只有var声明变量的时候,只存在全局作用域和函数作用域,导致会出现一些违背我们一般逻辑的现象出现,比如:

1、内层变量覆盖了外层变量

var a = 'outside'
function f (){
    console.log(a)
    if(true){
        var a='inside'
    }
}
f() 

这段代码本来想要实现的是在if块内使用内部的a变量,在if块外使用全局的a变量,但是因为没有块级作用域+var的变量提升特性,最终执行的代码如下,输出结果为undefined

var a = 'outside'
function f (){
    var a=undefined
    console.log(a)
    if(true){
        a='inside'
    }
}
f() // undefined

2、变量被泄漏为了全局变量

for(var i=0;i<3;i++){

}
console.log(i)  // 3

在上面代码中,i只是想作为循环的变量,循环结束后就没有用处了应该消失,但是被暴露成了全局变量,导致在循环的外面还是可以访问到

块级作用域和函数声明

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();
}());

函数声明也是会被提升的,提升到当前作用域的顶部(函数提升优先级 > 变量提升)

ES5中,当前作用域是函数作用域,提升后的代码如下,执行结果:'I am inside'

function f() { console.log('I am outside!'); }

(function () {
 function f() { console.log('I am inside!'); }
  if (false) {
    // 重复声明一次函数f
   
  }

  f(); 
}());

ES6中,当前函数声明所处的作用域是if的块级作用域,本来就在首部,不会有任何变化,理论上应该执行外层作用域的f函数,输出'I am outside',但是这样就直接改变了老代码的逻辑,为了兼容,所以在ES6中这段代码在执行的时候会报错

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    // 重复声明一次函数f
    function f() { console.log('I am inside!'); }
  }

  f();  // TypeError: f is not a function
}());

这是因为ES6 在附录 B里面规定,浏览器的实现可以不遵守上面的规定,有自己的行为方式

  • 允许在块级作用域内声明函数。
  • 函数声明类似于var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。

按照这个规定,实际执行的代码如下

function f() { console.log('I am outside!'); }

(function () {
  if (false) {     
    var f
    // 重复声明一次函数f
    f = { console.log('I am inside!'); }
  }

  f();  // TypeError: f is not a function
}());

因为var没有块级作用域,所以f存在于那个自调用函数作用域中,f的值是undefined,所以报错

注意事项

块级作用域必须要有{},没有的话,不会被认定为块级作用域名,书写代码时,对于if判断,只有一条语句的时候,可能会省略{}

if( true) let b = 2 // SyntaxError: Lexical declaration cannot appear in a single-statement context

if(true){
let a  = 1 // 正常
}

let

不存在变量提升

变量提升:将函数声明(但不赋值)提到当前作用域的顶部

变量提升导致的奇怪现象:变量提升造成了可以在声明该变量前使用该变量的奇怪现象,按照一般逻辑,使用某东西前得先保证这个东西存在

console.log(a) //a
var a = 1

console.log(b) // 谷歌浏览器报错:抛出 ReferenceError: b is not defined
let b = 2

console.log(x); // safari浏览器报错:Cannot access uninitialized variable.
let x = 10;

好像对于let是否有变量提升是有争议的,按照谷歌浏览器的报错,b没有被定义,肯定是没有变量提升的,但是按照safari浏览器的报错:不能使用未初始化的变量,那x应该是被声明了但是没有初始化为undefined,个人觉得是否存在变量提升,就看你对变量提升的概念,如果变量提升=声明变量+初始化undefined的话,那let就没有变量提升,他没有初始化undefined这个过程,如果变量提升=声明变量的话,那他就有变量提升。

暂时性死区

在块级作用域中,用let声明的变量,变量会绑定到这个块级作用域,不会受到外部的影响,在let声明该变量之前。不可以使用该变量。

image.png

不可重复声明

在同一作用域内,不可以重复声明同名变量

{
    let a= 1
    var a= 2 // SyntaxError: Identifier 'a' has already been declared
}
{
    let b= 1
    let b= 2 // SyntaxError: Identifier 'b' has already been declared
}

所以不可以在函数体内部再次声明形参

function f(a){
    let a=1 // SyntaxError: Identifier 'a' has already been declared
}

但是使用var是可以的

function f(a){
    var a=1 
}

函数形参与函数体是否是同一个作用域?

从上面代码的执行结果来看,在函数体内,用let声明和参数同名的变量报错,说明函数参数和函数体是在同一作用域内

函数参数到底是用let声明的还是用var声明的?

从上面代码的执行结果来看,用var声明和参数同名的变量不会报错,说明函数参数是由var声明的

const

初始化后不可修改

const声明的是只读常量,一经初始化就不可以修改

const A = '你好'
A = '拜拜了你嘞' // TypeError: Assignment to constant variable.

声明时初始化

因为const声明的变量声明后就不可以修改,所以必须在声明时就初始化

const A  //SyntaxError: Missing initializer in const declaration
A = '拜拜了你嘞' 

不存在变量提升

const和let一样,不存在变量提升

{
    console.log(A) //Cannot access 'A' before initialization
    const A = 'hello world'
}

暂时性死区

声明变量前的区域不可以使用变量

{
    console.log(A) //Cannot access 'A' before initialization
    const A = 'hello world'
}

不可重复声明

{
    const A = 'hello world'
    let A = 'bai' //SyntaxError: Identifier 'A' has already been declared
}

声明变量的方式

  • var
  • function
  • let
  • const
  • class
class A{
    name='xf'
    age=12
}
console.log(A) //[class A]
  • import
import {aFunc} from './index.js

顶层对象属性和全局变量

浏览器环境下,全局作用域内用var和function声明的会被作为window的属性

image.png

let、const、class声明的不会被作为window的属性

image.png

node环境下,var声明的变量不会被当作global的属性,其声明变量的作用域为函数作用域

image.png

globalThis

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

浏览器:window

node:global

通用:globalThis(ES2020提出)

image.png

image.png