不用 var!let 和 const 的正确用法,被忽略的细节

开发
目前,let 和 const 关键字已经取代了传统的 var,带来了更合理的作用域规则和更严格的使用限制。然而,即使是有经验的开发者,也会忽略一些微妙的细节。

目前,let 和 const 关键字已经取代了传统的 var,带来了更合理的作用域规则和更严格的使用限制。然而,即使是有经验的开发者,也会忽略一些微妙的细节。

一、var 的问题:为什么不要用它

在深入了解 let 和 const 之前,有必要先理解为什么我们不要用 var:

  • 函数作用域而非块级作用域
if (true) {
  var x = 10;
}
console.log(x); // 10,变量 x 泄露到外部作用域
  • 变量提升(Hoisting)带来的困惑
console.log(x); // undefined,而非报错
var x = 5;
  • 允许重复声明
var user = "张三";
var user = "李四"; // 不报错,静默覆盖
  • 全局声明成为全局对象的属性
var global = "我是全局变量";
console.log(window.global); // "我是全局变量"(浏览器环境)

这些特性导致了许多难以追踪的 bug,尤其在大型应用程序中。

二、let 的核心特性:被忽略的细节

1. 暂时性死区(Temporal Dead Zone)

这可能是 let 最容易被忽略的特性:

console.log(x); // ReferenceError: x is not defined
let x = 5;

与 var 不同,let 声明的变量存在"暂时性死区"(TDZ)。从块作用域开始到变量声明之前,该变量都是不可访问的。这并非简单的"不提升",而是一种更精细的机制。

let x = 10;
function example() {
  // 从这里开始,x 进入 TDZ
  console.log(x); // ReferenceError
  
  let x = 20; // 这里 x 离开 TDZ
}

被忽略的细节:即使外部作用域已有同名变量,内部作用域的暂时性死区仍然会阻止访问。

2. 真正的块级作用域

let 声明的变量严格遵循块级作用域规则,这点经常被低估:

for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3

被忽略的细节:每次循环迭代,let 声明都会创建一个新的变量实例,这在处理闭包时尤为重要。

3. 不污染全局对象

虽然知道 let 不会成为全局对象的属性,但有一个细节常被忽略:

被忽略的细节:全局 let 变量存储在称为"脚本作用域"的特殊环境中,而非全局对象上。

三、const 的核心特性:被误解的不变性

1. 对象和数组的可变性

许多开发者误以为 const 声明的对象或数组是完全不可变的:

被忽略的细节:const 只保证引用不变,而非内容不变。要创建不可变对象,需要使用 Object.freeze():

但要注意,Object.freeze() 只是浅冻结:

深度冻结需要递归应用 Object.freeze()。

2. 声明时必须初始化

这似乎是显而易见的,但容易被忽略的是初始化的时机:

被忽略的细节:const 声明的变量不能被赋予新值,但这并不意味着它的内容不可变。

3. 性能考虑

一个常被忽略的事实是,在某些 JavaScript 引擎中,const 声明可能会有轻微的性能优势:

引擎可以确保这些值永远不会改变,从而可能进行常量折叠等优化。

四、实用的使用模式和最佳实践

1. 默认使用 const,必要时退回到 let

这种方式可以最大限度减少代码中的变量重新赋值,提高可读性和可维护性。

2. 解构赋值中的 let 和 const

被忽略的细节:函数参数解构本质上是 const 声明,不能重新赋值。

3. 循环中的 let vs const

被忽略的细节:for-of 和 for-in 循环中使用 const 是合法的,因为每次迭代都会创建新的绑定。

五、深入理解:let、const 的内部工作机制

理解 JavaScript 引擎如何处理这些声明,有助于避免常见陷阱:

// 简化的内部处理流程
function example() {
  // 1. 创建词法环境
  // 2. 对 let/const 声明进行"未初始化"标记(TDZ 开始)
  
  // console.log(x); // 如果取消注释,会报错:x 在 TDZ 中
  
  let x = 10; // x 从 TDZ 中解除,并赋值为 10
  
  if (true) {
    // 创建新的块级词法环境
    const y = 20;
    x = 30; // 可以访问外部 x
    // y 仅在此块中可用
  }
  
  // console.log(y); // 如果取消注释,会报错:y 不在此作用域
}

词法环境(Lexical Environment)和变量环境(Variable Environment)的区别是 JavaScript 引擎如何区分处理不同类型的声明。

责任编辑:赵宁宁 来源: JavaScript
相关推荐

2021-07-16 07:26:48

ES6javascript开发语言

2013-11-14 10:06:10

2019-06-06 15:49:53

多线程iOS开发操作

2024-04-26 00:10:23

Python代码开发

2019-01-16 08:05:56

2010-05-18 19:03:21

linux MySQL

2017-01-15 01:12:40

码农简历专业名词

2011-06-21 10:37:56

const

2011-07-20 10:06:54

CC++const

2010-04-30 10:47:26

Oracle Nvl函

2010-04-26 15:30:45

Oracle join

2010-05-27 14:47:14

MySQL INSER

2010-05-25 15:24:38

MySQL updat

2010-04-30 11:58:45

Oracle sequ

2010-04-30 17:58:55

Oracle trun

2010-05-31 11:13:00

MySQL Date函

2009-12-08 17:53:31

PHP const

2023-11-09 09:02:26

TypeScriptas const

2009-04-24 10:57:25

2020-05-17 16:19:59

JavaScript代码开发
点赞
收藏

51CTO技术栈公众号