跳转至

JS/TS

从零开始

JS/TS 是当前前端开发中主要的编程语言之一。说它是最主要的前端编程语言,应该没有争议。本文由浅入深、实用优先、浅而重、深而用、有所述、有所不述的系统的介绍 JS/TS。


开始写代码之前

  1. 代码编辑器

    VS Code ,不管是前端开发还是后端开发,是当前最流行、最方便使用的代码编辑器,没有之一。需要说明的是作为专业的程序开发人员,熟练使用 Vim 仍然是必须的。VS Code 的第三方插件非常丰富,本文不做 VS Code 使用的说明,仅列出 VS Code 的三个常用功能(插件)供第一步使用:

    • 集成命令行终端(Terminal),可以在集成窗口内直接运行本地和远程程序和命令;
    • 集成远程连接(SSH),可以直接连接远程 Linux 服务器,不仅是命令行连接,是“整个 VS Code 连接远程服务器”(目录窗口显示远程服务器目录,编辑窗口直接显示远程代码文件,终端窗口显示远程命令行);
    • 集成 Git,不再需要使用 TortoiseGit、Git Bash 等第三方 Git 工具;
  2. Node.js

    一个基于 Chrome V8 引擎 的 JavaScript 运行时环境,它的主要作用是让开发者能够使用 JavaScript 在服务器端构建快速、可扩展的网络应用

    不管你是否看懂了这句话,只管按照官方 Get Started 的说明,安装即可。

  3. TypeScript Compiler

    Node.js 只能运行 JS 代码,TS 代码需要使用 TypeScript Compiler “翻译”为 JS 代码,因此,需要安装 TypeScript Compiler,本文不详细介绍工具的安装和使用。


开始写 JS/TS 代码

/**
 * This TypeScript code demonstrates:
 * - the use of various console methods,
 * - variable and constant declarations,
 * - primitive data types,
 * - and control flow structures.
 */

// 1. Console Methods:
console.log("\n0. Demonstrating Various Console Methods:\n");
console.info("1. This is an info message.");
console.warn("2. This is a warning message.");
console.error("3. This is an error message.");
console.debug("4. Debugging information here.");

// 2. Variables and Constants:
// 2.1 Using const to define constants (values that cannot be changed).
const userName: string = "Tony Stark";
const maxScore: number = 100;

// 2.2 Using let to define variables (values that can be changed).
let currentScore: number = 85;
let isPassing: boolean = false;
// Union type: message can be either a string or null
let message: string | null = null; 

/**
 * 3. Primitive Data Types:
 *    - number,
 *    - string, 
 *    - boolean, 
 *    - undefined, 
 *    - null
 */
console.log(`\nWelcome, ${userName}!`);
console.log('Welcome,', userName + '.');
console.log(`Your starting score is: ${currentScore} (Max score: ${maxScore})`);

const bonusPoints: number = 5;
currentScore = currentScore + bonusPoints;

console.log(`Congratulations on earning ${bonusPoints} bonus points! Your current score is now: ${currentScore}`);

// 4. Control Flow: if/else structure
if (currentScore === maxScore) {
    isPassing = true;
    message = "Excellent! You achieved a perfect score.";
} else if (currentScore >= 90 && currentScore < maxScore) {
    isPassing = true;
    message = "Well done! You passed, but there's room for improvement.";
} else {
    isPassing = false;
    message = "Unfortunately, the score did not meet the standard. Please try harder.";
}

console.log(`Is passing?: ${isPassing}`);
console.log(`Message: ${message}`);

// 5. Demonstrate undefined and null
let extraInfo: string | undefined;
console.log(`\nType of extraInfo: ${typeof(extraInfo)}`);
/**
 * Demonstrating the use of undefined in TypeScript.
 * Suppose under certain conditions extraInfo is not assigned,
 * it defaults to undefined
 */
console.log(`\nAdditional undefined information (default undefined): ${extraInfo}`); 

// 6. Control Flow: for loop
console.log("\nSimulating Eight Attempts to Improve Score:");

for (let attempt = 1; attempt <= 8; attempt++) {
    currentScore++;
    console.log(`Attempt ${attempt} ended, current score: ${currentScore}`);

    // Suppose when the score reaches 98, set extraInfo 
    if (currentScore === 98) {
        extraInfo = "Status is good, close to perfect!";
    }
}

console.log(`\nValue of extraInfo after the loop ends: ${extraInfo}`); 

// At the end, set message to null to indicate clearing
message = null; 
console.log(`\nType of message: ${typeof(message)}`);
console.log(`Program ended, message variable reset to: ${message}`);

如上所述,Node.js 不能运行 .ts 文件。需要先将 .ts 文件“翻译”为 .js 文件,再执行 .js 文件:

PS D:\ts_learning\ts_basic> ls

    目录: D:\ts_learning\ts_basic

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2025-11-17     18:19            316 index.html
-a----        2025-11-17     21:29           2875 ts_basic.ts


PS D:\ts_learning\ts_basic> tsc .\ts_basic.ts     
PS D:\ts_learning\ts_basic> ls

    目录: D:\ts_learning\ts_basic

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2025-11-17     18:19            316 index.html
-a----        2025-11-17     23:39           2875 ts_basic.js
-a----        2025-11-17     21:29           2875 ts_basic.ts

PS D:\ts_learning\ts_basic> 

开始,ts_learning\ts_basic> 文件夹下只有两个文件,一个 .html 文件,一个 .ts 文件,执行:

PS D:\ts_learning\ts_basic> tsc .\ts_basic.ts

后,文件夹下生成了一个 .js 文件。这个 .js 文件是 Node.js 环境的可执行文件,执行 .js 文件:

PS D:\ts_learning\ts_basic> node.exe .\ts_basic.js

程序输出:

0. Demonstrating Various Console Methods:

1. This is an info message.
2. This is a warning message.
3. This is an error message.
4. Debugging information here.

Welcome, Tony Stark!
Welcome, Tony Stark.
Your starting score is: 85 (Max score: 100)
Congratulations on earning 5 bonus points! Your current score is now: 90
Is passing?: true
Message: Well done! You passed, but there's room for improvement.

Type of extraInfo: undefined

Additional undefined information (default undefined): undefined

Simulating Eight Attempts to Improve Score:
Attempt 1 ended, current score: 91
Attempt 2 ended, current score: 92
Attempt 3 ended, current score: 93
Attempt 4 ended, current score: 94
Attempt 5 ended, current score: 95
Attempt 6 ended, current score: 96
Attempt 7 ended, current score: 97
Attempt 8 ended, current score: 98

Value of extraInfo after the loop ends: Status is good, close to perfect!

Type of message: object
Program ended, message variable reset to: null

程序重点解读

  1. 常量与变量

    • 常量(const 关键字定义),值不会改变,例如:
    const userName: string = "Tony Stark";
    const maxScore: number = 100;
    
    • 变量(let 关键字定义),值可以改变,例如:
    let currentScore: number = 85;
    let isPassing: boolean = false;
    
  2. 联合体类型(Union type)

    let message: string | null = null; 
    

    变量 message 可以是 string 类型,也可以是 null(默认值)。

  3. 五种原始数据类型(Primitive Data Types)

    • number
    • string
    • boolean
    • undefined
    • null
  4. 常量和变量的引用

    1
    2
    3
    console.log(`\nWelcome, ${userName}!`);
    console.log('Welcome,', userName + '.');
    console.log(`Your starting score is: ${currentScore} (Max score: ${maxScore})`);
    
  5. 比较运算符 (===)

    • ===== 的区别在于它们在比较两个值时,是否允许进行类型转换(Type Coercion)
    • === 执行严格比较,它的规则非常清晰:

      • 先比较类型: 如果两个值的类型(Type)不同,直接返回 false
      • 再比较值: 如果类型相同,则比较它们的值
    • == 执行宽松比较,它的规则非常复杂和不直观:

      • 先比较类型: 如果两个值的类型不同,JavaScript 会尝试进行类型转换,将一个或两个操作数转换为另一个类型,直到它们的类型相同,然后再比较值。
      • 再比较值: 如果类型相同,行为与 === 一致

    Note

    • 强烈推荐只使用 ===,不再使用 ==;
    • TS 在 strict 模式下(默认),== 不能编译通过;
  6. undefinednull

    undefinednull 是 JS/TS 中两个特殊的原始类型(Primitive Types),undefined,值尚未被赋值(系统或变量默认状态),typeof 结果 'undefined';参考上述代码和执行结果。

    特性 undefined null
    含义 值尚未被赋值(默认状态) 明确地、有意识地被赋值为“空”(表示一个对象或值的持有者,但它现在不持有任何东西)
    来源 通常由JavaScript运行时(就是运行环境)自动设置 必须由程序员显式设置
    typeof 'undefined' 'object' (这是 JS 的一个历史遗留 bug),但实际上 null 是一个原始类型

JS 文件的执行

JS 文件的两种执行方法:

  • node 执行:

    PS D:\ts_learning\ts_basic> node.exe .\ts_basic.js
    
  • 浏览器执行 JS 文件

    除了像上面那样使用 node 命令执行外,还可以在浏览器中执行。写一个 .html 文件,如下:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>TypeScript Demo 运行</title>
    </head>
    <body>
        <h1>请打开浏览器的开发者工具 (F12) 查看运行结果</h1>
        <p>结果将显示在 Console 标签页中。</p>
    
        <script src="ts_basic.js"></script>
    </body>
    </html>
    

    在浏览器中打开这个 html 文件,即可执行 js 文件,运行结果如下:

    浏览器运行JS代码文件

console 方法

  • console.log()
  • console.info()
  • console.warn()
  • console.error()
  • console.debug()

请注意不同的 console 方法在浏览器中执行的输出

1
2
3
4
5
console.log("\n0. Demonstrating Various Console Methods:\n");
console.info("1. This is an info message.");
console.warn("2. This is a warning message.");
console.error("3. This is an error message.");
console.debug("4. Debugging information here.");

可以发现,浏览器特殊处理了 console.warn()console.error()


可变更新与不可变更新

什么是可变更新和不可变更新?

  • 可变更新 (Mutable Update)

    • 可变更新,直接改变数据结构中的数据,例如,直接修改一个数组的元素或一个对象的属性;
    • ReactRedux 中,直接进行可变更新会导致问题,因为系统(例如 React 的 shouldComponentUpdate 检查)无法察觉状态发生了变化,从而导致 UI 不更新。
  • 不可变更新 (Immutable Update)

    • 不可变更新,不可以直接改变(mutate)一个对象或数组中的数据;
    • 当需要修改数据时,必须创建一份数据的副本,然后在副本上进行修改;
    • 这确保了旧的数据结构保持不变。
  • Redux, 是一个强制使用不可变更新的状态管理库

  • ReactRedux 等现代框架中,通过比较对象的引用地址来判断状态是否改变

    • 不可变更新: 创建新副本 ---> 引用地址改变 ---> 系统检测到变化 ---> UI 刷新;
    • 可变更新: 直接修改原对象 ---> 引用地址不变 ---> 系统认为状态未变 ---> UI 不刷新(Bug)

ReactRedux官网关于可变更新不可变更新的主要说明

可变更新(Mutable Update) && 不可变更新(Immutable Update) 是现代前端编程的核心技术之一,其重要性如下所示:

Although objects in React state are technically mutable, you should treat them as if they were immutable — like numbers, booleans, and strings. Instead of mutating them, you should always replace them.

Treat all state in React as immutable

You must treat React elements and their props as immutable and never change their contents after creation. In development, React will freeze the returned element and its props property shallowly to enforce this.

Props and state are immutable

Don’t mutate Props

Don’t mutate State

Return values and arguments to Hooks are immutable

Values are immutable after being passed to JSX

Whenever your object would be mutated, don’t do it. Instead, create a changed copy of it.

Why is immutability required by Redux?

Note

从上面的引用可以看出可变更新(Mutable Update)不可变更新(Immutable Update)ReactRedux等现代前端编程的重要技术。

示例代码

可变更新不可变更新在现代前端状态管理(如 ReactRedux)中得到了最广泛的应用和普及,为了比较好的解释,新先贴上代码:

/**
 * Immutable vs Mutable State Updates and Spread Operator Order
 *
 * This code demonstrates the difference between mutable and immutable state updates.
 * It highlights how mutable updates can lead to issues in frameworks like React,
 * where reference equality is used to determine if a component should re-render.
 * 
 * It also illustrates the importance of the order of properties when using
 * the spread operator (...) in object updates.
 */

console.log("\n--- Immutable vs Mutable State Updates and Spread Operator Order ---\n");

// 1. Define a simple State Interface
interface UserState {
    id: number;
    name: string;
    score: number;
}

// 2. Initializing State
const initialState: UserState = {
    id: 101,
    name: 'Alice',
    score: 80
};

/** 
 * 3. Example 1: Mutable Update
 * 
 * Mutable Update Explanation:
 * Directly modifies the existing state object.
 * Such mutations can lead to issues in frameworks like React,
 * where components rely on reference equality to detect changes.
 */ 
function mutableUpdate(state: UserState, newScore: number): UserState {
    state.score = newScore;
    return state;
}

/**
 * Objects are reference types (Address Types): * 
 * - Assignments, passing, and comparisons of reference types are all based on
 *   addresses (i.e., references), not the values of member variables;
 * - In `let currentStateMutable = initialState;`, what is assigned to
 *   currentStateMutable is the address (i.e., reference), not the values of member variables
 * - In `let currentStateMutable = initialState;`, currentStateMutable is a variable and
 *   can be reassigned (can be changed)
 * - In `const originalReferenceMutable = currentStateMutable;`, originalReferenceMutable
 *   is a constant, cannot be reassigned (reference address does not change),
 *   but the values of member variables can be changed
 */
console.log("\nlet currentStateMutable = initialState");
let currentStateMutable = initialState;             // Variable <-- Constant

/**
 * Backup the state before update for comparison
 * ... operator called the spread operator, 
 * which is used to creates a shallow copy of the object
 */
console.log("const preUpdateStateMutable = { ...currentStateMutable }");
const preUpdateStateMutable = { ...currentStateMutable };

console.log("\nExample 1: Mutable Update\n");

console.log("initialState(Constant):\t\t\t", initialState);
console.log("currentStateMutable(Variable):\t\t", currentStateMutable);
console.log("preUpdateStateMutable(Constant):\t", preUpdateStateMutable);

console.log("\ninitialState(Constant) == currentStateMutable(Variable) ?",
    initialState === currentStateMutable ? "Yes":"No", 
    "(both point to the same reference address)"
);
console.log("initialState(Constant) == preUpdateStateMutable(Constant) ?",
    initialState === preUpdateStateMutable ? "Yes":"No",
    "(preUpdateStateMutable is a new object created by spread operator(...))"
);

// Execute Mutable Update
const newStateMutable = mutableUpdate(currentStateMutable, 95);

console.log("\n----------After mutableUpdate:----------\n");

console.log("initialState(Constant):\t\t\t", initialState);
console.log("currentStateMutable(Variable):\t\t", currentStateMutable);
console.log("preUpdateStateMutable(Constant):\t", preUpdateStateMutable);
console.log("newStateMutable(Constant):\t\t", newStateMutable);

console.log("\ninitialState(Constant) == currentStateMutable(Variable) ?",
    /**
     * initialState === currentStateMutable
     * Comparing the memory addresses (i.e., references) that these two variables point to,
     * not their member variable values
     */
    initialState === currentStateMutable ? "Yes":"No",
    "(both point to the same reference address)"
);
console.log("preUpdateStateMutable(Constant) == currentStateMutable(Variable) ?",
    preUpdateStateMutable === currentStateMutable ? "Yes":"No", 
    "(preUpdateStateMutable is a new object created by spread operator(...))"
);
console.log("newStateMutable(Constant) == currentStateMutable(Variable) ?",
    newStateMutable === currentStateMutable ? "Yes":"No", 
    "(mutableUpdate returns the same object reference as currentStateMutable)"
);

console.log("\nHas the actual data content changed?",
    preUpdateStateMutable.score === newStateMutable.score ? "No":"Yes",
    "(preUpdateStateMutable holds the state before update)"
);

console.log("\nConclusion: \n \
    Although the data content has changed, \n \
    the object reference address remains the same (initialState == currentStateMutable == newStateMutable).\n"
);

console.log("\n--- Example 2: Immutable Update ---\n");

// 4. Example 2: Immutable Update
function immutableUpdate(state: UserState, newScore: number): UserState {
    /**
     * Immutable Update Explanation:
     * Uses the spread operator (...) to create a brand new object (new reference address),
     * and overrides the properties that need to be modified.
     */
    const newState: UserState = {
        ...state,
        /**
         * Override the score property with the newScore value
         * use : instead of = here, because we are defining a property
         * in an object to override the existing one
         */
        score: newScore
    };
    return newState;
}



// Backup a reference address D for comparison
let currentStateImmutable = initialState;         // Variable <-- Constant

console.log("initialState(Constant):\t\t\t", initialState);
console.log("currentStateImmutable(Variable):\t", currentStateImmutable);

// Execute immutable update
const newStateImmutable = immutableUpdate(currentStateImmutable, 98);

console.log("\n----------After immutableUpdate:----------\n");

console.log("initialState(Constant):\t\t\t", initialState);
console.log("currentStateImmutable(Variable):\t", currentStateImmutable);
console.log("newStateImmutable(Constant):\t\t", newStateImmutable);

console.log("currentStateImmutable === newStateImmutable?",
    currentStateImmutable === newStateImmutable ? "Yes":"No",
    "(immutableUpdate returns a new object with a new reference address)"
);
console.log("currentStateImmutable.score === newStateImmutable.score?",
    currentStateImmutable.score === newStateImmutable.score ? "Yes":"No",
    "(currentStateImmutable holds the state before update)"
);

console.log("\nConclusion: \n \
    Both the data content and the reference address have changed.\n"
);

console.log("\n--- Example 3: Spread Operator Order ---\n");

function immutableUpdateWrong(state: UserState, newScore: number): UserState {
    const newState: UserState = {        
        score: newScore,
        ...state,
    };
    return newState;
}

const newStateImmutableWrong = immutableUpdateWrong(currentStateImmutable, 99);

console.log("\n----------After immutableUpdateWrong:----------\n");
console.log("initialState(Constant):\t\t\t", initialState);
console.log("currentStateImmutable(Variable):\t", currentStateImmutable);
console.log("newStateImmutable(Constant):\t\t", newStateImmutableWrong);

执行结果如下:

--- Immutable vs Mutable State Updates and Spread Operator Order ---


let currentStateMutable = initialState
const preUpdateStateMutable = { ...currentStateMutable }

Example 1: Mutable Update

initialState(Constant):                  { id: 101, name: 'Alice', score: 80 }
currentStateMutable(Variable):           { id: 101, name: 'Alice', score: 80 }
preUpdateStateMutable(Constant):         { id: 101, name: 'Alice', score: 80 }

initialState(Constant) == currentStateMutable(Variable) ? Yes (both point to the same reference address)
initialState(Constant) == preUpdateStateMutable(Constant) ? No (preUpdateStateMutable is a new object created by spread operator(...))

----------After mutableUpdate:----------

initialState(Constant):                  { id: 101, name: 'Alice', score: 95 }
currentStateMutable(Variable):           { id: 101, name: 'Alice', score: 95 }
preUpdateStateMutable(Constant):         { id: 101, name: 'Alice', score: 80 }
newStateMutable(Constant):               { id: 101, name: 'Alice', score: 95 }

initialState(Constant) == currentStateMutable(Variable) ? Yes (both point to the same reference address)
preUpdateStateMutable(Constant) == currentStateMutable(Variable) ? No (preUpdateStateMutable is a new object created by spread operator(...))
newStateMutable(Constant) == currentStateMutable(Variable) ? Yes (mutableUpdate returns the same object reference as currentStateMutable)

Has the actual data content changed? Yes (preUpdateStateMutable holds the state before update)

Conclusion:
     Although the data content has changed,
     the object reference address remains the same (initialState == currentStateMutable == newStateMutable).


--- Example 2: Immutable Update ---

initialState(Constant):                  { id: 101, name: 'Alice', score: 95 }
currentStateImmutable(Variable):         { id: 101, name: 'Alice', score: 95 }

----------After immutableUpdate:----------

initialState(Constant):                  { id: 101, name: 'Alice', score: 95 }
currentStateImmutable(Variable):         { id: 101, name: 'Alice', score: 95 }
newStateImmutable(Constant):             { id: 101, name: 'Alice', score: 98 }
currentStateImmutable === newStateImmutable? No (immutableUpdate returns a new object with a new reference address) 
currentStateImmutable.score === newStateImmutable.score? No (currentStateImmutable holds the state before update)   

Conclusion:
     Both the data content and the reference address have changed.


--- Example 3: Spread Operator Order ---


----------After immutableUpdateWrong:----------

initialState(Constant):                  { id: 101, name: 'Alice', score: 95 }
currentStateImmutable(Variable):         { id: 101, name: 'Alice', score: 95 }
newStateImmutable(Constant):             { score: 95, id: 101, name: 'Alice' }

示例代码的重要说明

const 保护变量的绑定(Binding)

前面曾介绍 const 定义的是常量,值不可被修改,如果 const 定义的是一个引用类型而不是一个原始类型,那么,是引用(地址)不能被修改?还是所指的成员变量不能被修改?
答案是:const 确保的是它所存储的地址值(引用)不能被修改。它不能阻止通过该引用去修改对象内部的成员变量。

const 关键字的作用是创建一个只读的常量绑定(read-only binding):

  • 对于原始类型(如 number, string): const 保护的是值本身,因为变量直接存储了值;
    const x = 10;
    x = 20;         // 错误:不能重新赋值
    
  • 对于引用类型(如对象、数组): const 保护的是存储在变量中的那个内存地址(引用);

    1
    2
    3
    4
    5
    6
    7
    const originalReferenceMutable = currentStateMutable; 
    // originalReferenceMutable 中存储的是地址 A
    
    originalReferenceMutable = { id: 2 };   // 错误:不能重新赋值,因为这是修改地址 A
    
    // 但是:
    originalReferenceMutable.id = 5;        // 允许:这是通过地址 A 修改了内存中的对象内容
    
  • 总结:

    • const 提供了引用的不变性 (Immutability of the Reference);
    • 但没有提供内容的不可变性 (Immutability of the Content/Value);
    • 如果想要内容也无法修改,需要使用其他机制,例如在 JavaScript 中使用 Object.freeze()

展开运算符 (Spread Operator, ...)

  1. 简介
    ... 是现代 JS/TS 中用于处理数组、对象和函数参数的强大工具。

    • 只进行"浅拷贝"(Shallow Copy), 展开运算符只会复制第一层的值, 如果原始对象或数组包含引用类型(如另一个对象或数组),那么新的对象或数组只会复制这个引用地址;
    • 在对象中的应用(Object Spread)时,用于合并和创建新对象

    Note

    展开运算符 ... 无法用于执行不可变更新的深层嵌套数据。如果需要深拷贝,应使用 JSON 序列化(简单场景)或 lodash.cloneDeep 等工具库

    Note

    • 属性覆盖(Overwriting), 如果多个对象有同名属性,后面的属性会覆盖前面的属性;
    • 类型安全(TS), 在 TS 中, 对象展开通常会产生一个新的、合并了所有属性的新类型;
    • 顺序, 确保将默认值放在前面,将要覆盖的新值放在后面
  2. Spread Operator 和 Rest Operator

    展开运算符和剩余参数 (Rest Operator) 使用相同的 ... 语法,但它们的作用正好相反。

    1
    2
    3
    4
    5
    6
    7
    8
    // 展开 (Spread) - 函数调用
    const args = [1, 2, 3];
    myFunc(...args);    // 相当于 myFunc(1, 2, 3)
    
    // 收集 (Rest) - 函数定义
    function myFunc(a, ...rest) {
        // rest 是一个包含剩余参数的数组: [2, 3]
    }
    

  3. 在 State Management 中的关键作用
    ReactRedux 等状态管理框架中,展开运算符至关重要:

    • 创建新引用: 它是执行不可变更新 (Immutable Updates) 的主要手段。通过 const newState = { ...oldState };,你确保 newStateoldState 具有不同的引用,从而触发组件重新渲染;
    • 不可变数组更新: 用于在不修改原数组的情况下添加或删除元素:
      • 添加: [...oldArray, newItem];
      • 删除: 需要结合 filter 或 slice,不能仅靠展开运算符完成删除

JS/TS 数据类型总结

JS/TS 变量类型可以分为两大类:

  • 原始类型(Primitive Types): number, string, boolean, undefined, null, Symbol(ES6 新增), BigInt(ES2020 新增)
  • 引用类型(Reference Types): 除了上述 7 种原始类型之外,所有其他复杂的数据结构都属于引用类型,即: 对象(Object)。引用类型存储的是内存地址,而不是值本身,这是理解不可变更新(Immutable Update)的关键。

    类型 示例 描述
    对象 { key: value } 最基本的引用类型,用于存储键值对集合
    数组 [1, 2, 3] 用于存储有序集合,本质上也是对象
    函数 function() {} 函数在 JavaScript 中是“一等公民”,它们也是特殊的对象
    日期 new Date() 用于处理时间和日期
    内置对象 Map, Set, RegExp 用于特定用途的内置数据结构和类
    类实例 new MyClass() 通过 class 关键字创建的自定义类型实例
  • TypeScript 特有的类型(非运行时类型)

    类型 关键字 描述
    任意类型 any 禁用类型检查,允许任何值。(应尽量避免使用)
    未知类型 unknown 比 any 安全,强制在使用前进行类型检查
    虚无类型 void 通常用于函数的返回值,表示函数没有返回任何有意义的值
    永不类型 never 表示函数永远不会返回(如抛出错误或无限循环)
    元组 "[string, number]" 表示一个已知元素数量和类型的数组

不可变更新相关的第三方库

数组的 reduce 方法

Note

reduce 在这里不是"减少"的意思,请参考下面 Merriam-Webster 的部分意思:

  • to draw together or cause to converge: CONSOLIDATE
    // reduce all the questions to one
  • to bring to a specified state or condition
    // the impact of the movie reduced them to tears
  • to change the denominations or form of without changing the value
  • to add one or more electrons to (an atom or ion or molecule)

Array.prototype.reduce() 方法接受 1 个或者 2 个参数:

  • reduce(callbackFn), 接受 1 个参数
  • reduce(callbackFn, initialValue), 接受 2 个参数
  • 至少接受 1 个函数参数,这个函数叫做 reducer 函数

Array.reduce() 方法对数组中的每个元素按序执行一个提供的 reducer 函数(即,callbackFn),每一次运行 reducer / callbackFn 会将先前元素的计算结果作为参数传入,最后将其结果汇总为单个返回值。

arrayReducer.js 示例代码:

/**
 * @file arrayReducer.js
 */
const array1 = [1, 2, 3];
const initialValue = 0;

var t = 0;

const sumWithInitial = array1.reduce(
    (accumulator, currentValue, currentIndex, array) => {
        let i = 0;
        console.log("The time(s)(i) of callback is called:", i);
        i++;

        console.log("the time(s)(t) of callback is called:", t);
        t++;

        console.log("\nThe current index:", currentIndex)

        console.log("The arrary:", array);

        console.log("\naccumulator:", accumulator);
        console.log("currentValue:", currentValue);
        console.log("\n----------------------------------------\n")

        return accumulator + currentValue
    },
    initialValue,
);

console.log("Sum of the Array: ", sumWithInitial);

执行结果:

PS > node .\arrayReducer.js
The time(s)(i) of callback is called: 0
the time(s)(t) of callback is called: 0

The current index: 0
The arrary: [ 1, 2, 3 ]

accumulator: 0
currentValue: 1

----------------------------------------

The time(s)(i) of callback is called: 0
the time(s)(t) of callback is called: 1

The current index: 1
The arrary: [ 1, 2, 3 ]

accumulator: 1
currentValue: 2

----------------------------------------

The time(s)(i) of callback is called: 0
the time(s)(t) of callback is called: 2

The current index: 2
The arrary: [ 1, 2, 3 ]

accumulator: 3
currentValue: 3

----------------------------------------

Sum of the Array:  6
PS > 

reduce() 的特性:

  • reduce() 方法是一个迭代方法;
  • reduce() 不会改变被调用的数组,但是作为 callbackFn 提供的函数可能会改变数组;
  • 在第一次调用 callbackFn 之前,数组的长度会被保存。因此, 当开始调用 reduce() 时,callbackFn 将不会访问超出数组初始长度的任何元素;
  • 数组的长度会被保存, 但数组没有被保存,如果数组中一个现有的、尚未访问的元素被 callbackFn 更改,则它传递给 callbackFn 的值将是该元素被修改后的值;

Note

  • Redux 中的 reducer 的概念名称来自这里的数组的 reduce 方法;
  • Array.reduce 是对数组元素进行累积计算;
  • Redux reducer 是对 state + action 进行累积,生成新的状态。换句话说:Redux reducer 是把 "action 流" reduce 成"状态结果"。