理解 JavaScript、Node.js 和 TypeScript 的关系

Posted by Peter Dong on August 19, 2024

在现代 Web 开发领域,JavaScript、Node.js 和 TypeScript 构成了一个紧密相连又各具特色的技术生态系统。本文将从前端开发的历史演进角度,详细探讨这三者的定义、区别以及它们之间的关系。

1. JavaScript: 一切的起点

JavaScript 的诞生

JavaScript 诞生于 1995 年,由 Brendan Eich 在网景公司(Netscape)工作期间创造。它最初被设计为一种简单的脚本语言,用于在浏览器中操作网页元素和进行简单的客户端验证。

JavaScript 的标准化

1997 年,JavaScript 被提交给 ECMA International 进行标准化,形成了 ECMAScript 规范。这一标准确保了 JavaScript 在不同浏览器中的一致性实现。

ECMAScript: JavaScript 的核心

JavaScript 的核心语言部分,包括其语法基础类型函数,被称为 ECMAScript(简称 ES)。ECMAScript 是由 ECMA International 标准化组织制定的脚本语言规范。截至到 2024 年 8 月,最新的 ECMAScript 版本是 ECMAScript 2024 (ES15),这是 ECMAScript 标准的第 15 版。ECMAScript 标准每年都会更新一次,通常在每年的 6 月发布。

下面是 ES6(ECMAScript 2015) 特性的简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 使用箭头函数定义一个函数来计算两个数的和
const add = (a, b) => a + b;

// 调用函数并打印结果
console.log(add(2, 3)); // 输出:5

// 使用 let 和 const 定义变量
let name = 'Peter';
const age = 32;

console.log(`Name: ${name}, Age: ${age}`); // 输出:Name: Peter, Age: 32

// 使用模板字符串
const greeting = `Hello, ${name}!`;
console.log(greeting); // 输出:Hello, Peter!

// 创建一个对象并使用解构赋值提取属性
const person = {
  firstName: 'Peter',
  lastName: 'Dong',
  age: 32
};

const { firstName, lastName } = person;

console.log(`First Name: ${firstName}, Last Name: ${lastName}`); // 输出:First Name: Peter, Last Name: Dong

上面的这个示例展示了 ECMAScript 6 中的一些特性,包括箭头函数 (=>)letconst 关键字、模板字符串、以及对象的解构赋值

JavaScript 引擎:理解和执行代码

JavaScript 引擎是解释和执行 ECMAScript 代码的程序。最著名的 JavaScript 引擎包括:

  • V8:由 Google 开发,是 Chrome 浏览器和 Node.js 的 JavaScript 引擎。V8 因其高性能和高效的垃圾回收机制而著名。

  • SpiderMonkey:这是 Mozilla Firefox 浏览器使用的 JavaScript 引擎,也是第一个实现 JavaScript 的引擎。SpiderMonkey 支持多种高级功能,并且是 Firefox 扩展和其它 Mozilla 产品的核心部分。

  • JavaScriptCore(Nitro):由 Apple 开发,是 Safari 浏览器的 JavaScript 引擎。JavaScriptCore 在性能和安全性上表现出色,是 WebKit 项目的一部分。

  • Chakra:由 Microsoft 开发,是旧版 Microsoft Edge 浏览器的 JavaScript 引擎(基于 EdgeHTML 引擎)。新版 Edge 浏览器(基于 Chromium)则使用 V8 引擎。

这些引擎通常由更贴近机器语言的语言实现,比如:C++ 或 Rust 等,以确保高性能。

宿主环境:JavaScript 的执行环境 (执行上下文)

JavaScript 需要一个宿主环境来运行。最常见的宿主环境是 Web 浏览器,但不仅限于此。

  1. 浏览器环境

    JavaScript 最初是为浏览器而设计的,它可以直接在网页中运行,操作 DOM(文档对象模型)、处理事件、发送 HTTP 请求等。因此在浏览器中,JavaScript = ECMAScript + Web APIs(如 DOM, BOM).

    1
    2
    3
    4
    
     // 浏览器环境中的 JavaScript
     document.getElementById('myButton').addEventListener('click', () => {
         alert('Button clicked!');
     });
    
  2. Node.js 环境

    Node.js 是一个基于 V8 引擎的 JavaScript 运行时,它允许在服务器端运行 JavaScript 代码并提供了许多用于服务器端开发的内置模块,如 fs(文件系统)、http、net 等,因此在 Node.js 中,JavaScript = ECMAScript + Node.js APIs

    1
    2
    3
    4
    5
    6
    
     // NodeJS 环境中的 JavaScript
     const fs = require('fs');
     fs.readFile('example.txt', 'utf8', (err, data) => {
         if (err) throw err;
         console.log(data);
     });
    

运行环境的影响

不同的 JavaScript 运行环境提供的 API 和功能有所不同,因此在编写 JavaScript 代码时,必须考虑代码的运行环境。例如,浏览器中的代码无法直接访问文件系统,而在 Node.js 中则可以。

2. Node.js: 服务器端的 JavaScript

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。它将 JavaScript 带出浏览器,使其能够在服务器端运行。

NodeJS 的特点

  1. 异步 I/O:

    异步 I/O 是 Node.js 的核心特性之一,允许程序在执行 I/O 操作时不阻塞主线程。例如,读取文件时使用异步操作,而不是等待文件读取完毕。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     const fs = require('fs');
    
     // 异步读取文件
     fs.readFile('example.txt', 'utf8', (err, data) => {
         if (err) {
             console.error('Error reading file:', err);
             return;
         }
         console.log('File content:', data);
     });
    
     console.log('This will run immediately after calling readFile, not after the file is read.');
    

    在这个例子中,fs.readFile 是一个异步操作,它不会阻塞主线程。即使文件读取需要一些时间,console.log('This will run immediately...') 仍然会立即执行。

  2. 事件驱动:

    Node.js 使用事件循环来管理并发操作。这意味着 Node.js 能够通过事件驱动模型来处理大量的并发连接,而不需要为每个连接创建一个新的线程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
     const http = require('http');
    
     // 创建一个 HTTP 服务器
     const server = http.createServer((req, res) => {
         if (req.url === '/') {
             res.write('Hello, World!');
             res.end();
         }
     });
    
     // 服务器监听端口 3000
     server.listen(3000, () => {
         console.log('Server is listening on port 3000');
     });
    
     // 在服务器运行期间,可以处理多个请求
     server.on('request', (req, res) => {
         console.log(`Received request for ${req.url}`);
     });
    

    在这个例子中,HTTP 服务器通过事件驱动模型来处理客户端请求。server.on('request', ...) 用来监听每个传入的请求,而不会阻塞其他请求的处理。

  3. 跨平台:

    Node.js 可以在多种操作系统上运行,如 Windows、Linux 和 macOS。这里的示例展示了一个跨平台的脚本,能够在不同操作系统上执行并输出当前的操作系统类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
     const os = require('os');
    
     // 获取并输出操作系统类型
     console.log(`This script is running on: ${os.type()}`);
    
     // 跨平台路径处理示例
     const path = require('path');
     const filePath = path.join(__dirname, 'example.txt');
    
     console.log(`The file path is: ${filePath}`);
    

    在这个例子中,os.type() 返回当前操作系统的类型,无论脚本在哪个平台上运行,它都能正确识别。path.join 也能跨平台处理文件路径,使得代码能够在不同的操作系统上正常运行。

3. TypeScript: JavaScript 的超集

TypeScript 是 Microsoft 开发的编程语言,它是 JavaScript 的超集,也添加了许多特性,比如:静态类型检查,类型推断,接口(Interfaces), 类和面向对象编程等

TypeScript 的特点

  1. 静态类型检查: TypeScript 引入了静态类型系统,允许在编译时捕获类型相关的错误。

    1
    2
    3
    4
    5
    6
    
     let name: string = "Peter";
     let age: number = 32;
     let isStudent: boolean = false;
    
     // 错误示例
     name = 42; // 错误:不能将类型 "number" 分配给类型 "string"
    
  2. 接口: 允许你定义复杂的类型结构,提高代码的可读性和可维护性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
     interface Person {
         name: string;
         age: number;
     }
    
     function greet(person: Person) {
         return `Hello, ${person.name}! You are ${person.age} years old.`;
     }
    
     let john: Person = { name: "Peter", age: 32 };
     console.log(greet(john)); // 输出:Hello, Peter! You are 32 years old.
    
  3. : 支持基于类的面向对象编程。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
     class Animal {
         constructor(public name: string) {}
         move(distanceInMeters: number = 0) {
             console.log(`${this.name} moved ${distanceInMeters}m.`);
         }
     }
    
     class Dog extends Animal {
         bark() {
             console.log('Woof! Woof!');
         }
     }
    
     const dog = new Dog("Pete");
     dog.bark(); // 输出:Woof! Woof!
     dog.move(10); // 输出:Pete moved 10m.
    
  4. 泛型: 泛型允许你编写可重用的、类型安全的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     function identity<T>(arg: T): T {
         return arg;
     }
    
     let output1 = identity<string>("myString");
     let output2 = identity<number>(100);
    
     console.log(output1); // 输出:myString
     console.log(output2); // 输出:100
    
  5. 枚举: 枚举允许你定义一组命名常量,使代码更具可读性。

    1
    2
    3
    4
    5
    6
    7
    8
    
     enum Color {
         Red,
         Green,
         Blue
     }
    
     let c: Color = Color.Green;
     console.log(c); // 输出:1
    
  6. 高级类型: TypeScript 提供了多种高级类型,如联合类型、交叉类型。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
     // 联合类型
     type StringOrNumber = string | number;
    
     function printId(id: StringOrNumber) {
         console.log(`Your ID is: ${id}`);
     }
    
     printId(101); // Your ID is: 101
     printId("202"); // Your ID is: 202
    
     // 交叉类型
     type Employee = {
         name: string;
         id: number;
     };
    
     type Manager = {
         department: string;
     };
    
     type ManagerWithEmployeeInfo = Employee & Manager;
    
     let manager: ManagerWithEmployeeInfo = {
         name: "Peter",
         id: 123,
         department: "Salesforce"
     };
    

4. 三者之间的关系

JavaScript

  • 角色:
    • JavaScript 是一种广泛使用的编程语言,最初专为网页开发设计,运行在浏览器中。
    • 随着时间的推移,JavaScript 成为了一个通用的编程语言,不仅可以在客户端(浏览器)中运行,还可以在服务器端、桌面应用程序、移动应用程序等环境中使用。
  • 特性:
    • JavaScript 是一种动态、弱类型的语言,支持事件驱动、非阻塞的异步编程模型。

TypeScript

  • 角色:
    • TypeScript 是 JavaScript 的一个超集,由微软开发。
    • 它扩展了 JavaScript,增加了静态类型系统、面向对象编程特性(如接口、泛型)等特性。
  • 关系:
    • TypeScript 编写的代码在编译后会被转译为纯 JavaScript,这样可以在任何支持 JavaScript 的环境中运行。
    • TypeScript 的主要目的是增强开发体验,尤其是在大型项目中,通过提供静态类型检查来减少运行时错误。
  • 特性:
    • TypeScript 保留了 JavaScript 的所有功能,并添加了编译时类型检查、接口、枚举、泛型等特性。

Node.js

  • 角色:
    • Node.js 是一个 JavaScript 运行时环境,允许 JavaScript 在服务器端执行。
    • 它基于 Google 的 V8 引擎构建,使得 JavaScript 可以在不依赖浏览器的情况下运行,并与文件系统、数据库、网络等进行交互。
  • 关系:
    • Node.js 使用 JavaScript 作为主要编程语言,因此你可以在 Node.js 环境中编写和执行 JavaScript 代码。
    • 由于 TypeScript 是 JavaScript 的超集,你也可以在 Node.js 中使用 TypeScript,前提是将 TypeScript 编译为 JavaScript 或通过支持 TypeScript 的工具直接运行。
  • 特性:
    • Node.js 提供了异步 I/O、事件驱动、模块系统等服务器端开发需要的特性,适用于构建高性能、可扩展的网络应用程序。

总结它们的关系

  1. JavaScript 是一种编程语言,TypeScript 和 Node.js 都依赖于 JavaScript。
  2. TypeScript 是 JavaScript 的一个超集,增加了静态类型检查和其他编程特性,但最终编译为 JavaScript 代码在不同环境中运行。
  3. Node.js 是一个 JavaScript 运行时环境,使得 JavaScript 和 TypeScript 可以在服务器端运行,超出了传统的浏览器范围。

通过这三者的结合,你可以使用 JavaScript/TypeScript 编写客户端和服务器端代码,构建完整的应用程序。TypeScript 提供了更好的开发体验和错误检查,而 Node.js 提供了在服务器端执行 JavaScript 的能力。

5. 版本兼容性工具

Babel: JavaScript 的编译器

Babel 是一个广泛使用的 JavaScript 编译器,它可以将最新版本的 JavaScript 代码转换为向后兼容的 JavaScript 版本,以便在旧版浏览器或环境中运行

基本配置

最基本的 Babel 配置通常包括 @babel/preset-env,这是一个智能预设,可以根据你的目标环境自动确定需要的 Babel 插件。

1
2
3
{
  "presets": ["@babel/preset-env"]
}

针对特定浏览器的配置

你可以指定目标浏览器,Babel 会根据这些目标自动确定需要的转换。

1
2
3
4
5
6
7
8
9
10
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "chrome": "58",
        "ie": "11"
      }
    }]
  ]
}

React 项目配置

如果你正在开发 React 应用,你可能需要添加 @babel/preset-react

1
2
3
4
5
6
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

TypeScript 项目配置

对于 TypeScript 项目,你需要添加 @babel/preset-typescript

1
2
3
4
5
6
{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript"
  ]
}

结论

JavaScript、NodeJS 和 TypeScript 形成了一个相互补充的技术生态系统。JavaScript 作为基础,在客户端和服务器端都发挥着重要作用。Node.js 将 JavaScript 的应用扩展到服务器端,而 TypeScript 则通过静态类型系统增强了 JavaScript 的开发体验。理解这三者之间的关系,对于充分利用现代 Web 开发技术栈至关重要。

参考资料


Buy Me a Coffee