JavaScript & TypeScript
JavaScript & TypeScript ECMAScript(简称 ES)是一部由 Ecma International 制定的脚本语言规范,文件编号 ECMA 262 。它定义了语言的语法、类型系统、内建对象、错误模型、并发抽象(Promise Jobs)等核心语义,用来保证不同实现之间可以运行同一段脚本代码。
本文目录 29 个章节
本文介绍了JaavScript与TypeScript的基础概念以及互相关系。介绍了两者的核心语法以及差异,并说明 TS 编译时删除类型、按环境保留或降级语法。
JavaScript & TypeScript
ECMAScript(简称 ES)是一部由 Ecma International 制定的脚本语言规范,文件编号 ECMA-262。它定义了语言的语法、类型系统、内建对象、错误模型、并发抽象(Promise Jobs)等核心语义,用来保证不同实现之间可以运行同一段脚本代码。
- 1995 年 Brendan Eich 在 Netscape 浏览器中实现了早期的 LiveScript/JavaScript。
- 为了避免与 Sun 的“Java”商标纠纷,1996 年 Netscape 把这门语言送交标准机构 ECMA,双方同意用 “ECMAScript” 作为标准名称,浏览器实现继续对外叫 “JavaScript”。
ECMAScript 是 JavaScript 的规范。
| 维度 | JavaScript | TypeScript |
|---|---|---|
| 核心定位 | ECMAScript 标准的脚本语言,动态类型、解释执行 | JavaScript 的静态类型超集。在 JS 语法之上加入类型系统、装饰器、枚举、命名空间等,并在编译期做类型检查 |
| 设计目标 | 浏览器脚本、通用运行时语言 | JS 的_静态类型超集_,为中大型项目提供类型安全 |
| 运行方式 | 解释 / JIT 执行 | 必须先编译为 JS (tsc, esbuild, vite, webpack…) |
| 更新节奏 | ECMAScript 年度规范 (ES2024…) | TS 每月/季度发版(当前 5.8, 2025-03) |
| 生态依赖 | 浏览器、Node.js、Bun… | 与 JS 共用生态,编译后 100 % 兼容 |
代码结构与模块系统
| 主题 | JavaScript | TypeScript |
|---|---|---|
| 文件扩展名 | .js, .mjs, .cjs |
.ts, .tsx (React JSX) |
| 顶级模块化 | ESM import/export、CommonJS require/module.exports |
完全同 JS;编译器可输出多种模块格式 |
tsconfig.json |
— | 描述编译目标 (target)、模块格式 (module)、路径别名等 |
语法与差异
1. 代码结构与模块系统
1.1 文件后缀
- JS:
*.js/*.mjs/*.cjs - TS:
*.ts/*.tsx(含 React JSX)
1.2 模块化语法
- ES Modules →
import/export - CommonJS →
require/module.exports - TS 与 JS 写法一致,但可通过
tsconfig.json选择输出格式
1.3 tsconfig.json 要点
{
"target": "es2022", // 语法下限
"module": "esnext", // 模块格式
"strict": true, // 全面类型严格模式
"paths": { "@/*": ["src/*"] }
}
2. 基础语法
2.1 变量与作用域
// JavaScript
var oldVar = 1; // 函数级作用域
let count = 42; // 块级作用域
const PI = 3.14; // 只读
// TypeScript(加类型)
let count: number = 42;
2.2 原始类型
- JS 运行时:
number,string,boolean,undefined,null,symbol,bigint - TS 静态补充:
any,unknown,never,void
2.3 运算符 / 流程控制
- 与 C 家族基本一致
- TS 仅做 “类型是否合理” 检查,不改变写法
3. 函数与高阶能力
// JS:无类型限制
function add(a, b) {
return a + b;
}
// TS:显式标注
function add(a: number, b: number): number {
return a + b;
}
// 默认 / 剩余参数
function greet(name = 'world', ...extras) {}
// 箭头函数固定 this
const inc = (x: number) => x + 1;
// 泛型(TS 专属)
function identity<T>(value: T): T {
return value;
}
4. 面向对象(类机制)
4.1 类与实例字段
class Animal {
// ES2022 公有字段写法
speed = 0;
// ① 运行期真正私有
#id: number;
// ② 仅编译期私有(旧环境友好)
private name: string;
constructor(name: string, id: number) {
this.name = name;
this.#id = id;
}
move(distance: number = 0) {
this.speed = distance;
console.log(`${this.name} moved ${distance}m`);
}
}
4.2 高级特性(TS 增强)
- 访问修饰符:
public(默认) /private/protected/readonly - 抽象类 & 抽象方法:
abstract class Base { abstract foo(): void } - 接口契约:
interface Shape { area(): number } - 参数属性速写:
constructor(private id: number) {} - 泛型类:
class Box<T> { value: T }
5. 类型系统(TS 专属精华)
// 联合 & 交叉
type ID = number | string;
type Position = { x: number } & { y: number };
// 条件类型
type IsString<T> = T extends string ? 'yes' : 'no';
// 映射类型
type Readonly<T> = { readonly [K in keyof T]: T[K] };
// 模板字面量类型
type Hex = `#${string}`;
6. 异步编程
// Promise
function fetchData(): Promise<User[]> { ... }
// async / await
async function main() {
const list = await fetchData();
}
// Axios 响应体泛型
axios.get<User[]>('/api/user');
10. 常用参考
- TypeScript Handbook https://www.typescriptlang.org/docs/handbook/intro.html
- ECMAScript 提案追踪 https://tc39.es
- TS Playground 在线实验 https://www.typescriptlang.org/play
其他特性
对象字面量
用一对花括号 { … } 当场写出 键 → 值 的映射,马上得到一个对象实例,而 不 需要先声明类或构造函数。
const user = {
id: 42, // 数值字段
name: "Perrin", // 字符串字段
isAdmin: true, // 布尔字段
sayHi() { // 函数字面量(方法)
console.log(`Hi, I’m ${this.name}`);
}
};
- 运行期可增删属性
const p = { x:1, y:2 };
p.z = 3; delete p.x;
- 字段值可为函数
do: () => {}
| 写法 | 例子 | 说明 |
|---|---|---|
| 基本键值对 | { a: 1, b: 2 } |
冒号左边是键、右边是任何表达式 |
| 简写属性 | { a, b } |
当变量名与键名相同时写一次即可 |
| 方法定义简写 | { calc(x) { return x*2 } } |
等价于 calc: function(x){…} |
| 计算得出的键名 | { [prefix + id]: val } |
方括号里写表达式,求值后当键 |
| 解构合并 | { ...base, extra: 1 } |
扩展运算符 ... 复制字段 |
常见枚举方式
for (const k in obj) { ... } // 可遍历原型链 & 可枚举属性
Object.keys(obj) // 自有可枚举键数组
Object.getOwnPropertyNames(obj) // 包含不可枚举
与 类 (class) 的关系
class语法本质是语法糖:编译后仍然生成 函数 (constructor) 与 原型对象。- 对象字面量适合“一次性数据包”或“配置表”;若需要多个实例共享方法、属性访问控制,则用
class。
闭包
定义 闭包是指函数与其创建时的词法环境(Lexical Environment)共同组成的实体。该环境记录了函数在定义位置能够访问的所有变量(包括参数、本地变量及外层作用域变量)。
词法环境与环境记录
- 每次执行上下文(函数调用、全局、Eval)会创建一个环境记录,存放该上下文的所有标识符绑定。
- 闭包中的函数对象会持有对其所属环境记录的引用,即使外层函数已返回,环境记录也不会被 GC 回收。
多闭包共享变量 同一词法作用域内定义的多个闭包,都会引用同一个环境记录,从而可读写同一份变量。修改该变量会影响所有引用它的闭包。
与 C 语言对比
- C 语言无内建闭包;要跨函数共享状态,需使用全局/静态变量或显式传递指针/结构体。
- JavaScript 通过闭包实现私有化且可多函数共享的状态管理,无需暴露全局。
示例
// 外层函数,返回两个闭包
function createPair() {
let shared = 0; // 环境记录中的共享变量
function inc() {
shared += 1;
console.log('inc ->', shared);
}
function dec() {
shared -= 1;
console.log('dec ->', shared);
}
return { inc, dec };
}
const { inc, dec } = createPair();
inc(); // inc -> 1
inc(); // inc -> 2
dec(); // dec -> 1
dec(); // dec -> 0
shared变量存在于createPair的环境记录中。inc和dec两个闭包都捕获并共享同一份shared。- 对
shared的增减对另一个闭包可见,实现了多闭包之间的状态同步。
JS与TS面向对象差异与编译方法
class语法差异
1. JavaScript:原型链 + 语法糖
class Animal {
constructor(name) {
this.name = name; // 实例字段
}
move(dist = 0) { // 原型方法(所有实例共享)
console.log(`${this.name} moved ${dist}m`);
}
}
class Dog extends Animal {
bark() {
console.log('Woof!');
}
}
幕后:
class只是把早年的 “构造函数 + prototype” 写法包装得更像 C#/Java。继承时Dog.prototype会把原型链指向Animal.prototype,调用super()本质就是执行父构造函数。
2. TypeScript:在同一套语法上贴“类型安全标签”
abstract class Animal {
#id: number; // 运行期私有
protected speed = 0; // 编译期受保护
constructor(private name: string, id: number) { // “参数属性”自动赋值 name
this.#id = id;
}
abstract sound(): void; // 抽象方法
move(distance: number = 0) {
this.speed = distance;
console.log(`${this.name} moved ${distance} m`);
}
}
class Dog extends Animal {
sound() { console.log('Woof!'); }
}
TS class 编译过程
TypeScript 面向对象代码示例
// file: animal.ts —— TypeScript 源码
export default class Animal {
#id: number; // ECMAScript 私有字段
private name: string; // TS 私有修饰符(仅编译期检查)
protected speed = 0; // 受保护成员
constructor(id: number, name: string) {
this.#id = id;
this.name = name;
}
move(distance: number = 0) {
this.speed = distance;
console.log(`${this.name} moved ${distance} m`);
}
}
target: "es2015"(现代浏览器/Node 18+)
因为运行环境原生支持 class 与 #private 字段,TypeScript 只删掉类型信息,其余几乎原封不动:
// animal.js —— ES2015 输出
export default class Animal {
#id;
name; // ← 仅保留属性,private 被擦除
speed = 0;
constructor(id, name) {
this.#id = id;
this.name = name;
}
move(distance = 0) {
this.speed = distance;
console.log(`${this.name} moved ${distance} m`);
}
}
private name→name:修饰符只存在于 .ts,运行时代码里消失。#id保留:这是 ES 私有字段的语法,浏览器自己支持。
target: "es5"(老浏览器/IE 仍在时的写法)
老环境不认识 class,编译器会降级为 立即执行函数 + 原型 模式:
// animal.js —— ES5 输出
"use strict";
exports.__esModule = true;
var _id = new WeakMap(); // 用 WeakMap 模拟 #id 私有字段
var Animal = /** @class */ (function () {
function Animal(id, name) {
_id.set(this, id); // 维护私有值
this.name = name; // private 修饰符依旧被擦除
this.speed = 0;
}
Animal.prototype.move = function (distance) {
if (distance === void 0) { distance = 0; }
this.speed = distance;
console.log(this.name + " moved " + distance + " m");
};
return Animal;
}());
exports["default"] = Animal;
IIFE + 原型链:把 class 降级为构造函数
IIFE Immediately-Invoked Function Expression:写完就立刻调用自己,用来制造一个私有作用域,避免里面的变量泄漏到外面。
...() 最后那对小括号就是“立刻执行”。
(function () { ... }()): 这是一个 自执行函数 (IIFE),返回值是一个构造函数Animal, 赋给变量 Animal。
原型方法 Animal.prototype.move = function (distance) { ... }
| 元素 | 说明 |
|---|---|
Animal.prototype |
原型对象:挂在这里的方法会被 所有实例共享,节省内存。 |
move |
方法名。 |
function (distance) { ... } |
普通匿名函数,实现真正的业务逻辑。 |