JS

在开始这部分模块之前,你应该熟悉基本的 HTML 和 CSS。

JavaScript 是脚本编程语言,符合ECMA 标准规范(即 ECMA-262 标准)。我们所熟知的 Node.js、Web JS、Vue.js 等都是在标准 JS 基础上,基于 ECMA 规范扩展而来的。JS 可以用来创建动态更新的内容、控制多媒体、制作图像动画等,能实现多种复杂功能。

概述

作为程序员,你或许听说过这两个术语:解释(interpret)和编译(compile)。

  • 在解释型语言中,代码自上而下运行,且实时返回运行结果。代码在由浏览器执行前,不需要转化为其他形式,直接以文本格式被接收和处理。
  • 相对的,编译型语言需要先将代码转化(编译)成另一种形式才能运行。比如 C/C++ 先被编译成机器码,然后才能由计算机运行,程序将以二进制格式运行,这些二进制内容由程序源代码生成。

JavaScript 是轻量级解释型语言。浏览器接收到 JavaScript 代码后,以代码自身的文本格式运行它。技术上,几乎所有 JavaScript 引擎都运用了即时编译(just-in-time compiling)技术:当 JavaScript 源代码被执行时,会被编译成二进制(如 wasm 格式,感兴趣的同学可以深入了解),使代码运行速度更快。尽管如此,JavaScript 仍然是解释型语言,因为编译过程发生在代码运行中,而非运行前。

JS 常用作客户端代码(即前端代码),在 Node.js 环境中也可作为后端代码。

JavaScript 是一门形式自由的语言:不必声明所有变量、类和方法;不必关心方法的访问权限(公有、私有、受保护);不需要显式实现接口;无需显式指定变量类型(动态类型语言)。

请注意,Web 文档中,代码通常按其在页面上出现的顺序加载和执行。为确保所有 HTML 元素完成加载后再执行 JS,建议将 JavaScript 放在 <body> 底部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 我们通常在script标签中写js代码 -->
<!-- 直接编写js代码 -->
<script>
// js代码
</script>

<!-- 或者引入外部js文件 -->
<script type="module" src="script.js"></script>

<!-- 或者在html标签中通过事件响应js代码 -->
<button onclick="createParagraph()">
点我!
<!-- 注意:onclick中的函数createParagraph()只需在全局作用域中可访问(无需先通过DOM获取标签)。
但不推荐这种写法:会导致JS与HTML耦合,且批量处理元素时效率低。
推荐使用后文“DOM操作”中提到的addEventListener方法。 -->
</button>

js 基础语法

1. 变量

  1. 声明变量:通过let(块级作用域)或var(函数作用域)声明变量。
    例:let myName;

  2. 初始化:通过=赋值,可在声明时直接赋值(例:let age = 18;)。

  3. varlet 的区别

    • let:声明块级作用域局部变量(作用域被{}限制,如if/for代码块),可重新赋值。
    • var:声明函数作用域变量(作用域为整个函数,若无函数则为全局),存在“变量提升”特性(声明被提升到作用域顶部,赋值不提升)。
    • 推荐:若变量无需重新赋值,优先使用const(声明常量);需重新赋值时用let;尽量避免使用var
  4. 变量提升与暂时性死区

    • var声明的变量会“提升”:在声明前访问不会报错,值为undefined(例:console.log(a); var a = 1; 输出undefined)。
    • let/const声明的变量存在“暂时性死区”:在声明前访问会直接抛出ReferenceError(例:console.log(b); let b = 2; 报错)。
  5. 变量作用域

    • 全局作用域:不在任何函数/块内的变量,可被所有代码访问(浏览器中全局变量挂载在window对象上)。
    • 函数作用域:函数内声明的变量(var声明),仅在函数内可访问。
    • 块级作用域:{}内用let/const声明的变量,仅在块内可访问(如if (true) { let c = 3; } console.log(c); 报错)。

2. 全局变量

在网页中,全局变量默认挂载在window对象上;在所有环境中,globalThis变量(自身也是全局变量)可用于读取和设置全局变量(例:globalThis.username = "Alice";)。

3. 常量(const

const用于声明不可重新赋值的变量,必须在声明时初始化(例:const PI = 3.14;)。

  • 对于基本类型(数字、字符串、布尔等):值不可修改(例:PI = 3; 报错)。
  • 对于引用类型(对象、数组等):变量存储的“引用地址”不可修改,但内部属性/元素可修改(例:const arr = [1]; arr.push(2); 合法,数组变为[1,2])。

4. 数据结构和类型

  1. ECMA 标准的 8 种类型

    • 7 种基本类型:booleantrue/false)、null(空值)、undefined(未定义)、Number(整数/浮点数)、BigInt(大整数,例:123n)、String(字符串)、Symbol(实例唯一且不可变,例:Symbol("id"))。
    • 1 种引用类型:Object(对象,包括数组、函数、日期等)。
  2. 数据类型转换
    JavaScript 是动态类型语言:声明变量时不必指定类型,运行时会自动转换。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let answer = 42;
    answer = "嘿嘿,气不气"; // 合法,类型从Number转为String

    x = "答案是 " + 42; // "答案是 42"(数字转字符串)
    y = 42 + " 是答案"; // "42 是答案"
    z = "37" + 7; // "377"(数字转字符串)

    // 显式转换
    parseInt("101", 2); // 5(二进制字符串转十进制数字)
    Number("123"); // 123(字符串转数字)
    String(123); // "123"(数字转字符串)
  3. 类型判断方法

    • typeof:判断基本类型(注意:typeof null返回"object"typeof []返回"object")。
    • instanceof:判断对象类型(基于原型链,例:[] instanceof Arraytrue)。
    • Object.prototype.toString.call():最准确的类型判断(例:Object.prototype.toString.call(null) 返回"[object Null]")。
  4. 严格相等与非严格相等

    • ==:非严格相等,会先进行隐式类型转换再比较(例:0 == falsetrue)。
    • ===:严格相等,不转换类型,直接比较值和类型(例:0 === falsefalse,因类型不同)。推荐优先使用===
  5. 字面量

    • 数组字面量:由[]包裹的元素列表,用逗号分隔(例:const coffees = ["French Roast", "Colombian"];)。
    • 布尔字面量:truefalse
    • 对象字面量:由{}包裹的键值对列表(例:const person = { name: "Bob", age: 20 };)。
    • 模板字面量:由反引号` 包裹,支持多行字符串和插值(${变量}):
      1
      2
      3
      const name = "Lev",
      time = "today";
      `你好 ${name}${time} 过得怎么样?`; // 输出"你好 Lev,today 过得怎么样?"
    • 数字字面量:整数(123)、浮点数(123.45)、二进制(0b101)、十六进制(0x1f)等。

5. 条件语句

  1. if-else 语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    if (condition) {
    statement1; // 条件为true时执行
    } else {
    statement2; // 条件为false时执行
    }

    // if可单独使用
    if (condition) {
    // 条件为true时执行
    }

    // if-else if-else
    if (condition1) {
    statement1;
    } else if (condition2) {
    statement2;
    } else {
    statementLast; // 所有条件都为false时执行
    }
  2. switch 语句(适用于多值判断):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    switch (expression) {
    case value1:
    statement1;
    break; // 跳出switch
    case value2:
    statement2;
    break;
    default: // 所有case不匹配时执行
    statementDefault;
    }
  3. try-catch 语句(处理异常):
    throw抛出异常,用try...catch捕获,finally无论是否异常都会执行。

    1
    2
    3
    4
    5
    6
    7
    try {
    throw "我的异常"; // 主动抛出异常
    } catch (err) {
    console.log("捕获到异常:", err); // 处理异常
    } finally {
    console.log("无论是否异常,都会执行");
    }

6. 循环语句

  • for 循环

    1
    2
    3
    for (let step = 0; step < 5; step++) {
    console.log("第" + step + "步"); // 执行5次(step从0到4)
    }
  • while/do-while 循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // while:先判断条件,再执行
    let i = 0;
    while (i < 3) {
    console.log(i);
    i++;
    }

    // do-while:先执行一次,再判断条件
    let j = 0;
    do {
    console.log(j);
    j++;
    } while (j < 3);
  • 循环控制

    • break:跳出当前循环(可配合标签跳出多层循环)。
    • continue:跳过本次循环,直接进入下一次。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 带标签的循环(跳出多层循环)
    outLoop: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
    if (i === 1 && j === 1) {
    break outLoop; // 直接跳出outLoop标签对应的外层循环
    }
    console.log(i, j);
    }
    }
  • for…in(遍历对象属性/数组下标,不推荐遍历数组):

    1
    2
    3
    4
    const obj = { name: "Alice", age: 18 };
    for (let key in obj) {
    console.log(key + ": " + obj[key]); // 输出"name: Alice"、"age: 18"
    }
  • for…of(遍历可迭代对象的元素,如数组、字符串):

    1
    2
    3
    4
    5
    const arr = [3, 5, 7];
    arr.foo = "hello"; // 给数组添加额外属性
    for (let value of arr) {
    console.log(value); // 输出3、5、7(仅遍历数组元素,忽略额外属性)
    }

7. 函数

  1. 函数定义

    • 函数声明:
      1
      2
      3
      function square(number) {
      return number * number; // 返回计算结果
      }
    • 函数表达式(匿名函数赋值给变量):
      1
      2
      3
      const square = function (number) {
      return number * number;
      };
  2. 嵌套函数与闭包
    内部函数可访问外部函数的变量,形成闭包(外部函数执行后,内部函数仍能访问其变量)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function outer() {
    let count = 0;
    function inner() {
    // 嵌套函数(闭包)
    count++;
    return count;
    }
    return inner; // 返回内部函数
    }

    const func = outer();
    console.log(func()); // 1
    console.log(func()); // 2(count被闭包保存,未被销毁)
  3. 箭头函数(ES6+,语法更简洁,无自己的thisarguments):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 基本语法:(参数) => 返回值
    const sum = (a, b) => a + b;

    // 多行代码需用{}包裹
    const square = (x) => {
    return x * x;
    };

    // 无自己的this(继承外部作用域的this)
    const person = {
    age: 18,
    grow: function () {
    setInterval(() => {
    this.age++; // this指向person对象(继承grow函数的this)
    }, 1000);
    },
    };
  4. arguments对象
    函数内部的类数组对象,包含传入的所有实参(箭头函数无此对象,需用剩余参数...args)。

    1
    2
    3
    4
    5
    6
    7
    8
    function sum() {
    let total = 0;
    for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
    }
    return total;
    }
    sum(1, 2, 3); // 6
  5. 剩余参数...args,ES6+,将剩余参数转为数组):

    1
    2
    3
    4
    5
    function sum(...args) {
    // args是数组,可使用数组方法
    return args.reduce((a, b) => a + b, 0);
    }
    sum(1, 2, 3); // 6

8. 表达式和运算符

  • 解构赋值(快速提取数组/对象中的值,ES6+):

    1
    2
    3
    4
    5
    6
    // 数组解构
    const [a, b] = [1, 2]; // a=1,b=2
    const [c, ...rest] = [3, 4, 5]; // c=3,rest=[4,5]

    // 对象解构
    const { name, age } = { name: "Bob", age: 20 }; // name="Bob",age=20
  • this关键字
    指向函数执行时的上下文对象(取决于调用方式):

    • 全局函数中,this指向window(浏览器)或globalThis
    • 对象方法中,this指向该对象。
    • 构造函数中(new调用),this指向新创建的对象。
  • super关键字
    在类的方法中,指向父类的原型对象,用于调用父类方法(例:super.print())。

9. 数组(Array

数组是特殊的对象,用于存储有序集合,补充常用方法:

  • 遍历/转换

    • map((item) => { ... }):返回新数组,每个元素为原元素处理后的值(例:[1,2,3].map(x => x*2)[2,4,6])。
    • filter((item) => { ... }):返回新数组,包含满足条件的元素(例:[1,2,3].filter(x => x>1)[2,3])。
    • forEach((item) => { ... }):遍历数组,无返回值。
  • 聚合

    • reduce((total, item) => { ... }, initial):累加/合并数组(例:[1,2,3].reduce((a,b) => a+b, 0)6)。
    • every((item) => { ... }):所有元素满足条件则返回true(例:[2,4,6].every(x => x%2===0)true)。
    • some((item) => { ... }):至少一个元素满足条件则返回true
  • 操作

    • push(item)/pop():尾部添加/删除元素。
    • unshift(item)/shift():头部添加/删除元素。
    • slice(start, end):截取子数组(不修改原数组)。
    • splice(start, deleteCount, ...items):修改原数组(删除/添加元素)。

10. 数字和字符串

  1. 数学对象 Math

    • Math.PI:圆周率(≈3.14159)。
    • Math.abs(x):绝对值。
    • Math.floor(x)/Math.ceil(x):向下取整/向上取整(例:Math.floor(2.8)2Math.ceil(2.1)3)。
    • Math.min(x1, x2, ...)/Math.max(x1, x2, ...):求最小值/最大值。
    • Math.sqrt(x):平方根。
    • 三角函数:Math.sin(x)Math.cos(x)等(参数为弧度)。
  2. 日期对象 Date
    用于处理日期和时间,常用方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const today = new Date(); // 当前时间
    const birthday = new Date(2000, 0, 1); // 2000年1月1日(月份从0开始)

    // 获取日期
    today.getFullYear(); // 年份(4位)
    today.getMonth(); // 月份(0-11)
    today.getDate(); // 日(1-31)

    // 设置日期
    today.setFullYear(2024); // 设置年份为2024

11. 正则表达式

正则表达式用于匹配字符串中的模式,是处理文本的强大工具。

  1. 创建方式

    1
    2
    3
    4
    // 方式1:字面量(适合固定模式)
    const re1 = /ab+c/;
    // 方式2:RegExp对象(适合动态模式)
    const re2 = new RegExp("ab+c");
  2. 常用元字符与量词

    • 元字符:

      • .:匹配除换行外的任意字符。
      • ^/$:匹配字符串开头/结尾(例:/^abc$/ 匹配完整的”abc”)。
      • *:前一项出现 0 次或多次(例:/ab*c/ 匹配”ac”、”abc”、”abbc”等)。
      • +:前一项出现 1 次或多次(例:/ab+c/ 匹配”abc”、”abbc”等,不匹配”ac”)。
      • ?:前一项出现 0 次或 1 次;若紧跟量词后,变为非贪婪匹配(例:/a.*?b/ 匹配”a”到第一个”b”)。
      • (x):捕获分组(记住匹配的内容,可通过\1引用)。
      • (?:x):非捕获分组(仅分组,不记住内容)。
      • x(?=y):先行断言(匹配 x,且 x 后紧跟 y,例:/a(?=b)/ 匹配”ab”中的”a”)。
    • 字符类:

      • \d:匹配数字(等价于[0-9]),\D:匹配非数字。
      • \w:匹配字母/数字/下划线(等价于[A-Za-z0-9_]),\W:匹配非单词字符。
      • \s:匹配空白符(空格、制表符等),\S:匹配非空白符。
    • 量词:

      • {n}:恰好 n 次(例:/a{2}/ 匹配”aa”)。
      • {n,}:至少 n 次(例:/a{2,}/ 匹配”aa”、”aaa”等)。
      • {n,m}:n 到 m 次(例:/a{1,3}/ 匹配”a”、”aa”、”aaa”)。
  3. 常用标志

    • i:忽略大小写(例:/abc/i 匹配”ABC”、”aBc”等)。
    • g:全局匹配(找到所有匹配,而非第一个)。
    • m:多行模式(^/$匹配每行开头/结尾)。
  4. 使用方法
    | 方法 | 说明 |
    |——————————-|———————————————————-|
    | re.test(str) | 测试 str 是否匹配 re,返回 true/false |
    | re.exec(str) | 查找匹配,返回数组(含分组),无匹配则返回 null |
    | str.match(re) | 同 exec,全局匹配时返回所有匹配的数组 |
    | str.replace(re, newStr) | 替换匹配的子串,返回新字符串 |

    示例:

    1
    2
    3
    4
    5
    const str = "Hello 2024, Hello 2025";
    const re = /Hello (\d+)/g; // 全局匹配"Hello 数字",捕获数字

    str.match(re); // ["Hello 2024", "Hello 2025"]
    str.replace(re, "Hi $1"); // "Hi 2024, Hi 2025"($1引用捕获的数字)

12. 对象与原型

JavaScript 基于原型实现继承,对象是核心数据结构。

  1. 对象创建方式

    • 字面量:const obj = { name: "Tom", age: 18 };
    • 构造函数:function Person(name) { this.name = name; } const p = new Person("Tom");
    • 类(ES6+):class Person { constructor(name) { this.name = name; } }
  2. 原型与原型链

    • 每个对象有__proto__属性(指向其原型对象),原型对象也是对象,形成“原型链”。
    • 访问对象属性时,若自身没有,会沿原型链向上查找,直到null
    • 例:const arr = []; arr.__proto__ === Array.prototype; // true(数组的原型是 Array.prototype)。
  3. 常用对象方法

    • Object.keys(obj):返回对象自有属性的键名数组。
    • Object.values(obj):返回对象自有属性的键值数组。
    • Object.assign(target, ...sources):合并对象(复制属性到 target)。

13. 异步编程

JavaScript 是单线程语言,异步编程用于处理耗时操作(如网络请求、定时器)。

  1. 回调函数
    异步操作完成后执行的函数(易出现“回调地狱”):

    1
    2
    3
    setTimeout(() => {
    console.log("1秒后执行"); // 1秒后回调
    }, 1000);
  2. Promise(ES6+,解决回调地狱):
    表示异步操作的最终完成(或失败),有三种状态:pending(进行中)、fulfilled(成功)、rejected(失败)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
    if (Math.random() > 0.5) {
    resolve("成功"); // 成功时调用
    } else {
    reject("失败"); // 失败时调用
    }
    }, 1000);
    });

    promise
    .then((result) => console.log(result)) // 成功回调
    .catch((error) => console.log(error)) // 失败回调
    .finally(() => console.log("无论成功失败都执行"));
  3. async/await(ES7+,Promise 的语法糖):
    让异步代码像同步代码一样直观:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    async function fetchData() {
    try {
    const result = await promise; // 等待Promise完成
    console.log(result);
    } catch (error) {
    console.log(error);
    }
    }
    fetchData();

14. 模块化(ES6 Module)

通过exportimport实现代码拆分与复用,需在<script type="module">中使用。

  1. 导出(export

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // module.js
    export const name = "JS"; // 导出变量
    export function hello() {
    console.log("Hello");
    } // 导出函数

    // 批量导出
    const version = "ES6";
    export { version };
  2. 导入(import

    1
    2
    3
    4
    5
    6
    7
    8
    // main.js
    import { name, hello } from "./module.js";
    console.log(name); // "JS"
    hello(); // 输出"Hello"

    // 导入所有(别名)
    import * as module from "./module.js";
    console.log(module.version); // "ES6"