JavaScript 表面上看起来很简单,但在幕后,发生了很多事情。今天,我们将探讨一些基本概念,例如执行上下文、提升、原始数据类型与非原始数据类型以及类型转换。如果您想编写更好、无错误的代码,这些对于理解它们至关重要。
全局执行上下文和词法环境
当您在浏览器中运行 JavaScript 文件时,代码会在调用堆栈中逐行执行。但是,在运行任何代码之前,都会创建一个全局执行上下文。该上下文设置了 this 和 window 对象。在 Node.js 中,window 相当于 global,比较两者,你会发现 window === global 返回 true。
每当你调用一个函数时,都会创建一个新的词法环境。全局执行上下文是第一个被创建的,其中定义的所有函数都可以访问其变量。这就是 JavaScript 作用域链的工作原理——您可以从函数内部访问外部(全局)作用域中的变量。
提升:变量和函数
JavaScript 有一种称为提升的机制,其中变量和函数在编译期间被“移动”到其作用域的顶部。其工作原理如下:
变量:用 var 声明的变量会被部分提升,这意味着您可以在初始化它们之前引用它们,但在到达它们初始化的行之前,它们的值将是未定义的。
函数:使用函数声明语法声明的函数是完全提升的,这意味着您甚至可以在代码中声明该函数之前调用该函数。
示例:
console.log(a); // undefined var a = 5; console.log(b); // Error: b is not defined let b = 10; hoistedFunction(); // Works! function hoistedFunction() { console.log('This function is hoisted!'); } notHoistedFunction(); // Error: notHoistedFunction is not a function var notHoistedFunction = function() { console.log('This function is not hoisted!'); }
如您所见,let 和 const 不像 var 那样被提升,并且函数表达式(如 notHoistedFunction)仅在运行时定义。
原始类型与非原始类型
JavaScript 有两种类型的数据:原始数据和非原始数据。
原始类型包括字符串、数字、布尔值、未定义、空、符号和bigint。它们是不可变的,这意味着它们的值不能改变。例如:
let x = 'hello'; x[0] = 'H'; // This won’t change the string, it stays 'hello'
非原始类型是对象、数组和函数。它们是可变的,并且它们的值可以更改,因为它们是通过引用传递的。例如:
let obj1 = { name: 'John' }; let obj2 = obj1; // Both obj1 and obj2 now reference the same object obj2.name = 'Doe'; console.log(obj1.name); // Outputs: Doe
为了避免修改原始对象,您可以使用 Object.assign() 或扩展运算符 (...) 创建浅表副本。对于复制嵌套对象的深层复制,请使用 JSON.parse() 和 JSON.stringify()。
示例代码片段:浅复制与深复制
// Shallow copy example let obj1 = { name: 'John', details: { age: 30 } }; let obj2 = { ...obj1 }; // Shallow copy obj2.details.age = 40; console.log(obj1.details.age); // Output: 40 (Shallow copy affects the original) // Deep copy example let obj3 = JSON.parse(JSON.stringify(obj1)); // Deep copy obj3.details.age = 50; console.log(obj1.details.age); // Output: 40 (Deep copy doesn’t affect the original)
类型转换与比较
JavaScript 是一种动态类型语言,这意味着您不必显式指定变量类型。然而,这有时会导致意外的行为,特别是在使用比较运算符时。
始终更喜欢使用三重等于 (===) 而不是双重等于 (==) 以避免类型强制。例如:
console.log(0 == '0'); // true (type coercion happens) console.log(0 === '0'); // false (no type coercion)
对于特殊情况,例如比较 NaN,请使用 Object.is(),因为 NaN === NaN 返回 false。
console.log(NaN === NaN); // false console.log(Object.is(NaN, NaN)); // true
JavaScript 的运行时和 Node.js
JavaScript 在单线程、同步运行时运行,这意味着它一次只能执行一项任务。这似乎是有限制的,但 JavaScript 通过使用 Web API 和回调队列来有效地处理异步任务。其工作原理如下:
当 JavaScript 遇到异步任务(如 setTimeout 或 HTTP 请求)时,它会将任务发送到 Web API。
调用堆栈继续执行剩余的代码。
异步任务完成后,会被添加到回调队列中,并在调用堆栈为空时执行。
Node.js 使用 V8 引擎和由 libuv 提供支持的非阻塞 I/O 系统,将此运行时扩展到服务器端。 Node.js 引入了单线程事件循环的思想,可以处理多个请求而不阻塞其他操作。
通过了解 JavaScript 如何处理执行上下文、提升、类型转换和异步任务,您将能够编写更清晰、更高效的代码。凭借 JavaScript 的动态特性,TypeScript 等工具可以通过提供静态类型检查来帮助您避免常见的陷阱,使您的代码做好生产准备。
以上是揭秘 JavaScript:了解执行上下文、提升和类型转换的详细内容。更多信息请关注PHP中文网其他相关文章!