跳到主要内容

变量声明与作用域

var

在ES5中,顶层对象的属性和全局变量是等价的,用var声明的变量既是全局变量,也是顶层变量

注意:顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象

var a = 10;
console.log(window.a) // 10

使用var声明的变量存在变量提升的情况

console.log(a) // undefined
var a = 20

在编译阶段,编译器会将其变成以下执行

var a
console.log(a)
a = 20

使用var,我们能够对一个变量进行多次声明,后面声明的变量会覆盖前面的变量声明

var a = 20 
var a = 30
console.log(a) // 30

在函数中使用使用var声明变量时候,该变量是局部的

var a = 20
function change(){
var a = 30
}
change()
console.log(a) // 20

而如果在函数内不使用var,该变量是全局的

var a = 20
function change(){
a = 30
}
change()
console.log(a) // 30

let

用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

{
let a = 20
}
console.log(a) // ReferenceError: a is not defined.

不存在变量提升

console.log(a) // 报错ReferenceError, 这表示在声明它之前,变量a是不存在的,这时如果用到它,就会抛出一个错误
let a = 2

只要块级作用域内存在let命令,这个区域就不再受外部影响

var a = 123
if (true) { // 使用let声明变量前,该变量都不可用,也就是大家常说的“暂时性死区”
a = 'abc' // ReferenceError,
let a;
}

let不允许在相同作用域中重复声明

let a = 20
let a = 30
// Uncaught SyntaxError: Identifier 'a' has already been declared

相同作用域,下面这种情况是不会报错的

let a = 20
{
let a = 30
}

不能在函数内部重新声明参数

function func(arg) {
let arg;
}
func()
// Uncaught SyntaxError: Identifier 'arg' has already been declared

const

const声明一个只读的常量,一旦声明,常量的值就不能改变

const a = 1
a = 3
// TypeError: Assignment to constant variable.

这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值

const a;
// SyntaxError: Missing initializer in const declaration

如果之前用varlet声明过变量,再用const声明同样会报错

var a = 20
let b = 20
const a = 30
const b = 30
// 都会报错

const实际上保证的并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动

对于简单类型的数据,值就保存在变量指向的那个内存地址,因此等同于常量

对于复杂类型的数据,变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的,并不能确保改变量的结构不变

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

其它情况,constlet一致

FAQ?

let、const 块级作用域以及和 var 的区别

声明方式变量提升作用域初始值重复定义
var函数级不需要允许
let块级不需要不允许
const块级必需不允许

下面执行后输出什么?

for(var i = 1; i <= 5; i ++){
setTimeout(function timer(){
console.log(i)
}, 0)
}

结论: 输出5个6。

因为setTimeout为宏任务,由于JS中单线程eventLoop机制,在主线程同步任务执行完后才去执行宏任 务,因此循环结束后setTimeout中的回调才依次执行,但输出i的时候当前作用域没有,往上一级再找,发现了i,此时循环已经结束,i变成了6。因此会全部输出6。

变量提升

var 声明的变量存在变量提升,即变量可以在声明之前调用,值为undefined

let / const 不存在变量提升是不完全正确的,只能说由于暂时性死区的存在使得我们无法直观感受到变量提升的效果。

let 和 const 定义的变量都会被提升,但是不会被初始化,不能被引用,不会像var定义的变量那样,初始值为undefined。

当进入let变量的作用域时,会立即给它创建存储空间,但是不会对它进行初始化。

变量的赋值可以分为三个阶段:

  • 创建变量,在内存中开辟空间
  • 初始化变量,将变量初始化为undefined
  • 真正赋值

关于let、var和function:

  • let 的「创建」过程被提升了,但是初始化没有提升。
  • var 的「创建」和「初始化」都被提升了。
  • function 的「创建」「初始化」和「赋值」都被提升了。
// var
console.log(a) // undefined
var a = 10

// let
console.log(b) // Cannot access 'b' before initialization
let b = 10

// const
console.log(c) // Cannot access 'c' before initialization
const c = 10

暂停死区

在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

var不存在暂时性死区

letconst存在暂时性死区,只有等到声明变量的那一行代码出现,才可以获取和使用该变量

// var
console.log(a) // undefined
var a = 10

// let
console.log(b) // Cannot access 'b' before initialization
let b = 10

// const
console.log(c) // Cannot access 'c' before initialization
const c = 10

块级作用域

var不存在块级作用域

letconst存在块级作用域

// var
{
var a = 20
}
console.log(a) // 20

// let
{
let b = 20
}
console.log(b) // Uncaught ReferenceError: b is not defined

// const
{
const c = 20
}
console.log(c) // Uncaught ReferenceError: c is not defined

对作用域、作用域链的理解

作用域和作用域链是 JavaScript 中关于变量访问和可见性的重要概念。

  1. 作用域(Scope):

    • 作用域是一个定义了变量和函数可访问性的规则集。
    • JavaScript 中有全局作用域和局部作用域。
    • 全局作用域是整个 JavaScript 程序环境中可访问的范围。
    • 局部作用域是在函数内部定义的范围,其中的变量和函数只能在该函数内部访问。
  2. 作用域链(Scope Chain):

    • 作用域链是一系列嵌套的作用域的集合,用于确定变量和函数的访问规则。
    • 当访问一个变量或函数时,JavaScript 引擎会先在当前作用域中查找,如果找不到,则会沿着作用域链向上一级一级地查找,直到找到该变量或函数或到达全局作用域。
    • 作用域链的顶端是当前作用域,底端是全局作用域。
  3. 作用域和作用域链的影响:

    • 变量和函数在作用域内是可见的,可以被访问和使用。
    • 在一个作用域中定义的变量和函数可以被该作用域及其子作用域访问。
    • 子作用域可以访问父作用域中的变量和函数,但父作用域无法访问子作用域中的变量和函数。
    • 如果在当前作用域和父作用域中都找不到变量或函数,就会抛出一个 ReferenceError。

示例代码如下:

// 全局作用域
const globalVariable = 'Global'; // 全局变量

function outer() {
// 外部函数作用域
const outerVariable = 'Outer'; // 外部函数作用域变量

function inner() {
// 内部函数作用域
const innerVariable = 'Inner'; // 内部函数作用域变量

console.log(innerVariable); // 可以访问内部作用域变量
console.log(outerVariable); // 可以访问外部作用域变量
console.log(globalVariable); // 可以访问全局作用域变量
}

inner(); // 调用内部函数
}

outer(); // 调用外部函数

在上述代码中,全局作用域包含 globalVariable 变量和 outer 函数。outer 函数作用域包含 outerVariable 变量和 inner 函数。inner 函数作用域包含 innerVariable 变量。变量可以在其定义的作用域及其父级作用域

中访问,但无法在子作用域中访问。通过作用域链的方式,内部函数可以访问外部函数和全局作用域中的变量。