使用let/const定义变量的场景

本文探讨了JavaScript中let和const的使用场景,包括避免函数内层变量覆盖外层变量,防止循环变量污染全局,以及块级作用域和暂时性死区的概念。let和const的使用能更好地管理变量,避免内存浪费和全局变量污染,同时强调了const声明的常量值不可变的特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

虽互不曾谋面,但希望能和您成为笔尖下的朋友

以读书,技术,生活为主,偶尔撒点鸡汤

不作,不敷衍,意在真诚吐露,用心分享

点击左上方,可关注本刊

标星公众号(ID:itclanCoder)

16d479b995573248bea0640b2773e5bd.png

背景

javaScript中,定义变量是一个非常常见的操作,在Es5中,通常使用var定义声明变量,而在Es6中新增了letconst关键字,也是用于声明定义变量

那究竟在什么样的情况下使用它们,解决自己开发过程当中定义变量的一些困扰

为什么使用Let,const定义变量更节省内存?

javScript中有多少种方法定义声明变量呢?

ES5中只有两种声明变量的方法,varfunction两个关键字,而Es6新增了let,和const,另外,还有两种就是import,和class关键字


41f1f68d21849bb3f057eaebea3ba7c9.gif

01

var声明及变量提升

在Es5中只有函数作用域和全局作用域,没有块级作用域,通过关键字var声明的变量,无论是在哪里声明的,都会被当成在当前作用域顶部声明的变量,这就是我们常说的提升机制

这会带来一些问题

场景1-函数内层变量可能会覆盖外层变量

var tmp = new Date();
function f(condition) {
    // 此处可以访问变量tmp,其值为undefined
    console.log(tmp);
    if(condition) {
        var tmp = "itclanCoder";
        return tmp;
    } else {
        // 此处可访问变量value,其值为undefined
        console.log(tmp);
        return null;
    }
}
f(false);  // undefined

上面的代码,或许你会认为只有condition条件为true时,才会创建变量tmp,事实上,函数f无论如何变量tmp都会被创建,在预编译阶段,javaScript引擎会将上面的f函数修改成下面这样

函数f执行后,输出结果为undefined,原因就是在于,当使用函数声明时,变量会提升到运行坏境的顶部,导致内层的tmp变量覆盖了外层的tmp变量

它会变成如下这样

function f(condition) {
    var tmp; // 先定义变量
    if(condition) {
        tmp = "itclanCoder";
        return tmp;
    }else {
        return null;
    }

}

var tmp = new Date();
f(false)

变量tmp的声明被提升至函数顶部,而初始化操作依旧停留在原处执行,这就意味着else中的也可以访问到该变量tmp,因为此时变量还没有初始化,只有定义,但没有赋值,所以值是undefined

场景2-用来计数循环变量泄露为全局变量

循环遍历一字符串javaScript,输出打印出每个字符

var str = "javaScript";
for(var i = 0;i<str.length;i++) {
    console.log(i,str[i]);
}

console.log(i);

//0 'j'
1 'a'
2 'v'
3 'a'
4 'S'
5 'c'
6 'r'
7 'i'
8 'p'
9 't'
10

上面的i变量只是用来控制循环,但是循环结束后,它并没有消失,释放,而是泄露成了全局变量,这样会造成全局变量的污染

解决办法:

若使用let定义变量,则变量不会被提升置作用域顶部,它只会在它定义的块级作用域内生效

注意事项

使用let,const定义变量,因为它不存在变量提升,所以,变量一定要在声明后使用,否则会报错

console.log(tmp);  // ReferenceError
let tmp = 2;

上面的i变量只是用来控制循环,但是循环结束后,它并没有消失,释放,而是泄露成了全局变量,这样会造成全局变量的污染

解决办法:

使用let定义变量的话,那么for循环的计数器变量i,只在for循环内有效

如下示例所示

var arr = [];
for(var i = 0;i<10;i++) {
    arr[i] = function() {
        console.log(i);
        return arr.push(i)
    }
}
console.log(arr[8]());  // 10, 11

在上面的代码中,使用var声明的,在全局范围内都是有效的,所以每一次循环,新的i值都会覆盖旧值,导致最后输出的是最后一轮的i的值

如果使用let,声明的变量仅在块级作用域内有效,最后将输出8


f7ec694aab81561b5b77a249435afbcf.gif

02

块级声明及块级(词法)作用域

正因为Es5中使用var声明的变量,没有块级作用域,会污染全局变量,如果使用不当,会产生一些达不到自己预期的效果,所以在Es6中就有了块级作用域

块级作用域:用于声明在指定的块的作用域之外无法访问的变量

函数内部

  1. 块中(字符{}之间的区域)

  2. 块级与块级之间的代码块是相互隔离的,互不影响的,如下所示

示例代码:

function fn() {
    let n = 12;
    if(true) {
        let n = 20;
    }
    console.log(n);  // 12
}

上面的函数有两个代码块,都声明了变量n,运行后输出12

注意事项

Es6允许块级作用域任意嵌套,外层作用域无法读取内层作用域的变量

{{{{let name = 'itclanCoder'}}}}

内层作用域可以定义外层作用域的同名变量,内部声明的函数都不会影响到作用域的外部

{
    let name = '随笔川迹'
    {
        let name = 'itclanCoder'
    }
}

有了块级作用域的出现,立即执行匿名函数变得不在必要了

(function() {
  var tmp = '';
}())
// 块级作用域
{
    let tmp = '';
}

不存在变量的提升

let不像var那样会发生变量提升现象,所以,变量一定要先声明后使用,否则就会报错

console.log(foo) // ReferenceError
let foo = 2222;

在同一块作用域内不允许重复声明

// 报错
function () {
    let a = 1;
    var a = 2;
}

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

function func(arg) {
    let arg; // 报错
}

但是要是这样的话则是不报错的

function func(arg) {
    if(true) {
        let arg; // 不报错
    }
}


ab14e0ea07921f3315f1cd8157c7235d.gif

03

暂时性死区

只要在花括号{}let,const声明定义的变量,它会绑定在这个区域内,不会受外部的影响,它会形成自己封闭的作用域,只要在声明之前使用这些定义的变量,就会报错

在代码块内,使用let,const命令声明变量之前,该变量都是不可用的,这称为暂时性死区(TDZ),换言之,需要提前声明并且赋值,就可以使用

if(true) {
    // 暂时性死区开始
    tmp = 'itclanCoder'; // ReferenceError,报错
    console.log(tmp);

    let tmp; // 暂时性死区结束
    console.log(tmp); // undefined
    tmp = "随笔川迹";
    console.log(tmp);
}

let命令声明变量tmp之前,都属于变量的tmp的死区

之所以定义暂时性死区,和不存在变量的提升,主要是为了减少运行时的错误,防止在变量声明之前就使用这个变量,从而导致一些Bug

暂时性死区的本质是: 只要一进入当前作用域,所使用的变量就已存在,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量


7e7b6f2660eec9932c317e090e30a7a3.gif

04

为什么使用let,const声明变量可节省内存空间

如下面代码

function f(condition) {
    if(condition) {
        let dateVal = new Date();
        return dateVal;
    }else {
        // 变量dateVal在此处不存在
        return null;
    }

    // 变量dateVal在此处不存在
}

以上函数f内定义的dateVal变量在使用let声明后,不在被提升至函数顶部,当离开if语句块后,dateVal会立即被销毁

condition的值为false,那么永远不会声明并初始化dateVal


ead4c330781a73f43efd102f690a34d5.gif

05

const 声明命令

constEs6新增的关键字,一旦声明后,它的值就不能被更改,所以通过const声明的常量必须进行初始化,不能留到以后在赋值

// 有效的常量
const maxLength = 10;
// 语法错误,常量未初始化
const name;


623ef0c92444693c96e7519a1184f444.gif

06

关于循环中const声明

在代码中,经常会用到for循环,需要初始化变量,对于for循环来说,可以在初始化时使用const,但要是更改这个变量的话,它就会抛出错误

var arrs = [];
for(const i = 0; i< 10;i++) {
  arrs.push(function() {
      console.log(i);
  })
}

在这段代码中,变量i被声明为常量,在第一次循环中,i0,迭代执行成功,然后执行i++,因为这条语句试图修改常量,因此抛出错误,如果后续循环不会修改该常量,那么可以使用const声明

比如:for-onfor-of循环中使用const时的行为与使用let一致,如果使用const定义的常量,后续不会被修改,那么可以使用

var arrs = [];
var object = {
    a: true,
    b: true,
    c: true
}

// 不会产生错误
for(const key in object) {
    arrs.push(function() {
        console.log(key);
    })
}

arrs.forEach(function(arr) {
    arr();
})

注意事项

对于复合类型的变量,变量名不指向数据,而是指向数据所在的地址,const命令只是保证变量名指向的地址不变,并不保证该地址的数据不变

因此,将一个对象声明为常量必须非常小心

const foo = {};
foo.data = 123;
console.log(foo.data) // 123
foo = {};  // TypeError: 'foo' is read-only不起作用

在上面的代码中,常量foo存储的是一个地址,指向一个对象,不可变的只是这个地址,不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新的属性


418f27d4f9061b61f935ee6f916d550e.gif

07

关于全局块作用域的绑定

var,和function被用于全局作用域时,它会创建一个新的全局变量对象作为全局对象(浏览器环境中的window对象),使用var会覆盖一个已经存在的全局变量

let,constclass命令声明的全局变量不属于全局对象的属性,声明的变量不会提升,而且只可以在声明这些变量的代码块中使用

不能在声明变量前访问它们

如果不想为全局对象创建属性,则使用letconst要安全得多

如果希望在全局对象下定义变量,仍然可以使用var,这种情况下常见用于在浏览器中跨ifram或跨window访问代码


5ee0b29259cfcc14dc0b37c5031a9489.gif

08

具体什么时候使用var,let,const

对于需要写保护的变量则使用const,只有确实需要改变变量的值时,则使用let,因为大部分变量的值在初始化后不应该在改变,而预料外的变量的值的改变会产生很多Bug

如果希望在全局对象下定义变量,可以使用var

2cea1fe6f1776cc8367d5c09138797cd.gif

总结

块级作用域绑定的let,const为javaScript引入了词法作用域,使用它们声明变量不会提升,而且只可以在声明这些变量的代码块种使用

使用let,const也能够节省内存空间,不会造成全局变量的污染,必须的得前置声明赋值,然后才能使用(暂存性死区)

对于变化的变量,则使用let,而不改变的定义变量,使用const声明,如:for循环体中,使用const定义初始化值变量,那么就会报错,因为常量不能被改变

而for..in,fo..of循环中,let,const都会每次迭代创建一个新的绑定,从而使循环体内创建的函数可以访问到相应迭代的值,而非最后一次迭代后的值


【自我介绍】

作者:川川,一个靠前排的90后帅小伙,具有情怀的代码男,路上正追逐斜杠青年的践行者,愿做你耳朵旁边的枕男,眼睛笔尖下的窗户

715adc1d9ca336740a08e1cc03256b9e.png

itclanCoder

添加微信suibichuanji

097676c840b4ffb0ab587156e97e8e17.png

阅后即焚,随手三连击

  • 点赞】随意,您的鼓励将会使我更加的努力,如果喜欢,点个【】,或与人【分享】,让我知道您曾今来过

  • 欢迎文章下方【留言】,一起学习探讨,您的评论藏过你读过的书

  • 关注公众号【itclanCoder】,分享的不仅仅是代码,还有柴米油盐,致力于为你打开你另一扇窗


好知识,好经验值得点【在看】一下

### JavaScript 中 `let`、`const` 和 `var` 的使用场景及区别 #### 变量声明方式的特点 在 JavaScript 中,`var`、`let` 和 `const` 都是用来声明变量的关键字,但是它们之间有着显著的区别。 - **`var` 关键字** - 具有函数作用域和全局作用域,在函数内声明的变量在整个函数体内都有效;如果是在最外层声明,则会在整个程序中都可以访问到这个变量。 - 存在变量提升现象,即可以在未初始化前就调用此变量,不过此时它的值为 undefined[^2]。 - **`let` 关键字** - 引入了块级作用域的概念,只在其所在的代码块 `{}` 内部起效。 - 不会发生重复声明错误,不允许在同一范围内多次声明同一个变量名称。 - 同样存在暂时性死区(TDZ),意味着无法读取尚未初始化的变量,直到执行流离开 TDZ 才能正常工作[^3]。 - 类似于 `let` 提供块级作用域。 - 定义常量,一旦赋初值之后就不能再被重新分配新值。 - 对象或数组类型的常量虽然不能指向新的对象实例,但仍然允许修改其内部属性或元素[^1]。 ```javascript // 使用 const 声明一个不可变的对象 const obj = { key: 'value' }; obj.key = 'newValue'; // 这是可以的 console.log(obj); // 输出: {key:"newValue"} // 尝试给 const 赋予另一个完全不同的对象将会失败 obj = {}; // 抛出异常 TypeError ``` #### 实际应用场景举例 当编写现代 JavaScript 应用时,建议优先采用 `const` 来声明那些不会更改的数据结构,比如配置项或者 API URL 等。而对于可能会发生变化的状态信息则应该选用 `let`。至于 `var`,除非是为了兼容旧版本浏览器或其他特殊情况下的需求,一般情况下应尽量避免使用它[^4]。 下面是一个简单的例子展示了如何利用这些关键字: ```javascript function calculateSum(x, y) { const sumResult = x + y; // 使用 const 表示计算结果不变 if (typeof x !== "number") { let errorMessage = `${x} is not a number`; // 使用 let 因为此处 message 可能在不同条件下变化 console.error(errorMessage); return null; } return sumResult; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值