JavaScript中var、let、const区别

JavaScript中var、let、const区别

一、前言

  • ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景。

  • ES6中增加了块级作用域的概念

JavaScript中var、let、const区别

二、var

如果使用关键字 var 声明一个变量,那么这个变量就属于当前的函数作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量就属于全局作用域。举例说明:

var a = 1; //此处声明的变量a为全局变量
function foo(){
   var a = 2;//此处声明的变量a为函数foo的局部变量
   console.log(a);//2
}
foo();
console.log(a);//1

如果在声明变量时,省略 var 的话,该变量就会变成全局变量,如全局作用域中存在该变量,就会更新其值。如:

var a = 1; //此处声明的变量a为全局变量
function foo(){
   a = 2;//此处的变量a也是全局变量
   console.log(a);//2
}
foo();
console.log(a);//2

var命令会发生”变量提升“现象,即变量可以在声明之前使用,值为undefined
提升是指无论 var 出现在一个作用域的哪个位置,这个声明都属于当前的整个作用域,在其中到处都可以访问到。注意只有变量声明才会提升,对变量赋值并不会提升。如下例所示:

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

相当于

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

而如果对未声明过的变量进行操作,就会报错

console.log(b);//假设b未声明过,Uncaught ReferenceError: b is not defined

三、let

let 声明的变量,具有如下几个特点:

  1. let 声明的变量具有块作用域的特征。
  2. 在同一个块级作用域,不能重复声明变量。
  3. let 声明的变量不存在变量提升,换一种说法,就是 let 声明存在暂时性死区(TDZ)。
let a = 1;
console.log(a);//1
console.log(b);//Uncaught ReferenceError: b is not defined
let b = 2;
function foo(){
    let a = 1;
    let a = 2;//Uncaught SyntaxError: Identifier 'a' has already been declared
}

以下是一个经典的关于 var 和 let 的一个例子:

for (var i = 0; i < 10; i++) {
    setTimeout(function(){
        console.log(i);
    },100)
};

该代码运行后,会在控制台打印出10个10.若修改为:

for (let i = 0; i < 10; i++) {
    setTimeout(function(){
        console.log(i);
    },100)
};

则该代码运行后,就会在控制台打印出0-9.

四、var let 在for循环中的区别

var let 在for循环中的区别

分析
这里先说一下吾辈两个关于 js 的认知

  1. js 里 setTimeout 如果延迟时间为 0 应该会立刻执行
  2. js 里的 for 循环和 java 应该差不多,for 循环内部是单独的作用域
    图解如下
    在这里插入图片描述

那么答案只有一个,两段代码执行的结果应该都是 0 1 2 才对!O(≧▽≦)O

然而当吾辈执行后的结果却是

let: 0 1 2
var: 3 3 3
发生了什么?吾辈表示很无语。。。┐( ̄ヮ ̄)┌

解答
然而,上面的两个认知全错了!
下面是错误的理解
其一:js 里 setTimeout 如果延迟时间为 0 应该会立刻执行

JavaScript的同步与异步

好吧,异步没有 立刻执行 这个说法,js 中异步函数实际上是被 事件队列 所管理的。当使用 setTimeout 函数时,即便延迟为 0,函数 () => console.log(i) 也不会立刻执行,而是会被放到 事件队列 中去,然后等待浏览器空闲之后执行。

在 MDN 上有一段关于零延迟的描述
零延迟
零延迟并不意味着回调会立即执行。以 0 为第二参数调用 setTimeout 并不表示在 0 毫秒后就立即调用回调函数。
其等待的时间取决于队列里待处理的消息数量。在下面的例子中,”this is just a message” 将会在回调获得处理之前输出到控制台,这是因为延迟参数是运行时处理请求所需的最小等待时间,但并不保证是准确的等待时间。
基本上,setTimeout 需要等待当前队列中所有的消息都处理完毕之后才能执行,即使已经超出了由第二参数所指定的时间。

所以 setTimeout 实际上并没有立刻执行,而是等到整个 for 循环结束之后才执行的。
其二:js 里的 for 循环和 java 应该差不多,for 循环内部是单独的作用域
好吧,这个认知更是错的一塌糊涂,for 循环居然没有块级作用域?i 和 k 都是可以直接访问的,犹如直接声明到 for 循环外一样。

for (var i = 0; i < 3; i++) {
  var k = 10 - i
}
console.log(`i: ${i}, k: ${k}`)

// 结果:
// i: 3, k: 8

相当于

var i = 0
var k
for (; i < 3; ) {
  k = 10 - i
  i++
}

如果换成 let 则两者都无法访问

for (let i = 0; i < 3; i++) {
  let k = 10 - i
}
console.log(`i: ${i}, k: ${k}`)

// 结果:
// Uncaught ReferenceError: i is not defined

甚至还有一个更有趣的情况,在 for 的表达式和块中可以声明相同的变量,这只说明了一件事,let 声明的变量和循环内部声明的变量不在同一个作用域中!

for (var i = 0; i < 3; console.log('in for expression', i), i++) {
  let i
  console.log('in for block', i)
}

// 结果:
// in for block undefined
// in for expression 0
// in for block undefined
// in for expression 1
// in for block undefined
// in for expression 2

或许,i 只是加了新的作用域,就像下面这样,如此,循环外面就访问不到内部的值,循环内部和 for 的表达式也同样不在一个作用域了,每次循环结束就更新这个值

for (var i = 0; i < 3; i++) {
  ;(_i => {
    setTimeout(() => console.log(_i), 0)
    i = _i
  })(i)
}

附:这里吾辈是根据 babel 编译的结果修改而来。而且 babel 真的很聪明,当迭代变量 i 没有更新时,就不会使用 _i 进行区分呢!
解决
重新建立了自己的认知之后,可以再对 let/var 在 for 循环进行分析了。

首先是 let + for

1. let + for
再看下面这段代码,可以对其进行分解

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

创建 for 循环,表达式中存在 let 变量,for 将会创建一个块级作用域(ES6 let 专用)
每次迭代时,会创建一个子块级作用域,迭代变量 i 也会重新生成
对 i 的任何操作,都会被记住并赋值给下一次的迭代
块级作用域只对 let 有效,var 声明的变量仍然能在 for 循环外使用,证明 for 循环并不是像函数作用域那样是连 var 都能封闭的作用域。

图解如下
在这里插入图片描述
2. var + for
分析一下

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}

进入 for 循环
在这里创建了迭代变量 i,因为是函数作用域变量所以在 for 循环外可以访问,被提升到了函数作用域顶部声明
setTimeout 函数执行,闭包绑定函数作用域外部变量 i,在循环结束输出 i 的值 3
继续迭代
在这里插入图片描述

五、const

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

  1. const 声明方式,除了具有 let 的上述特点外,其还具备一个特点,即 const 定义的变量,一旦定义后,就不能修改,即 const 声明的为常量。

例如:

const a = 1;
console.log(a);//1
a = 2;
console.log(a);//Uncaught TypeError: Assignment to constant variable.

但是,并不是说 const 声明的变量其内部内容不可变,如:

const obj = {a:1,b:2};
console.log(obj.a);//1
obj.a = 3;
console.log(obj.a);//3

所以准确的说,是 const 声明创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。
2. .const一旦声明变量,就必须立即初始化,不能留到以后赋值。对于const来说,只声明不赋值,就会报错

const a;// SyntaxError: Missing initializer in const declaration
说明:const一旦声明,必须赋值

六、总结

var 声明的变量属于函数作用域,let 和 const 声明的变量属于块级作用域;
var 存在变量提升现象,而 let 和 const 没有此类现象;
var 变量可以重复声明,而在同一个块级作用域,let 变量不能重新声明,const 变量不能修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值