JavaScript中 var、let、const 特性及区别详解

简介: JavaScript中 var、let、const 特性及区别详解

1. ES5 中 var 声明变量的弊端

ES5 中其实给我们提供了两种声明变量的方法:var 命令和 function 命令。

使用 function 命令声明函数这里不参与讨论,我们主要对比 var 命令。

var 命令声明变量有三大特点:

  • 变量可以重复声明;
  • 变量声明会被提升;
  • 没有块级作用域;

这三个特点使得 JS 代码的编写显得有些“随意”,不够规范,而且这样的语法逻辑也有悖常理,举两个例子:

1.1 内层变量会覆盖外层同名变量

我们用 var 声明一个全局变量 num 赋值为 1,之后在 show 函数作用域的 if 代码块中重新声明 num 变量,并赋值 2

var num = 1;
function show() {
  console.log(num);
  if (false) {
    var num = 2;
  }
}
show(); // undefined

最终 show 函数执行输出结果为 undefined。

这是因为 var 声明的变量没有块级作用域,第二次重新声明的 num 会被提升至函数开头,覆盖了外层的全局同名变量,此时输出的结果必然是undefined。

1.2 各种循环结构中用来计数的循环变量会泄露为全局变量

在使用循环结构时会声明一个循环的控制变量(比如i,j,k等等),但是循环结束后,它并没有消失,泄露成了全局变量。

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

更好的做法是我们希望它只在循环控制内有效,循环结束后自动失效,不会影响其他部分的代码执行。这个时候就需要用到我们ES6里新增的 let 。

2. let 、const 基础

2.1 let 基础用法

let 命令是 ES6 标准中用来声明变量的新增命令。

它的使命就是来代替 var 命令的,它的用法类似于 var,但是却弥补了 var 的设计缺陷。

区别:let 命令可以把当前代码块声明为块级作用域,使用 let 命令声明的变量,只在当前代码块内有效。(代码块以一对花括号{}为单位)

{
  let a = 1;
  var b = 2;
}
console.log(a) // ReferenceError: a is not defined.
console.log(b) // 2

在代码块外访问变量 a 会报未定义的错误,而使用 var 定义的 b 变量仍然可以访问到。

现在有了 let 命令,就可以很好的解决本文开头的第二个问题:循环变量泄露的问题。

我们把 for 循环的计数器 i 用 let 声明:

for (let i = 0; i < 3; i++) {
  //...
}
console.log(i); // ReferenceError: i is not defined

此时变量 i 就访问不到了。

❀ 拓展1: ❀

循环中使用 let 命令,还有一个要注意的点:每次循环中变量i都会被重新声明

使用 var 声明的循环变量 i,每次循环中添加一个定时器函数来打印出这个循环变量 i:

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

输出了 3 个 3,是因为整个 for 循环中的循环变量 i 一直都是同一个变量,最后循环结束变量 i 最终被赋值3。

之后换成 let 命令再输出一次:

for(let i = 0; i < 3; i++) {
  setTimeout(function() {
    console.log(i); // 0  1  2
  }, 1000);
}

可以看到输出结果和没有加定时器函数一样,这是因为每次循环中变量i都被重新声明了。

它之所以还是按正常逻辑输出,是由于JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量 i 时,就在上一轮循环的基础上进行计算。

❀ 拓展2: ❀

for 循环本身还有一个特别之处:设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

for (let i = 0; i < 3; i++) {
  let i = 'zevin';
  console.log(i); // zevin  zevin  zevin
}

输出了 3 次 zevin。这表明循环体内部的变量 i 与循环变量 i 不在同一个作用域,有各自单独的作用域。

2.2 const 基础用法

const 命令用于声明一个只读的常量。 声明之初就必须赋值,而且一旦声明就不可更改。

const PI;
// SyntaxError: Missing initializer in const declaration
const PI = 3.1415926;
PI = 3.14;
// TypeError: Assignment to constant variable.

❀ 拓展 ❀

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

对于基本类型的简单数据(数值、字符串、布尔值)来说,变量所指向的内存地址保存的就是值本身,所以就等同于变量的值不得修改;

const obj = {};
// 可以添加属性
obj.name = 'zevin';
console.log(obj.name);
// zevin
// 让obj指向另一个对象就会报错
foo = {}; 
// TypeError: "obj" is read-only

而对于引用数据类型(主要说数组,对象)来说,变量所指向的内存地址保存的是引用地址,只是不能更改引用地址的指向,而对于数组,对象本身来说,我们仍然可以添加或删除元素。

3. ES6 变量声明新规范

为了改善 ES5 中 var 命令的声明现状,同时也是为了提高JS语言的规范性,ES6中提出了以下四条新的变量声明规范,let 和 const 命令都适用。

3.1 块级作用域

ES5 只有全局作用域和函数作用域,终于在ES6中新加入了块级作用域。使用let 或 const 命令声明的变量都只在声明所在的块级作用域内有效。

{
  let a = 1;
  if(true){
    const a = 2;
  };
  console.log(a); // 1
}

代码块以一对大花括号 {} 为单位,互相之间可以任意嵌套,且互不影响。

{{{{
  { const age = 12 }
  console.log(age); // age is not defined
}}}};

上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。

第四层作用域无法读取第五层作用域的内部变量。

3.2 不存在变量提升

var 命令声明的变量会被提升至文档开头或函数开头,即变量可以在声明之前使用,值为undefined,

而在ES6中修复了这一语法行为,let 和 const 命令所声明的变量一定要在声明后使用,否则报错。

console.log(a); // undefined 
console.log(b); // ReferenceError: Cannot access 'b' before initialization
console.log(c); // ReferenceError: Cannot access 'c' before initialization
var a = 1;
let b = 2;
const c = 3;

3.3 暂时性死区

ES6 明确规定,如果区块中存在 let 和 const 命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。

凡是在声明之前就使用这些变量,就会报错。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

if (true) {
  // TDZ开始
  tmp = 'abc'; // ReferenceError
  console.log(tmp); // ReferenceError
  let tmp; // TDZ结束
  console.log(tmp); // undefined
  tmp = 123;
  console.log(tmp); // 123
}

上面代码中,在 let 命令声明变量 tmp 之前,都属于变量 tmp 的“死区”。

❀ 拓展 ❀

“暂时性死区”的出现也意味着 typeof 不再是一个永不报错的操作。

console.log(typeof a); // undefined
console.log(typeof b); // ReferenceError
const b = 1;

在变量 b 使用 const 声明之前都属于变量 b 的“死区”,只要用到该变量就会报错。因此,typeof 运算符就会抛出一个ReferenceError 的错误。

但是如果一个变量根本没有被声明(变量a),使用 typeof 反而不会报错,而是 undefined。

3.4 不允许重复声明

ES6 中不允许在同一块作用域内,重复声明同一个变量。

if(true){
  var a = 1;
  let a = 2;
  const a = 3;
}
// SyntaxError: Identifier 'a' has already been declared

所以同理,函数内也不可以使用 let 或 const 命令重新定义与形参同名的变量,但是 var 可以。

function func(num) {
  let num = 1;
  console.log(num);
}
func() // SyntaxError: Identifier 'num' has already been declared
function func(num) {
  var num = 1;
  console.log(num);
}
func() // 1

由于不同块级作用域之间互不影响,所以我们可以在不同块级作用域中定义同名变量。

所以下面的代码不会报错:

function func(num) {
  var num = 1;
  if(false){
    let num = 2;
    console.log(num); // 2
  }else{
    const num = 3;
    console.log(num); // 3
  }
  console.log(num); // 1
}
func()
相关文章
|
1月前
|
JavaScript 前端开发 编译器
掌握现代化JavaScript:ECMAScript提案与特性
【10月更文挑战第13天】本文介绍了ECMAScript(ES)的最新提案与特性,包括可选链、空值合并运算符、类字段和顶层Await等。通过跟踪TC39提案、使用Babel或TypeScript、测试兼容性以及逐步迁移,开发者可以高效地采用这些新特性,简化代码、提高开发效率并增强应用功能。文章还提供了实战技巧,帮助开发者在现代Web开发中充分利用这些现代化的特性。
|
1月前
|
JavaScript 前端开发 索引
JavaScript ES6及后续版本:新增的常用特性与亮点解析
JavaScript ES6及后续版本:新增的常用特性与亮点解析
27 4
|
16天前
|
前端开发 JavaScript
JavaScript新纪元:ES6+特性深度解析与实战应用
【10月更文挑战第29天】本文深入解析ES6+的核心特性,包括箭头函数、模板字符串、解构赋值、Promise、模块化和类等,结合实战应用,展示如何利用这些新特性编写更加高效和优雅的代码。
32 0
|
1月前
|
JavaScript 前端开发
【JavaScript】let,const和var的区别
总的来说,随着ECMAScript 6(ES6)及后续版本的推广,`let`和 `const`因其增强的块级作用域和对变量行为的更严格控制,逐渐成为现代JavaScript编码实践中推荐使用的变量声明方式。而 `var`由于其历史遗留的局限性,正逐渐被边缘化,但在维护老代码或处理特定兼容性需求时仍需了解。
35 3
|
2月前
|
JavaScript 前端开发 Oracle
软件工程师,学习下JavaScript ES6新特性吧
软件工程师,学习下JavaScript ES6新特性吧
43 9
|
1月前
|
JavaScript 前端开发 安全
JS中const有没有变量提升
JS中const有没有变量提升
14 0
|
2月前
|
JavaScript 前端开发
JavaScript let 和 const
JavaScript let 和 const
19 3
|
3月前
|
开发者 图形学 C#
深度解密:Unity游戏开发中的动画艺术——Mecanim状态机如何让游戏角色栩栩如生:从基础设置到高级状态切换的全面指南,助你打造流畅自然的游戏动画体验
【8月更文挑战第31天】Unity动画系统是游戏开发的关键部分,尤其适用于复杂角色动画。本文通过具体案例讲解Mecanim动画状态机的使用方法及原理。我们创建一个游戏角色并设计行走、奔跑和攻击动画,详细介绍动画状态机设置及脚本控制。首先导入动画资源并添加Animator组件,然后创建Animator Controller并设置状态间的转换条件。通过编写C#脚本(如PlayerMovement)控制动画状态切换,实现基于玩家输入的动画过渡。此方法不仅适用于游戏角色,还可用于任何需动态动画响应的对象,增强游戏的真实感与互动性。
98 0
|
3月前
|
JavaScript 前端开发
|
4月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
96 2