MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript 与 JavaScript 的关系 

2022-12-274.8k 阅读

语法层面的关系

TypeScript 是 JavaScript 的超集

从语法角度来看,TypeScript 是 JavaScript 的超集,这意味着任何有效的 JavaScript 代码都是有效的 TypeScript 代码。例如,下面这段简单的 JavaScript 代码:

function greet(name) {
    return 'Hello, ' + name;
}
console.log(greet('World'));

在 TypeScript 中同样可以直接使用,无需做任何修改:

function greet(name) {
    return 'Hello, ' + name;
}
console.log(greet('World'));

这种兼容性使得 JavaScript 开发者可以轻松地过渡到 TypeScript,无需完全抛弃现有的代码库和开发经验。TypeScript 在 JavaScript 的基础语法上进行了扩展,添加了类型系统等特性。例如,我们可以为上述函数添加类型注解:

function greet(name: string): string {
    return 'Hello, ' + name;
}
console.log(greet('World'));

这里,name: string 表示 name 参数的类型是字符串,string 则表示函数的返回值类型也是字符串。这种类型注解在 JavaScript 中是不存在的,是 TypeScript 对 JavaScript 语法的扩展。

类型系统扩展

  1. 基础类型 TypeScript 增加了丰富的基础类型。除了 JavaScript 已有的 numberstringboolean 等,还引入了 nullundefinedvoidnever 等。
  • void 表示没有任何类型,通常用于函数没有返回值的情况:
function logMessage(message: string): void {
    console.log(message);
}
  • never 表示永远不会有值的类型,常用于函数抛出异常或无限循环的情况:
function throwError(message: string): never {
    throw new Error(message);
}
  1. 类型别名与接口 TypeScript 允许开发者通过类型别名和接口来定义复杂类型。
  • 类型别名:可以为任意类型创建一个新的名字。例如:
type User = {
    name: string;
    age: number;
};
function printUser(user: User) {
    console.log(`Name: ${user.name}, Age: ${user.age}`);
}
const myUser: User = { name: 'John', age: 30 };
printUser(myUser);
  • 接口:主要用于定义对象的形状。例如:
interface User {
    name: string;
    age: number;
}
function printUser(user: User) {
    console.log(`Name: ${user.name}, Age: ${user.age}`);
}
const myUser: User = { name: 'John', age: 30 };
printUser(myUser);

虽然类型别名和接口在很多情况下功能相似,但也有一些区别。接口只能用于定义对象类型,而类型别名可以用于任何类型,包括联合类型、交叉类型等。例如:

type StringOrNumber = string | number;
  1. 泛型 泛型是 TypeScript 中一个强大的特性,它允许开发者在定义函数、类或接口时使用类型参数。例如,我们可以定义一个通用的 identity 函数:
function identity<T>(arg: T): T {
    return arg;
}
const result1 = identity<number>(5);
const result2 = identity<string>('hello');

这里的 <T> 就是类型参数,T 可以代表任何类型。通过泛型,代码可以在保持类型安全的同时,提高复用性。在 JavaScript 中,没有这样直接的泛型概念,开发者通常需要通过鸭子类型等方式来模拟类似的功能,但这种方式缺乏类型检查的严格性。

编译与运行机制

TypeScript 的编译过程

TypeScript 代码不能直接在浏览器或 Node.js 环境中运行,需要经过编译转化为 JavaScript 代码。TypeScript 编译器(tsc)负责这个转换过程。例如,我们有一个 hello.ts 文件:

function greet(name: string): string {
    return 'Hello, ' + name;
}
console.log(greet('World'));

在命令行中运行 tsc hello.ts,会生成对应的 hello.js 文件:

function greet(name) {
    return 'Hello, ' + name;
}
console.log(greet('World'));

可以看到,TypeScript 编译器移除了类型注解,将 TypeScript 代码转化为了普通的 JavaScript 代码。这个编译过程不仅仅是简单的移除类型注解,还会进行类型检查。如果我们在 hello.ts 中出现类型错误,比如这样:

function greet(name: string): string {
    return 'Hello, ' + name;
}
console.log(greet(123)); // 错误:参数类型应为 string

运行 tsc 时会报错:Argument of type 'number' is not assignable to parameter of type'string'.。只有通过了类型检查,TypeScript 代码才能成功编译为 JavaScript 代码。

编译选项的影响

TypeScript 编译器提供了丰富的编译选项,可以根据项目的需求进行配置。常见的编译选项有 targetmodulestrict 等。

  1. target 选项:用于指定编译后的 JavaScript 版本。例如,target: 'es5' 会将 TypeScript 代码编译为 ES5 语法的 JavaScript 代码,这样可以保证在较旧的浏览器中也能运行。而 target: 'es6' 则会编译为 ES6 语法的 JavaScript 代码,利用 ES6 的新特性。
  2. module 选项:决定了编译后的 JavaScript 模块系统。常见的值有 commonjses6umd 等。如果项目是在 Node.js 环境中运行,通常会选择 commonjs 模块系统,因为 Node.js 默认使用的就是 commonjs。例如:
// math.ts
export function add(a: number, b: number): number {
    return a + b;
}

module 选项设置为 commonjs 时,编译后的 math.js 文件如下:

exports.add = function (a, b) {
    return a + b;
};
  1. strict 选项:开启严格类型检查模式。当 strict 设置为 true 时,TypeScript 编译器会进行最严格的类型检查,例如要求所有变量都必须初始化,不允许隐式的类型转换等。如果项目对类型安全要求较高,建议开启 strict 选项。

JavaScript 的运行机制

JavaScript 是一种解释型语言,通常在浏览器或 Node.js 环境中直接运行。在浏览器中,JavaScript 代码会被 JavaScript 引擎(如 Chrome 的 V8 引擎)解析和执行。例如,我们有一个简单的 index.html 文件,包含如下 JavaScript 代码:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>JavaScript Example</title>
</head>

<body>
    <script>
        function greet(name) {
            return 'Hello, ' + name;
        }
        console.log(greet('World'));
    </script>
</body>

</html>

当浏览器加载这个 HTML 文件时,会逐行解析和执行 <script> 标签内的 JavaScript 代码。在 Node.js 环境中,同样是通过 Node.js 的 JavaScript 运行时来执行 JavaScript 代码。例如,我们有一个 app.js 文件:

function greet(name) {
    return 'Hello, ' + name;
}
console.log(greet('World'));

在命令行中运行 node app.js,Node.js 会执行这段代码并输出结果。与 TypeScript 不同,JavaScript 没有编译阶段,代码直接在运行时被解释执行,这也导致 JavaScript 在运行时才会暴露一些类型错误等问题,而 TypeScript 通过编译阶段的类型检查可以提前发现这些问题。

应用场景与优势对比

JavaScript 的应用场景

  1. 前端开发 JavaScript 是前端开发的核心语言。在网页中,JavaScript 可以实现交互效果,操作 DOM(文档对象模型)来动态更新页面内容。例如,实现一个按钮点击切换文本的功能:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>JavaScript DOM Example</title>
</head>

<body>
    <button id="myButton">Click me</button>
    <p id="myText">Original text</p>
    <script>
        const button = document.getElementById('myButton');
        const text = document.getElementById('myText');
        button.addEventListener('click', function () {
            text.textContent = 'Text changed';
        });
    </script>
</body>

</html>

几乎所有的前端框架,如 React、Vue、Angular 等,都是基于 JavaScript 构建的。这些框架利用 JavaScript 的灵活性和动态性,帮助开发者更高效地构建复杂的用户界面。 2. 后端开发 Node.js 的出现使得 JavaScript 可以用于后端开发。Node.js 基于 Chrome 的 V8 引擎,提供了丰富的内置模块和异步 I/O 能力。例如,使用 Node.js 搭建一个简单的 HTTP 服务器:

const http = require('http');
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content - Type', 'text/plain');
    res.end('Hello, World!');
});
const port = 3000;
server.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

Node.js 在处理高并发、I/O 密集型任务方面表现出色,被广泛应用于 Web 服务器、实时应用(如聊天应用、在线游戏等)的开发。 3. 脚本自动化 JavaScript 可以用于编写脚本,实现自动化任务。例如,在浏览器中可以使用 Greasemonkey 或 Tampermonkey 等插件编写用户脚本,实现网页的自定义功能,如自动填写表单、屏蔽广告等。在服务器端,也可以使用 JavaScript 脚本来自动化一些系统管理任务,如文件操作、进程管理等。

TypeScript 的应用场景

  1. 大型项目开发 在大型项目中,代码的可维护性和可扩展性至关重要。TypeScript 的类型系统可以帮助开发者更好地理解代码结构,减少因类型错误导致的 bug。例如,在一个大型的企业级前端项目中,可能有多个开发人员协作开发不同的模块。使用 TypeScript 可以明确各个模块的接口和数据类型,避免因数据类型不一致而产生的问题。假设我们有一个用户管理模块,使用 TypeScript 可以这样定义用户数据的接口:
interface User {
    id: number;
    name: string;
    email: string;
}
function getUserById(id: number): User | null {
    // 模拟从数据库获取用户数据
    const users: User[] = [
        { id: 1, name: 'John', email: 'john@example.com' },
        { id: 2, name: 'Jane', email: 'jane@example.com' }
    ];
    return users.find(user => user.id === id) || null;
}

这样,其他开发人员在调用 getUserById 函数时,能清楚地知道函数的参数类型和返回值类型,减少出错的可能性。 2. 与强类型语言交互 当项目需要与其他强类型语言(如 Java、C# 等)进行交互时,TypeScript 可以作为桥梁。例如,在一个全栈项目中,后端使用 Java 开发,前端使用 TypeScript 开发。通过在前端使用 TypeScript 进行严格的类型定义,可以更好地与后端的接口进行对接。假设后端提供了一个获取用户列表的 RESTful API,前端可以使用 TypeScript 定义对应的接口和数据类型:

interface User {
    id: number;
    name: string;
    email: string;
}
async function getUsers(): Promise<User[]> {
    const response = await fetch('/api/users');
    if (!response.ok) {
        throw new Error('Network response was not ok');
    }
    return response.json();
}

这种方式可以在前端对数据进行类型验证,确保与后端交互的数据格式正确,提高项目的稳定性。 3. 代码重构与升级 在对现有 JavaScript 项目进行重构或升级时,TypeScript 可以提供很大的帮助。通过逐步将 JavaScript 代码转换为 TypeScript 代码,并利用 TypeScript 的类型检查,可以发现潜在的问题,提高代码质量。例如,在一个老旧的 JavaScript 项目中,有一个函数 calculateTotal 用于计算购物车商品的总价:

function calculateTotal(items) {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        total += items[i].price * items[i].quantity;
    }
    return total;
}

将其转换为 TypeScript 代码:

interface CartItem {
    price: number;
    quantity: number;
}
function calculateTotal(items: CartItem[]): number {
    let total = 0;
    for (let i = 0; i < items.length; i++) {
        total += items[i].price * items[i].quantity;
    }
    return total;
}

在这个过程中,我们可以发现如果 items 数组中的元素没有 pricequantity 属性,TypeScript 会报错,从而帮助我们修复潜在的问题。

TypeScript 相对 JavaScript 的优势

  1. 类型安全 TypeScript 的类型系统可以在编译阶段发现类型错误,而 JavaScript 只有在运行时才会暴露这些问题。例如:
function addNumbers(a: number, b: number): number {
    return a + b;
}
const result = addNumbers(5, '10'); // 编译时错误:参数类型不匹配

在 JavaScript 中,同样的代码不会在语法层面报错,而是在运行时抛出 NaN 的结果:

function addNumbers(a, b) {
    return a + b;
}
const result = addNumbers(5, '10');
console.log(result); // 输出 NaN
  1. 代码可读性与可维护性 类型注解和接口定义使得代码的意图更加清晰。其他开发人员在阅读代码时,可以更容易地理解函数的参数和返回值类型,以及对象的结构。例如:
interface Employee {
    name: string;
    age: number;
    position: string;
}
function printEmployee(employee: Employee) {
    console.log(`Name: ${employee.name}, Age: ${employee.age}, Position: ${employee.position}`);
}
const myEmployee: Employee = { name: 'Alice', age: 25, position: 'Developer' };
printEmployee(myEmployee);

相比之下,JavaScript 代码可能需要更多的注释来达到类似的清晰度:

// 定义一个员工对象,包含name、age和position属性
function printEmployee(employee) {
    console.log(`Name: ${employee.name}, Age: ${employee.age}, Position: ${employee.position}`);
}
const myEmployee = { name: 'Alice', age: 25, position: 'Developer' };
printEmployee(myEmployee);
  1. 工具支持 TypeScript 得到了众多编辑器和开发工具的良好支持。例如,在 Visual Studio Code 中,TypeScript 代码具有智能代码补全、类型提示、错误检查等功能,大大提高了开发效率。而 JavaScript 在这些方面的支持相对较弱,特别是在大型项目中,缺乏类型信息使得编辑器难以提供有效的代码提示和检查。

生态系统与社区支持

JavaScript 的生态系统

  1. NPM 与包管理 JavaScript 的包管理主要依赖于 NPM(Node Package Manager)。NPM 是世界上最大的开源软件注册表,拥有数以百万计的开源包。开发者可以通过 npm install 命令轻松安装各种依赖包。例如,要安装流行的 lodash 库,只需要在项目目录下运行 npm install lodash。然后在代码中就可以引入使用:
const _ = require('lodash');
const numbers = [1, 2, 3, 4, 5];
const sum = _.sum(numbers);
console.log(sum);

除了 NPM,还有 Yarn 等包管理器,它们在功能上与 NPM 类似,但在性能和一些特性上有所不同。例如,Yarn 支持并行安装依赖包,在安装速度上可能更快。 2. 前端框架与库 JavaScript 拥有丰富的前端框架和库,如 React、Vue、Angular 等。

  • React:由 Facebook 开发,采用虚拟 DOM 和组件化的开发模式,使得构建高性能的用户界面变得容易。例如,一个简单的 React 组件:
import React from'react';
import ReactDOM from'react - dom';
function App() {
    return <div>Hello, React!</div>;
}
ReactDOM.render(<App />, document.getElementById('root'));
  • Vue:是一个渐进式的 JavaScript 框架,易于上手,适合快速开发中小型项目。例如,一个简单的 Vue 实例:
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF - 8">
    <title>Vue Example</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        {{ message }}
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                message: 'Hello, Vue!'
            }
        });
    </script>
</body>

</html>
  • Angular:是一个全面的 JavaScript 框架,由 Google 维护,提供了一套完整的解决方案,包括模板、依赖注入、路由等。例如,一个简单的 Angular 组件:
import { Component } from '@angular/core';
@Component({
    selector: 'app - root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})
export class AppComponent {
    title = 'Hello, Angular!';
}
  1. 后端框架与库 在后端开发方面,Node.js 有 Express、Koa 等框架。
  • Express:是 Node.js 中最流行的 Web 框架,提供了简单的路由系统和中间件支持。例如,搭建一个简单的 Express 服务器:
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
    res.send('Hello, Express!');
});
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  • Koa:是一个轻量级的 Node.js 框架,以其优雅的异步处理和中间件机制而受到欢迎。例如,一个简单的 Koa 应用:
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx) => {
    ctx.body = 'Hello, Koa!';
});
const port = 3000;
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

TypeScript 的生态系统

  1. 与 JavaScript 生态的融合 TypeScript 与 JavaScript 的生态系统高度融合。由于 TypeScript 是 JavaScript 的超集,几乎所有的 JavaScript 包都可以在 TypeScript 项目中使用。同时,许多流行的 JavaScript 框架和库也提供了对 TypeScript 的支持。例如,React 从 v16.8 版本开始,官方文档就开始推荐使用 TypeScript 进行开发。Vue 也在 3.0 版本中对 TypeScript 提供了更好的支持,包括类型声明文件的完善等。在后端,Express 和 Koa 等框架同样可以在 TypeScript 项目中使用,只需要进行适当的配置。例如,使用 TypeScript 编写 Express 应用:
import express from 'express';
const app = express();
const port = 3000;
app.get('/', (req, res) => {
    res.send('Hello, Express with TypeScript!');
});
app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
  1. 类型声明文件 为了让 TypeScript 能够正确识别 JavaScript 库的类型,社区提供了类型声明文件(.d.ts 文件)。例如,对于 lodash 库,在安装时可以同时安装其类型声明文件 @types/lodash。这样,在 TypeScript 项目中使用 lodash 时就会有类型提示和检查:
import _ from 'lodash';
const numbers: number[] = [1, 2, 3, 4, 5];
const sum: number = _.sum(numbers);

如果没有安装类型声明文件,TypeScript 可能无法准确识别 lodash 函数的参数和返回值类型。此外,一些库本身就自带类型声明文件,例如 axios,在安装 axios 后,TypeScript 项目中就可以直接使用其类型定义:

import axios from 'axios';
axios.get('/api/data').then(response => {
    console.log(response.data);
});
  1. TypeScript 专属工具与库 除了与 JavaScript 共享的生态资源,TypeScript 还有一些专属的工具和库。例如,ts - node 可以让开发者在 Node.js 环境中直接运行 TypeScript 代码,而无需先编译。type - goblin 是一个用于生成 TypeScript 类型定义的工具,可以根据 JavaScript 代码自动生成对应的类型声明。在测试方面,jest 是一个流行的 JavaScript 测试框架,也对 TypeScript 提供了很好的支持,通过配置可以轻松编写和运行 TypeScript 测试用例。

社区支持

  1. JavaScript 社区 JavaScript 拥有庞大且活跃的社区。Stack Overflow 上有大量关于 JavaScript 的问题和答案,开发者遇到问题时很容易找到解决方案。各种论坛、博客也不断分享 JavaScript 的最新技术和开发经验。例如,在前端开发领域,Smashing Magazine 经常发布关于 JavaScript 框架、性能优化等方面的文章。此外,每年还有许多 JavaScript 相关的技术大会,如 JSConf、Node.js Conf 等,汇聚了全球的开发者,分享最新的技术趋势和实践经验。
  2. TypeScript 社区 TypeScript 的社区也在不断壮大。官方的 TypeScript 文档非常详细,为开发者提供了全面的学习资源。在 GitHub 上,TypeScript 项目拥有大量的星标和贡献者,开发者可以直接参与到 TypeScript 的开发和改进中。同时,社区也有许多关于 TypeScript 的教程、博客和开源项目。例如,在 Medium 上有很多高质量的 TypeScript 技术文章,涵盖从基础到高级的各种主题。一些社区驱动的项目,如 DefinitelyTyped,致力于为 JavaScript 库提供高质量的类型声明文件,进一步推动了 TypeScript 在整个 JavaScript 生态系统中的应用。