阅读 212

JavaScript 变量声明var、let、const详解

JavaScript 中的变量是松散类型的,可以保存任何类型数据,变量只不过是一个名称。JavaScript 中,可以声明变量的关键字有varletconst

1. var

使用var定义变量,可以保存任何类型的值。若不初始化变量,变量会保存undefined

1. 函数级作用域

使用var定义的变量会成为包含它的函数的局部变量。

function func() {     var a = 'hi'; // 局部变量 } func(); console.log(a); // ReferenceError: a is not defined 复制代码

变量a在函数内部使用var定义,调用函数func,会创建这个变量并给它赋值。函数执行结束后,变量就会被销毁,所以上述代码最后一行会报错,显示变量a未定义。

若在函数内部,不使用var操作符,直接给变量a赋值,那么a就成为了全局变量,可以在函数外部访问到。在浏览器环境下,a成为window对象的属性。

function func() {     a = 'hi'; // 全局 } func(); console.log(a); // hi console.log(window.a); // hi console.log(window.a === a); // true 复制代码

2. 变量提升

使用var声明变量,会自动提升到函数作用域顶部,如下代码:

function func() {     console.log(a);     var a = 1; } func(); // undefined 复制代码

代码没有报错,输出了undefined,这是因为变量的声明被提升到了函数作用域顶部,等价于如下代码:

function func() {     var a;     console.log(a);     a = 1; } func(); // undefined 复制代码

3. 重复声明

另外,使用var重复声明同一个变量也可以:

function func() {     var a = 1;     var a = 2;     var a = 3;     console.log(a); } func(); // 3 复制代码

4. 全局变量挂载到 window

浏览器环境中,全局作用域下,使用var声明的变量,会挂载到window对象上。

var a = 1; console.log(window.a === a); // true 复制代码

2. let

let也可以声明变量,但和var操作符有很大的区别。

1. 块级作用域

以下代码会报错,因为let声明的作用域,具有块级作用域,即被{}包裹的部分。

if (true) {     let a = 10; } console.log(a); // a is not defined 复制代码

2. 不可重复声明

以下代码,执行到let a = 2就会报错,因为变量a在当前块级作用域中已经被声明过了,不能重复声明。

if (true) {     let a = 1;     let a = 2; // SyntaxError: Identifier 'a' has already been declared     let a = 3; } 复制代码

另外,如果混用varlet声明变量,也是不允许的,下面的代码都会报错:

let a; var a; // 报错 复制代码

var a; let a; // 报错 复制代码

2. 不存在变量提升(暂时性锁区)

使用let声明的变量,不能在声明之前访问它。

if (true) {     console.log(a); // ReferenceError: Cannot access 'a' before initialization     let a = 1; } 复制代码

实际上,JavaScript 也会注意出现在块后面的let声明,只不过在此之前不能以任何方式来引用未声明的变量。在let声明之前的执行瞬间被称为暂时性死区

3. 全局变量不会挂载到 window

var不同,即使在全局作用域下,使用let声明的变量也不会挂载到window对象。

var a = 1; let b = 2; console.log(window.a === a); // true console.log(window.b === b); // false 复制代码

4. 不依赖条件声明

if (typeof name === 'undefined') {     let name; } // name 被限制在 if {} 块的作用域内 name = 'Matt'; // 全局赋值 try {     console.log(age); // 如果 age 没有声明过,则会报错 } catch (error) {     let age; } // age 被限制在 catch {}块的作用域内 age = 26; // 全局赋值 复制代码

3. const

const的特点与let基本一致,但const有一些自己的特点。

1. 声明变量时必须同时初始化

以下声明报错,因为声明的同时没有初始化变量。

const a; // Missing initializer in const declaration 复制代码

2. 不能修改声明后的变量

使用const定义了一个变量后,不能再更改它的值:

const a = 1; a = 2; // TypeError: Assignment to constant variable 复制代码

这里有一个误区,实际上,使用const声明的变量,不能修改的是内存地址!!

具体规则如下:

  • const定义的常量为基本数据类型时,不能被修改

  • const定义的常量为引用数据类型时,可以通过其属性进行数据修改

基本数据类型的值就保存在内存地址中,所以const定义的基础数据类型不可被改变。 而引用数据类型指向的内存地址只是一个指针,通过指针来指向实际数据,也就是说,不可被改变的是指针而不是数据,所以const定义的引用数据类型的常量可以通过属性来修改其数据。

例如,使用const定义了一个数组,虽然不能更改数据类型,但可以通过push等方法,修改这个数组中的数据。

const arr = []; arr.push(1, 2, 3); console.log(arr); // [ 1, 2, 3 ] 复制代码

4. 总结及最佳实践

varletconst
函数级作用域块级作用域块级作用域
重复声明不可重复声明不可重复声明
变量提升不存在变量提升不存在变量提升
值可更改值可更改不可更改
全局变量挂载到window全局变量不会挂载到window全局变量不会挂载到window

通常,写 JavaScript 代码时,遵循以下原则:

  1. 不使用var

  2. const优先,let次之

5. 一道面试题

以下代码运行后会打印什么?

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

答案:6 6 6 6 6

虽然每个for循环中定时器设置的时间都是0,但由于 JavaScript 是单线程 eventLoop机制,setTimeout是异步任务,遇到setTimeout函数时,JavaScript 会将其放入任务队列中,待同步任务执行完毕后,才执行任务队列中的异步任务

又因为setTimeout函数也是一种闭包,往上找它的父级作用域链window,而变量i是用var声明的,是window上的全局变量,所以此时变量i的值已经变成i = 6了,最后执行setTimeout时,当然会输出 5 个6了!

使用let解决

利用 JavaScript 的块级作用域,就不用这么麻烦了。如果for循环使用块级作用域变量关键字,循环就会为每个循环创建独立的变量,从而每次打印都会有正确的索引值。

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


作者:火星飞鸟
链接:https://juejin.cn/post/7026163740806479902


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐