Skip to content

JavaScript

介绍

JavaScript(通常缩写为JS)是一门基于 原型头等函数多范式 高级 解释型 编程语言,它支持 面向对象 程序设计、 指令式编程函数式编程

它提供方法来操控文本、数组、日期以及正则表达式等。

不支持I/O,比如网络、存储和图形等,但这些都可以由它的宿主环境提供支持。

它由Ecma通过ECMAScript实现语言的标准化。

目前,它被世界上的绝大多数网站所使用,也被世界主流浏览器(Chrome、Firefox、Safari和Opera)所支持。

JavaScript与Java在名字和语法上都很相似,但这两门编程语言从设计之初就有很大不同。 JavaScript在语言设计上主要受到了Self(一种基于原型的编程语言)和Scheme(一门函数式编程语言)的影响, 在语法结构上它和C语言很相似(如if条件语句、switch语句、while循环和do-while循环等)。

ECMAScript

JavaScript 是语言,ECMAScript 是规范。

JavaScriptECMAScript 的实现之一(还有如 JScriptActionScript


几个重要的ECMAScript 版本:

  1. ES3(1999)
  • 第一个广泛支持的版本
  • 引入:正则表达式、异常处理(try-catch)、switch、do...while
  1. ES5(2009)
  • 大规模应用前的标准,兼容性强
  • 特性:
    • "use strict" 严格模式
    • Array.prototype.forEach/map/filter
    • Object.defineProperty
    • JSON.parse / JSON.stringify
  1. ES6 / ECMAScript 2015
  • JavaScript 的一次重大变革
  • 引入现代开发核心语法:
    • let / const
    • 箭头函数 ()=>{}
    • 类(class)、模块(import/export)
    • 解构赋值、模板字符串、默认参数
    • Promise
    • Map / Set
  1. ES8 / ECMAScript 2017
  • 引入了 异步编程革命
  • 特性:
    • async/await(写异步代码像同步代码)
    • Object.entries / Object.values
  1. ES11 / ECMAScript 2020
  • 现代语法糖丰富
  • 特性:
    • 可选链操作符 ?.
    • 空值合并操作符 ??
    • Promise.allSettled
    • 动态 import()

基本用法

插入方式

  • 内联方式
html
<button onclick="alert('点击了按钮')">点我</button>
  • 内部脚本
html
<script>
  console.log('页面加载完成');
</script>
  • 外部引入(推荐做法)
html
<script src="main.javascript"></script>

推荐引入方式

推荐 HTML 末尾引入外部 JS

html
<body>
  <!-- 页面内容 -->

  <script src="main.javascript"></script> <!-- 放在 body 末尾 -->
</body>

优点:

  • 提升页面加载速度 浏览器解析 HTML 是自上而下的,如果 JS 写在前面,会阻塞 DOM 构建。而写在后面可以先加载内容,提升用户体验。

  • 确保 DOM 元素已加载 当 JS 执行时,页面中的元素已经加载完成,避免出现 document.getElementById(...) is null 这类错误。

  • 无需等待或写 DOMContentLoaded 不用额外包装代码在:

javascript
document.addEventListener("DOMContentLoaded", () => {
  // code here
});

若放在 <head> 中,使用 defer延迟执行,等 DOM 完成后才运行。

html
<script src="main.javascript" defer></script>

执行过程

  1. HTML 解析 → 浏览器解析 HTML,从上到下构建 DOM 树;
  2. 遇到 <script> 标签 → 浏览器暂停 HTML 解析,执行 JS 脚本;
  3. 同步执行代码 → JavaScript单线程的,按顺序同步执行;
  4. 操作 DOM响应事件 → 可以通过 JS 修改页面内容、响应用户操作;
  5. 异步任务(如 setTimeoutfetch) → 放入任务队列,等待主线程空闲时执行。

变量

在 JavaScript 中,变量是用于存储数据值的容器。理解变量是 JS 编程的基础之一。


JS 中声明变量的三种方式

关键字作用域是否可重复声明是否可修改值是否提升说明
var函数作用域✅ 是✅ 是✅ 是早期使用方式,灵活但易出错
let(ES6)块级作用域❌ 否✅ 是❌ 否推荐用于可变变量
const(ES6)块级作用域❌ 否❌ 否(值不能变)❌ 否推荐用于常量或不可变引用

变量的使用示例

javascript
// var 示例(不推荐)
var x = 5;
var x = 10; //  允许重复声明

// let 示例(推荐)
let a = 3;
// let a = 4; //  报错:不能重复声明
a = 4;       //  可修改

// const 示例(推荐用于常量)
const PI = 3.14;
// PI = 3.1415; //  报错:不可修改

作用域

函数作用域(var

javascript
function test() {
  var message = "hello";
}
console.log(message); // 报错,message 在函数外不可访问

块级作用域(letconst

javascript
{
  let score = 100;
}
// console.log(score); // 报错,score 在块外不可访问

变量提升(仅 var

var 声明的变量会被提升(Hoisting)到当前作用域的顶部,这个“作用域”可以是:

  • 当前的 函数作用域(如果 var 在函数内声明)
  • 当前的 全局作用域(如果 var 在函数外声明)
javascript
console.log(a); // undefined (已声明但未赋值)
var a = 5;

等价于:

javascript
var a;
console.log(a);
a = 5;

letconst 也会提升,但它们的初始化不会被提升。在声明之前访问这些变量会导致ReferenceError。,访问会直接报错(称为“暂时性死区”)

参考:深究一下let、const到底有没有提升?


推荐用法

  • 推荐使用 let 代替 var

    • var 会“泄露”出当前代码块,导致变量被意外访问或污染。
    • let 不允许重复声明同名变量(更安全)
    • var 会被提升到作用域顶部,容易造成未定义变量被错误使用。
    • let 为每次循环创建了新的作用域,var 只创建一次作用域。

    比如:使用 varfor 循环中的异步操作出错

    javascript
    for (var i = 0; i < 3; i++) {
      setTimeout(function () {
        console.log("var i:", i);
      }, 100);
    }
    
    //输出
    //1. var i: 3
    //2. var i: 3
    //3. var i: 3
    
    // 原因:  1. 循环 0, i = 0; => 2. 把 setTimeout(() => console.log(i)) 放入宏任务队列 
    //        => 3. 循环 1, i = 1; => 4. 把 setTimeout(() => console.log(i)) 放入宏任务队列
    //        => 5. 循环 2, i = 2; => 6. 把 setTimeout(() => console.log(i)) 放入宏任务队列
    //        => 7. i++,变为 3,不再满足循环条件,循环结束 
    //        => 8. 主线程空闲,宏任务队列执行, 输出 var i: 3; var i: 3; var i: 3;

    应该使用:

    javascript
    for (let i = 0; i < 3; i++) {
      setTimeout(function () {
        console.log("let i:", i);
      }, 100);
    }
    
    //输出
    //1. let i: 0
    //2. let i: 1
    //3. let i: 2
    
    // 原因:  1. 循环 0, 创建新的块作用域, 创建 let i = 0; => 2. 把 setTimeout(() => console.log(i)) (捕获 i=0) 放入宏任务队列 
    //        => 3. 循环 1, 创建新的块作用域, 创建 let i = 1; => 4. 把 setTimeout(() => console.log(i)) (捕获 i=1) 放入宏任务队列
    //        => 5. 循环 2, 创建新的块作用域, 创建 let i = 2; => 6. 把 setTimeout(() => console.log(i)) (捕获 i=2) 放入宏任务队列
    //        => 7. i++,变为 3,不再满足循环条件,循环结束 
    //        => 8. 主线程空闲,宏任务队列执行, 输出 let i: 0; let i: 1; let i: 2;

    也可以使用 使用 var + IIFE(立即执行函数表达式):

    javascript
    for (var i = 0; i < 3; i++) {
      (function (j) {
        setTimeout(() => {
          console.log("var+j i:", j);
        }, 0);
      })(i);
    }
    
    //输出
    //1. var+j i: 0
    //2. var+j i: 1
    //3. var+j i: 2
    
    // 原因
    // 每次循环时,调用一个立即执行函数,将 i 的值传进去
    // 这个 j 是参数,属于 IIFE 的局部变量
    // 回调闭包捕获的是 j,不会被后续循环影响
  • const 声明不会改变的值(如配置信息、常量)

  • 避免重复声明变量

  • 避免不使用 varletconst 来声明变量而直接赋值,会被自动变成window全局变量(在非严格模式下)


数据类型

JavaScript 的数据类型分为 基本类型(primitive types)引用类型(reference types)


基本类型(Primitive Types)

基本类型是不可变、按值传递的。

类型示例说明
number42, 3.14, NaN, Infinity所有数字,包含整数和浮点数
string'hello', "world"字符串
booleantrue, false布尔值
undefinedlet x;x === undefined未赋值时的默认值
nullnull表示“空值”或“无对象”
bigint1234567890123456789012345n超过 Number.MAX_SAFE_INTEGER 的整数
symbolSymbol('id')创建独一无二的值(常用于对象属性)

基本类型是不可变的值类型x = y 赋值后两者互不影响。

四舍五入

场景用法
四舍五入整数Math.round(num)
保留 n 位小数Math.round(num * 10^n) / 10^n
转字符串保留小数num.toFixed(n)
精确金融运算(精度丢失)decimal.javascript 等库
银行家舍入自定义函数

toString

在 JavaScript 中,toString() 方法可以将对象、数组、数值、函数等转为字符串,但不同类型的 toString() 行为和格式是不同的,下面是详细的分类讲解与格式说明:


  1. 基本数据类型的 toString() 格式
  • 数字类型
javascript
let num = 255;
console.log(num.toString());     // "255"
console.log(num.toString(2));    // "11111111"(二进制)
console.log(num.toString(16));   // "ff"(十六进制)
  • Number.prototype.toString([radix])
  • radix 是进制(2–36),默认为 10

  • 布尔类型
javascript
true.toString();   // "true"
false.toString();  // "false"

  • 字符串类型

字符串调用 toString() 不变:

javascript
"hello".toString();  // "hello"

  1. 引用类型的 toString() 格式
  • 数组
javascript
[1, 2, 3].toString();     // "1,2,3"
[null, undefined].toString();  // ","
[].toString();            // ""
  • 等价于 Array.prototype.join(',')

  • 对象
javascript
({ a: 1 }).toString();    // "[object Object]"

默认行为是:

javascript
Object.prototype.toString.call(value); // 标准类型标签

例如:

javascript
Object.prototype.toString.call([]);           // "[object Array]"
Object.prototype.toString.call(null);         // "[object Null]"
Object.prototype.toString.call(new Date());   // "[object Date]"

  • 函数
javascript
function greet() { return "hi"; }
console.log(greet.toString());

输出函数的源码字符串:

javascript
"function greet() { return \"hi\"; }"

  • Date 对象
javascript
new Date().toString(); // e.g. "Tue May 13 2025 17:40:00 GMT+0800 (China Standard Time)"
方法名返回值示例描述
toString()"Tue May 13 2025 20:30:00 GMT+0800 (CST)"本地时间字符串(默认)
toDateString()"Tue May 13 2025"本地日期(无时间)
toTimeString()"20:30:00 GMT+0800 (CST)"本地时间(无日期)
toUTCString()"Tue, 13 May 2025 12:30:00 GMT"UTC 格式字符串
toISOString()"2025-05-13T12:30:00.000Z"ISO 8601 格式(UTC)
toLocaleString()"2025/5/13 20:30:00"(取决于地区)本地化日期+时间

  • 自定义对象的 toString() 方法

你可以自定义对象的 toString() 方法:

javascript
const person = {
  name: "Alice",
  toString() {
    return `Person: ${this.name}`;
  }
};

console.log(person + "");  // "Person: Alice"

  • 原型链上的 toString() 差异

所有对象继承自 Object.prototype,如果你没有覆写 toString(),就会默认返回 "[object Object]"。你可以使用:

javascript
Object.prototype.toString.call(value);

来获取准确的类型标签

返回
[]"[object Array]"
{}"[object Object]"
null"[object Null]"
undefined"[object Undefined]"
1"[object Number]"
/regex/"[object RegExp]"

symbol

特点说明
唯一性每次调用 Symbol() 都会生成一个独一无二的值
不可隐式转换字符串与字符串拼接时会报错,只能显式转成字符串
可作为对象键可用作对象属性的唯一键,不会与其他键冲突
不可枚举for...inObject.keys() 等方法无法获取 Symbol 键
支持全局注册表通过 Symbol.for() 实现跨文件共享同一个 Symbol 值

示例:

javascript
// 创建 symbol
const id1 = Symbol('id');
const id2 = Symbol('id');
console.log(id1 === id2); // false(唯一性)

// 用作对象键
const user = {
  [id1]: 123
};
console.log(user[id1]);   // 123

// Symbol 不能和字符串自动拼接
// console.log("User ID is: " + id1); // ❌ TypeError
console.log("User ID is: " + id1.toString()); // ✅

Symbol.for() vs Symbol()

javascript
const a = Symbol.for('key');
const b = Symbol.for('key');
console.log(a === b); // true(从全局注册表获取)

const x = Symbol('key');
const y = Symbol('key');
console.log(x === y); // false(每次唯一)

内置symbol(Well-known Symbols)

内置 SymbolJavaScript 提供的一组预定义 Symbol 值,它们挂在全局 Symbol 对象上,用于改变对象的默认行为或与语言底层机制交互。

内置 Symbol类型/接口用途简述
Symbol.iterator可迭代协议对象支持 for...of、展开运算符
Symbol.asyncIterator异步可迭代协议支持 for await...of 的异步迭代器
Symbol.toPrimitive类型转换控制对象转换为原始值时的行为
Symbol.toStringTag类型标签控制 Object.prototype.toString.call(obj) 的返回值
Symbol.hasInstanceinstanceof 运算符控制某个对象被 instanceof 判断时的结果
Symbol.isConcatSpreadable数组操作控制 concat() 时是否将对象“展开”
Symbol.species构造函数派生控制像 .map() 这样的方法返回什么构造函数创建的对象
Symbol.match字符串匹配自定义 str.match(obj) 行为
Symbol.replace字符串替换自定义 str.replace(obj) 行为
Symbol.search字符串搜索自定义 str.search(obj) 行为
Symbol.split字符串拆分自定义 str.split(obj) 行为
Symbol.unscopableswith 语句指定对象属性在 with 环境中是否可用(一般不推荐使用)

示例:

  • Symbol.iterator

定义对象的默认迭代器,让对象可用于 for...of、展开语法等。

javascript
const myIterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  }
};
console.log([...myIterable]); // [1, 2, 3]

  • Symbol.toPrimitive

控制对象到原始类型(如数字或字符串)的转换行为。

javascript
const obj = {
  [Symbol.toPrimitive](hint) {
    return hint === "number" ? 42 : "custom";
  }
};
console.log(+obj);     // 42
console.log(`${obj}`); // "custom"

  • Symbol.toStringTag

定义对象的类型标签,影响 Object.prototype.toString.call() 的输出。

javascript
const obj = {
  [Symbol.toStringTag]: "MyTag"
};
console.log(Object.prototype.toString.call(obj)); // "[object MyTag]"

  • Symbol.hasInstance

控制对象是否为某个构造函数的“实例”。

javascript
class MyClass {
  static [Symbol.hasInstance](instance) {
    return instance.name === "special";
  }
}
console.log({ name: "special" } instanceof MyClass); // true

  • Symbol.isConcatSpreadable

决定对象在 concat() 中是否被“打平”。

javascript
const arrLike = {
  0: "a",
  1: "b",
  length: 2,
  [Symbol.isConcatSpreadable]: true
};
console.log(["x"].concat(arrLike)); // ["x", "a", "b"]

  • Symbol.species

控制子类方法如 map()filter() 等返回的构造函数。

javascript
class MyArray extends Array {
  static get [Symbol.species]() {
    return Array;
  }
}
const a = new MyArray(1, 2, 3);
const b = a.map(x => x * 2);
console.log(b instanceof MyArray); // false
console.log(b instanceof Array);   // true

  • Symbol.match / replace / search / split

自定义字符串方法行为。

javascript
const matcher = {
  [Symbol.match](str) {
    return str.includes("test");
  }
};
console.log("This is a test".match(matcher)); // true

  • Symbol.unscopables

定义 with 环境中哪些属性应被忽略(不推荐使用 with)。

javascript
const obj = {
  foo: 1,
  [Symbol.unscopables]: { foo: true }
};

引用类型(Reference Types)

引用类型是可变的,存的是对象的地址引用

类型示例说明
Object{name: 'Alice'}所有对象的基类
Array[1, 2, 3]数组
Functionfunction() {}函数本质上是对象
Datenew Date()日期对象
RegExp/\d+/正则表达式
Mapnew Map()键值对集合(键可为任意值)
Setnew Set()不重复值集合

引用类型是按引用传递,多个变量可以指向同一对象。


Date(日期对象)

js
const now = new Date();
console.log(now.toISOString());  // 当前时间 ISO 格式

// 创建指定日期
const d = new Date("2025-05-21");

// 获取日期组件
console.log(d.getFullYear(), d.getMonth() + 1, d.getDate());

// 设置日期组件
d.setFullYear(2030);

类型判断方法

JavaScript 是动态类型语言,变量类型在运行时决定,因此:

  • 类型判断在调试、数据验证、类型保护、函数参数处理等场景非常关键。
  • 不同类型的判断方式适用场景不同,选择错误可能会导致 bug。

typeof — 判断基础类型

javascript
typeof 123        // "number"
typeof 'abc'      // "string"
typeof true       // "boolean"
typeof undefined  // "undefined"
typeof Symbol()   // "symbol"
typeof BigInt(1)  // "bigint"
typeof function(){} // "function"

陷阱:

javascript
typeof null     // "object" ❌ 历史 bug
typeof []       // "object" ❌

instanceof — 判断引用类型是否由某构造函数创建

javascript
[] instanceof Array              // true
{} instanceof Object             // true
new Date() instanceof Date       // true
(() => {}) instanceof Function   // true

限制:

  • 无法判断基础类型(例如 123 instanceof Number 为 false)
  • 不适合跨 iframe、window 判断(构造函数不同)

Object.prototype.toString.call(val) — 万能判断法

这是最准确的方式。

javascript
Object.prototype.toString.call(123);       // [object Number]
Object.prototype.toString.call('abc');     // [object String]
Object.prototype.toString.call(null);      // [object Null]
Object.prototype.toString.call(undefined); // [object Undefined]
Object.prototype.toString.call([]);        // [object Array]
Object.prototype.toString.call({});        // [object Object]
Object.prototype.toString.call(() => {});  // [object Function]

优势:

  • 可判断任意数据类型
  • 最精确,不受构造函数影响

Array.isArray() — 判断数组的推荐方式

javascript
Array.isArray([]);        // true
Array.isArray({});        // false
Array.isArray('abc');     // false

instanceof Array 更稳健(能跨 iframe 判断)。


.constructor — 查看构造函数

javascript
(123).constructor === Number         // true
'abc'.constructor === String         // true
[].constructor === Array             // true
({}).constructor === Object          // true

风险: 可被人为更改:

javascript
const arr = [];
arr.constructor = Object;
arr.constructor === Array;  // false ❌

推荐组合方案

目标推荐方式
判断基础类型typeof
判断是否为数组Array.isArray()
判断 nullval === null
判断引用类型准确类型Object.prototype.toString.call(val)
判断某类实例val instanceof Constructor

函数封装

javascript
function getType(val) {
  return Object.prototype.toString.call(val).slice(8, -1);
}

// 示例:
getType(null);        // "Null"
getType([]);          // "Array"
getType({});          // "Object"
getType(() => {});    // "Function"
getType(new Date());  // "Date"

跨 iframe/window 类型

每个 iframewindow 中都有自己的 JavaScript 全局环境(也就是自己的 globalThiswindowObjectArray 等构造函数)。

所以:iframe1.Array !== iframe2.Array, 虽然你在两个窗口中都写了 new Array(),它们是不同的构造函数实例。

html
<!-- 假设在主页面中引用了一个 iframe -->
<iframe id="myFrame" src="iframe.html"></iframe>

<script>
  const iframeWin = document.getElementById('myFrame').contentWindow;
  const arr = new iframeWin.Array();
  arr instanceof Array; // false (与当前的window是不同的构造函数)
</script>

由于 instanceof 比较的是 构造函数的引用地址,会在跨 window 场景失败,因此应该使用:Object.prototype.toString.call(arr); 这个方法只看内部 [[Class]] 标签,不受构造函数影响,因此跨上下文判断是安全的

类型转换

JavaScript 的类型转换是一个核心概念,因为它是一门动态类型语言,变量的数据类型可以自动转换。类型转换可以分为两大类:


  • 显式类型转换(Explicit Conversion)

开发者通过代码手动进行类型转换。

例如:

javascript
Number("123")   // 123
String(123)     // "123"
Boolean(0)      // false

  • 隐式类型转换(Implicit Conversion)

JavaScript 引擎在运行时自动进行的转换,也叫类型强制转换(Type Coercion)。

例如:

javascript
"5" + 1        // "51"(1 被转成字符串)
"5" - 1        // 4   ("5" 被转成数字)
true + 1       // 2   (true 转为 1)

转换规则

  1. 转为字符串(String)
  • 显式:
javascript
String(123)         // "123"
String(true)        // "true"
String(null)        // "null"
String(undefined)   // "undefined"
String({})          // "[object Object]"
  • 隐式:
javascript
123 + "abc"         // "123abc"
true + "test"       // "truetest"

加号 + 运算符涉及字符串时,会触发转字符串。


  1. 转为数字(Number)
  • 显式:
javascript
Number("123")       // 123
Number("abc")       // NaN
Number(true)        // 1
Number(false)       // 0
Number(null)        // 0
Number(undefined)   // NaN
  • 隐式:
javascript
"6" * 2             // 12
"6" - 1             // 5
true + 1            // 2
null + 1            // 1

除了加号 +,其他数学运算符都会触发数字转换。


  1. 转为布尔值(Boolean)
  • 显式:
javascript
Boolean(0)          // false
Boolean("")         // false
Boolean(null)       // false
Boolean(undefined)  // false
Boolean(NaN)        // false
Boolean([])         // true
Boolean({})         // true
  • 隐式:

在以下场景中,会触发布尔转换:

  • if (value)
  • while (value)
  • 三元运算符:value ? a : b
  • 逻辑运算符:&&||!

示例:

javascript
if ("hello") { console.log("yes"); } // 输出 "yes"

  1. 对象到原始值的转换(ToPrimitive)

当对象参与运算(比如加法或比较),会尝试转为原始值。

转换顺序:

  1. Symbol.toPrimitive
  2. 先调用 obj.valueOf(),如果是原始值就返回;
  3. 否则再调用 obj.toString()
  4. 如果仍不是原始值,就报错。

示例:

javascript
let obj = {
  valueOf() { return 42; },
  toString() { return "hello"; }
};

obj + 1    // 43

空对象 {} 的默认行为

javascript
console.log([]);  // ""

const obj = {};
console.log(obj.valueOf());   // 返回自身对象:{}
console.log(obj.toString());  // "[object Object]"

{} + 1  // → "[object Object]" + 1 → "[object Object]1"

//注意,下面情况 JS 解释器把 {} 解析为代码块,这时它就被忽略了
{} + []  // → [] → ""

//想明确表示它是对象,必须加括号
({} + [])  // → "[object Object]"

  1. 特殊值转换行为
转为 Boolean转为 Number转为 String
undefinedfalseNaN"undefined"
nullfalse0"null"
truetrue1"true"
falsefalse0"false"
""false0""
"123"true123"123"
"abc"trueNaN"abc"
[]true0""
[1,2]trueNaN"1,2"
{}trueNaN"[object Object]"

==(宽松等于)和 ===(严格等于)的区别

  • == 会进行类型转换:
javascript
0 == false       // true
"" == false      // true
null == undefined // true
  • === 不进行类型转换:
javascript
0 === false      // false
"" === false     // false
null === undefined // false

常见面试题

尽量使用 === !!!

javascript
[] == false   // true
// [] == false
// → [] 转成原始值 '' → '' == false
// → '' 转成数字 0,false 也转成 0 → 0 == 0

[] == ![]     // true
// [] == ![]
// → ![] 是 false,所以变成 [] == false(同上)

null == undefined // true

// 对象与对象比较的是引用,不是值。
{} == {}; // false
[] == []; // false

// NaN 不等于自身,是 JS 中少数需要特判的值
NaN === NaN        // false
isNaN(NaN)         // true
Number.isNaN(NaN)  // true(更严格)

运算符

JavaScript 的运算符(operators)是构建表达式和控制程序逻辑的核心工具。它们可以操作数值、字符串、对象等不同类型的数据。下面是 JS 中常见运算符的分类和用法讲解。


算术运算符(Arithmetic Operators)

用于执行数学计算:

运算符含义示例结果
+加法3 + 25
-减法3 - 21
*乘法3 * 26
/除法3 / 21.5
%取模(余数)5 % 21
**幂运算2 ** 38
++自增a++先返回 a,再加 1
--自减--a先减 1,再返回 a

赋值运算符(Assignment Operators)

用于给变量赋值或更新变量值:

运算符含义示例等同于
=赋值x = 5
+=加并赋值x += 2x = x + 2
-=减并赋值x -= 3x = x - 3
*=乘并赋值x *= 4x = x * 4
/=除并赋值x /= 2x = x / 2
%=取模并赋值x %= 2x = x % 2
**=幂并赋值x **= 3x = x ** 3

比较运算符(Comparison Operators)

用于判断两个值之间的关系,结果为布尔值(true 或 false):

运算符含义示例结果
==相等(类型转换)'5' == 5true
!=不相等'5' != 5false
===全等(值和类型)'5' === 5false
!==不全等'5' !== 5true
>大于5 > 3true
<小于5 < 3false
>=大于等于5 >= 5true
<=小于等于5 <= 3false

逻辑运算符(Logical Operators)

用于多条件判断或控制表达式执行:

运算符含义示例说明
&&与(and)a && ba 为真则返回 b,否则返回 a
||或(or)a || ba 为真则返回 a,否则返回 b
!非(not)!true取反:false

逻辑运算符的短路行为

&&|| 不一定返回布尔值,而是返回最后计算的操作数。

javascript
console.log(0 || "default");   // "default"
console.log("hello" && 123);   // 123
console.log(false && "fail");  // false

位运算符(Bitwise Operators)

用于对二进制位进行操作(一般用于底层优化或特殊逻辑):

运算符含义示例说明
&按位与5 & 30101 & 0011 = 0001
``按位或`53``01010011 = 0111`
^按位异或5 ^ 30101 ^ 0011 = 0110
~按位取反~5~00000101 = 11111010(负数)
<<左移5 << 11010(乘以 2)
>>右移5 >> 12(除以 2)
>>>无符号右移-5 >>> 1得到正整数

三元运算符(三目运算符)

javascript
const result = condition ? value1 : value2;

示例:

javascript
let age = 18;
let type = age >= 18 ? "adult" : "child"; // "adult"

typeof 和 instanceof 运算符

运算符含义示例
typeof返回数据类型字符串typeof "abc""string"
instanceof判断对象类型arr instanceof Arraytrue

解构与展开(ES6+)

虽然不属于传统运算符,但也像运算符使用:

  • 解构赋值
javascript
//数组解构
const [a, b] = [1, 2];
const [x, , y] = [10, 20, 30];  // x=10, y=30

//对象结构
const { x, y } = { x: 1, y: 2 };
//使用别名
const { name: userName } = person;
//使用默认值
const { gender = "unknown" } = person;
//undefined会被默认值覆盖但null不会
const [a = 1] = [undefined];  // a = 1
const [b = 2] = [null];       // b = null(不是默认值)
  • 展开运算符
javascript
//数组展开
const arr = [1, 2, 3];
const newArr = [...arr, 4, 5];  // [1, 2, 3, 4, 5]

//克隆数组
const copy = [...arr];

//组合多个数组
const all = [...arr1, ...arr2];

//对象展开
const obj = { a: 1, b: 2 };
const copy = { ...obj };           // 克隆
const merged = { ...obj, c: 3 };   // 合并新字段

//冲突覆盖
const o1 = { a: 1 };
const o2 = { a: 2, b: 3 };
const merged = { ...o1, ...o2 }; // { a: 2, b: 3 }

//对象展开拷贝只有一层
const o1 = { a: { b: 1 } };
const o2 = { ...o1 };
o2.a.b = 2;
console.log(o1.a.b);  // 2

表达式

在 JavaScript 中,**表达式(Expression)是任何能产生值(value)**的代码。它与“语句”不同 —— 语句是“做某事”,而表达式是“返回某个值”。


什么是表达式

一个表达式是可以被计算(evaluated)并且返回一个值的代码单位,例如:

javascript
3 + 4              // 表达式,结果是 7
"hello"            // 表达式,结果是 "hello"
x = 5              // 表达式,结果是 5(赋值表达式)
myFunction()       // 表达式,返回函数执行结果

表达式的类型

原始值表达式

直接使用字面量:

javascript
42         // 数字
"JS"       // 字符串
true       // 布尔
null       // null
undefined  // undefined

字面量

**字面量(Literal)**是指在代码中直接写出的、表示固定值的写法。

简单来说,字面量就是你写在代码里的值本身,不需要计算或函数调用。


变量表达式

访问变量名就是表达式:

javascript
let x = 10;
x; // 表达式,结果是 10

算术表达式

返回数学运算结果:

javascript
3 + 5 * 2     // 13
(10 - 2) / 4  // 2

赋值表达式

赋值操作本身是表达式,会返回被赋的值:

javascript
let y;
y = 7         // 表达式,值是 7
let z = (y = 3); // z = 3

逻辑表达式

&&||! 是逻辑运算符:

javascript
true && false  // false
"hello" || "world"  // "hello"

常用于默认值判断:

javascript
let name = inputName || "Guest";

比较表达式

返回布尔值的表达式:

javascript
5 > 3         // true
"a" === "a"   // true

函数表达式

  • 匿名函数表达式:
javascript
const greet = function(name) {
  return "Hi, " + name;
};
  • 箭头函数表达式:
javascript
const add = (a, b) => a + b;

:::


调用表达式

调用函数并返回值:

javascript
sayHello()       // 返回函数结果
Math.max(3, 7)   // 返回 7

数组/对象表达式

javascript
[1, 2, 3]           // 数组表达式
{ name: "JS" }      // 对象表达式

表达式 vs 语句的区别

项目表达式语句
定义产生值的代码执行动作的代码
示例3 + 4let x = 3 + 4;
返回值有返回值无返回值
用法可嵌套在语句中不可嵌套在表达式中

特殊表达式:条件(三元)表达式

javascript
let result = (score >= 60) ? "Pass" : "Fail";

这是一种表达式,不是语句,返回一个值。


语句

在 JavaScript 中,**语句(statement)**是用来执行特定操作的代码单位。每一条语句告诉浏览器执行一件事,比如声明变量、判断条件、循环、调用函数等。


声明语句

用于创建变量、常量或函数。

  • varletconst:声明变量或常量
javascript
let name = "Alice";
const PI = 3.14;
var age = 30;
  • function:声明函数
javascript
function greet() {
  console.log("Hello!");
}

表达式语句

表达式可以被当作语句使用(即表达式后加分号)。

javascript
x = 5 + 3;
console.log(x);

条件语句

根据条件执行不同的代码块。

  • if / else if / else
javascript
if (score > 90) {
  console.log("Excellent!");
} else if (score > 60) {
  console.log("Passed.");
} else {
  console.log("Failed.");
}
  • switch
javascript
switch (day) {
  case 1: console.log("Monday"); break;
  case 2: console.log("Tuesday"); break;
  default: console.log("Unknown");
}

循环语句

反复执行代码块。

  • for
javascript
for (let i = 0; i < 5; i++) {
  console.log(i);
}
  • while
javascript
let i = 0;
while (i < 5) {
  console.log(i);
  i++;
}
  • do...while
javascript
let i = 0;
do {
  console.log(i);
  i++;
} while (i < 5);
  • for...in / for...of
javascript
for (let key in obj) { ... }
for (let item of array) { ... }

跳转语句

控制流程转向。

  • break:跳出循环或 switch
  • continue:跳过本次循环
  • return:从函数返回
  • throw:抛出异常

异常处理语句

处理程序运行时的错误。

javascript
try {
  riskyFunction();
} catch (error) {
  console.error("Error occurred:", error);
} finally {
  console.log("Cleanup code.");
}

空语句

只有一个分号,什么都不做,偶尔用于占位。

javascript
;

提示

语句通常以分号(;)结尾,虽然 JavaScript 有自动分号插入机制,但最好显式写出分号以避免意外错误。


函数

在 JavaScript 中,函数(Function)是核心概念之一,是用来封装可重用代码块的机制。它是 JavaScript 的一等公民,可以像变量一样传递、赋值、嵌套。


函数的定义方式

1. 函数声明(Function Declaration)

javascript
function greet(name) {
  return "Hello, " + name;
}

函数提升

JavaScript 引擎在代码执行前,会先预处理 变量函数声明,把它们提升到作用域的顶部。

javascript
foo(); // "hello"
function foo() {
  console.log("hello");
}

2. 函数表达式(Function Expression)

javascript
const greet = function(name) {
  return "Hello, " + name;
};

函数表达式不会提升

javascript
bar(); // TypeError: bar is not a function
var bar = function () {
  console.log("hi");
};

//var bar 被提升(值为 undefined)但函数赋值在运行阶段才赋上去

3. 箭头函数(Arrow Function)

javascript
const greet = name => "Hello, " + name;
  • 箭头函数的this指向

普通函数的 this 是调用时绑定,动态决定;

而箭头函数不会创建自己的 this,而是继承外层作用域的 this

不建议用箭头函数作为对象方法,因为 this 不是指向对象,而是定义时的外层上下文。

javascript
function Timer() {
  this.seconds = 0;
  setInterval(() => {
    this.seconds++;
    console.log(this.seconds); // 正常访问 Timer 实例的 this
  }, 1000);
}
new Timer();


const obj = {
  count: 10,
  doSomethingLater: function () {
    setTimeout(() => {
      console.log(this.count);
    }, 1000);
  }
};

obj.doSomethingLater(); //输出 10,因为箭头函数继承了 doSomethingLater 的 this,即 obj。

const obj1 = {
  count: 10,
  doSomethingLater: function () {
    setTimeout(function () {
      console.log(this.count);
    }, 1000);
  }
};

obj1.doSomethingLater(); //输出 function,因为普通函数继动态绑定this,可以使用.bind(this)或者使用 self 或 that 保存 this 实现类似功能。
  • 箭头函数不能作为构造函数(不能用 new)

箭头函数没有 [[Construct]] 内部方法,没有 prototype 属性。

javascript
const Person = (name) => {
  this.name = name;
};
const p = new Person('Tom'); // ❌ TypeError: Person is not a constructor
  • 箭头函数没有 arguments 对象

箭头函数中访问不到 arguments,可以用 rest 参数代替。

javascript
const logArgs = (...args) => {
  console.log(args);
};
  • 箭头函数不能使用 yield,因此不能定义为 generator 函数
javascript
const gen = *() => { 
  yield 1;
  yield 2;
  yield 3; 
} // ❌ SyntaxError
  • 箭头函数的返回值
javascript
const add = (a, b) => { a + b };
console.log(add(2, 3)); //输出 undefined。箭头函数使用大括号时必须写 return,否则默认不返回值。

4. 匿名函数(Anonymous Function)

没有名字的函数,多用于回调。

javascript
setTimeout(function() {
  console.log("Hello");
}, 1000);

5. 立即执行函数(IIFE)

立即执行函数(Immediately Invoked Function Expression,)是一个被立即调用的函数表达式(不是函数声明),一般用来创建作用域。

javascript
(function() { ... })();   // 常见写法
(function() { ... }());   // 也可以
!function() { ... }();     // 也可以(少见写法)
+function() { ... }();     // 有效(但不推荐)

应用:

  • 封装变量,避免污染全局
javascript
var x = 100;

(function () {
  var x = 10;
  console.log("内部x:", x); // 10
})();

console.log("全局x:", x); // 100
  • 初始化代码执行一次
javascript
const config = (function () {
  const apiKey = "abc123";
  const env = "production";
  return { apiKey, env };
})();
  • 在循环中“锁定”变量值(使用 var)
javascript
for (var i = 0; i < 3; i++) {
  (function (j) {
    setTimeout(() => {
      console.log(j); // 0, 1, 2
    }, j * 1000);
  })(i);
}

函数的参数

1. 默认参数

javascript
function greet(name = "Guest") {
  return `Hello, ${name}`;
}

2. 剩余参数(Rest Parameters)

javascript
function sum(...numbers) {
  return numbers.reduce((a, b) => a + b);
}

3. arguments 对象(仅普通函数)

javascript
function showArgs() {
  console.log(arguments);
}

函数特点

  • 函数可以作为值赋给变量:

    javascript
    const sayHi = function() {};
  • 函数可以作为参数传递:

    javascript
    function callLater(callback) {
      callback();
    }
  • 函数可以作为返回值返回:

    javascript
    function multiplier(x) {
      return function(y) {
        return x * y;
      };
    }

作用域链

  • 什么是作用域

**作用域(Scope)**是变量的可访问范围。JavaScript 有以下几种作用域:

  • 全局作用域:在所有函数外定义的变量(包括未声明直接赋值的变量);

  • 函数作用域:函数内部定义的变量只能在函数内访问;

  • 块级作用域(ES6):使用 let / const 在 {} 中定义的变量仅在块内有效。

  • 什么是作用域链?

当访问变量时,JavaScript 会从当前作用域开始查找,若找不到,就沿着父作用域一层层向上查找,直到全局作用域为止。这个查找路径形成了作用域链

作用域链是词法静态的,取决于函数定义位置,而非调用位置。

示例:

javascript
var a = 10;

function outer() {
  var b = 20;

  function inner() {
    var c = 30;
    console.log(a, b, c);
  }

  inner();
}

outer(); // 输出:10 20 30

//查找变量 a:在 inner 中找不到,去 outer,再去全局。[inner → outer → global]

闭包

闭包是指一个函数能够访问其词法作用域中定义的变量,即使这个函数是在其作用域外被调用的。

换句话说:

函数 + 它所捕获的作用域变量 = 闭包

javascript
function outer() {
  let x = 10;
  return function inner() {
    console.log(x); // 闭包访问外层作用域
  };
}

创建闭包的常见方式

  • 返回函数
javascript
function outer() {
  var count = 0;
  return function () {
    count++;
    console.log(count);
  };
}

const counter = outer();
counter(); // 1
counter(); // 2
  • 在异步中使用
javascript
function greet(name) {
  setTimeout(function () {
    console.log("Hello, " + name);
  }, 1000);
}

greet("Alice");
  • 使用闭包实现私有变量

js中没有访问修饰符。

javascript
function createCounter() {
  let count = 0; // 私有变量

  return {
    increment() {
      count++;
      console.log(count);
    },
    decrement() {
      count--;
      console.log(count);
    }
  };
}

const counter = createCounter();
counter.increment(); // 输出:1
counter.increment(); // 输出:2
counter.decrement(); // 输出:1

注意

  • 闭包会导致变量不会被释放,可能导致内存泄露,慎重使用
  • 每个闭包都会创建新的作用域链 ,大量使用会带来性能问题
  • 闭包和 this 无直接关系, this 是运行时绑定,与闭包无关

对象

在 JavaScript 中,**对象(Object)是最核心的数据类型之一,用于存储键值对(key-value)**的数据结构。


什么是对象?

对象是一个由属性和方法组成的无序集合。

javascript
const person = {
  name: "Alice",
  age: 30,
  greet: function () {
    console.log("Hello, I'm " + this.name);
  }
};
  • nameage属性(property)
  • greet()方法(method)
  • this 指向当前对象

对象的创建方式

1. 对象字面量(最常用)

javascript
const obj = { a: 1, b: 2 };

2. 使用 new Object()

javascript
const obj = new Object();
obj.a = 1;

3. 使用构造函数

javascript
function Person(name) {
  this.name = name;
}
const p = new Person("Tom");

4. 使用 Object.create(proto)

javascript
const parent = { greet() { console.log("Hi"); } };
const child = Object.create(parent);
child.name = "Child";

5. 使用类(ES6)

javascript
class Animal {
  constructor(type) {
    this.type = type;
  }
}
const dog = new Animal("dog");

属性访问与操作

javascript
const obj = { name: "Bob", age: 25 };

// 点语法
console.log(obj.name); // "Bob"

// 中括号语法(适合变量动态访问)
console.log(obj["age"]); // 25

// 添加属性
obj.gender = "male";

// 删除属性
delete obj.age;

遍历对象属性

1. for...in(可枚举自身+继承属性)

javascript
for (let key in obj) {
  console.log(key, obj[key]);
}

2. Object.keys() / Object.values() / Object.entries()

javascript
Object.keys(obj);    // ['name', 'gender']
Object.values(obj);  // ['Bob', 'male']
Object.entries(obj); // [['name', 'Bob'], ['gender', 'male']]

对象的特性

  • 对象是引用类型

当将一个引用类型的变量赋值给另一个变量时,复制的是引用地址,而不是对象本身。

javascript
const a = { value: 10 };
const b = a;
b.value = 20;
console.log(a.value); // 20(引用指向同一个对象)
  • 对象比较的行为

在比较对象时,JavaScript 比较的是引用地址,而不是对象的内容。

javascript
let objA = { value: 10 };
let objB = { value: 10 };
console.log(objA === objB); // 输出: false
  • 对象的动态性与可变性

引用类型的对象是动态的,可以随时添加、修改或删除其属性。

javascript
let person = {};
person.name = "John"; // 添加属性
person.age = 30;
delete person.age;    // 删除属性
console.log(person);  // 输出: { name: "John" }
  • 深拷贝与浅拷贝

由于对象赋值是引用复制,多个变量可能指向同一个对象,导致修改一个变量会影响到其他变量。

为了避免这种情况,可以使用深拷贝创建对象的独立副本。

javascript

//浅拷贝
const original = { a: 1, b: { c: 2 } };
const shallowCopy = { ...original };
shallowCopy.b.c = 3;
console.log(original.b.c); // 输出: 3
// shallowCopy 是 original 的浅拷贝,修改嵌套对象 b.c 的值会影响到原对象。

//深拷贝
//1. JSON.parse(JSON.stringify(obj))
//优点:简单易用,适用于纯数据对象。
//缺点:无法处理函数、undefined、Symbol、循环引用、Date、RegExp、Map、Set 等特殊对象。
const original = { name: "Alice", details: { age: 25 } };
const copy = JSON.parse(JSON.stringify(original));
copy.details.age = 30;
console.log(original.details.age); // 输出: 25

//2. structuredClone(obj)
//优点:原生支持,能处理大多数数据类型,包括 Date、RegExp、Map、Set 等。
//缺点:不支持函数和某些特殊对象;在某些环境中可能不兼容。
const original = { a: 1, b: { c: 2 } };
const deepCopy = structuredClone(original);
deepCopy.b.c = 3;
console.log(original.b.c); // 输出: 2

//3. 第三方库(如 Lodash 的 cloneDeep)
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
const deepCopy = _.cloneDeep(original);
deepCopy.b.c = 3;
console.log(original.b.c); // 输出: 2
  • 垃圾回收机制

JavaScript 的垃圾回收器会自动释放不再被引用的对象所占用的内存。当一个对象没有任何引用指向它时,它就会被视为不可访问,进而被垃圾回收器回收。

javascript
let obj = { name: "Alice" };
obj = null; // 原对象不再被引用,等待垃圾回收

对象的常用方法

  1. Object.assign()

用于将一个或多个源对象的属性复制到目标对象中,常用于对象的合并或浅拷贝。

javascript
const target = { a: 1 };
const source = { b: 2 };
const result = Object.assign(target, source);
console.log(result); // 输出: { a: 1, b: 2 }
  1. Object.create()

创建一个新对象,使用指定的原型对象和可选的属性。

javascript
const proto = { greet() { console.log("Hello"); } };
const obj = Object.create(proto);
obj.greet(); // 输出: Hello
  1. Object.keys()

返回一个数组,包含对象自身的所有可枚举属性的键名。

javascript
const obj = { a: 1, b: 2 };
console.log(Object.keys(obj)); // 输出: ['a', 'b']
  1. Object.values()

返回一个数组,包含对象自身的所有可枚举属性的键值。

javascript
const obj = { a: 1, b: 2 };
console.log(Object.values(obj)); // 输出: [1, 2]
  1. Object.entries()

返回一个数组,包含对象自身的所有可枚举属性的键值对数组。

javascript
const obj = { a: 1, b: 2 };
console.log(Object.entries(obj)); // 输出: [['a', 1], ['b', 2]]
  1. Object.fromEntries()

将键值对数组转换为对象,常与 Object.entries() 搭配使用。

javascript
const entries = [['a', 1], ['b', 2]];
const obj = Object.fromEntries(entries);
console.log(obj); // 输出: { a: 1, b: 2 }
  1. Object.freeze()

冻结一个对象,防止对其进行修改(添加、删除或更改属性)。

javascript
const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2;
console.log(obj.a); // 输出: 1
  1. Object.seal()

密封一个对象,防止添加或删除属性,但可以修改已有属性的值。

javascript
const obj = { a: 1 };
Object.seal(obj);
obj.a = 2;
console.log(obj.a); // 输出: 2
  1. Object.hasOwnProperty()

判断对象是否具有指定的自身属性。

javascript
const obj = { a: 1 };
console.log(obj.hasOwnProperty('a')); // 输出: true
  1. Object.getPrototypeOf()Object.setPrototypeOf()

获取或设置对象的原型。

javascript
const proto = {};
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // 输出: true

原型与原型链

在 JavaScript 中,**原型(Prototype)原型链(Prototype Chain)**是理解对象继承和属性查找机制的关键概念。它们构成了 JavaScript 的核心特性之一,尤其在实现继承和共享属性方面起着重要作用。


什么是原型(Prototype)

在 JavaScript 中,每个对象(除了 null)在创建时都会与另一个对象关联,这个对象就是其原型。原型本身也是一个对象,因此也有自己的原型,形成一个链式结构,直到某个对象的原型为 null 为止,这个链式结构被称为原型链

每个函数都有一个特殊的属性 prototype,它指向一个对象,称为该函数的原型对象。当使用构造函数创建新对象时,新对象的内部属性 [[Prototype]] 会被设置为构造函数的 prototype 属性。

例如:

javascript
function Person(name) {
  this.name = name;
}
Person.prototype.sayHello = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const alice = new Person('Alice');
alice.sayHello(); // 输出: Hello, I'm Alice

在这个例子中,alice 对象的原型是 Person.prototype,因此可以访问 sayHello 方法。


什么是原型链(Prototype Chain)

原型链是由多个对象通过其原型连接形成的链式结构。当访问一个对象的属性或方法时,JavaScript 引擎会首先在该对象自身查找,如果找不到,则会沿着原型链向上查找,直到找到该属性或方法,或者到达原型链的末尾(即原型为 null 的对象)。([CSDN博客][3], [CSDN博客][1])

例如:

javascript
console.log(alice.toString()); // 输出: [object Object]

虽然 alice 对象没有定义 toString 方法,但它的原型链上有 Object.prototype,而 Object.prototype 定义了 toString 方法,因此可以调用。

原型链的结构如下:

javascript
// 每个箭头表示对象的原型指向。
alice --> Person.prototype --> Object.prototype --> null

原型和原型链的作用

  1. 实现继承:通过原型链,JavaScript 对象可以继承其他对象的属性和方法,实现代码复用。
  2. 属性查找机制:当访问对象的属性或方法时,JavaScript 会沿着原型链查找,直到找到为止。这种机制使得对象可以共享属性和方法。
  3. 节省内存:将共享的属性和方法定义在原型上,可以避免每个实例都创建一份,节省内存空间。

注意事项

  • 原型链的终点是 null:原型链最终会指向 null,表示没有更多的原型可查找。
  • 避免过深的原型链:过深的原型链会增加属性查找的时间,影响性能。
  • 修改原型会影响所有实例:如果修改了构造函数的原型对象,所有通过该构造函数创建的实例都会受到影响。

原型模式

JavaScript 中,**原型模式(Prototype Pattern)**是一种创建型设计模式,它通过复制(克隆)已有对象来创建新对象,而不是通过实例化类。 这种模式利用了 JavaScript 的原型机制,使得对象之间可以共享属性和方法,从而提高代码的复用性和效率。

原型模式的核心思想是:

使用一个已经创建的对象作为原型,通过复制该原型对象来创建一个与原型相同或相似的新对象。

JavaScript 中,由于其基于原型的特性,每个对象都可以作为其他对象的原型。这使得原型模式在 JavaScript 中具有天然的优势。


如何实现原型模式

    1. 使用 Object.create()

Object.create() 方法创建一个新对象,使用指定的原型对象和可选的属性。

javascript
const prototypeObject = {
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

const newObj = Object.create(prototypeObject);
newObj.name = 'Alice';
newObj.greet(); // 输出: Hello, I'm Alice

在这个例子中,newObj 继承了 prototypeObject 的方法 greet,实现了对象之间的属性和方法共享。([阿里云开发者社区][4])

  1. 使用构造函数和原型

通过构造函数定义对象的属性,并将方法添加到构造函数的原型上,实现方法共享。([CSDN博客][5])

javascript
function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  console.log(`Hello, I'm ${this.name}`);
};

const alice = new Person('Alice');
alice.greet(); // 输出: Hello, I'm Alice

在上述示例中,所有通过 Person 构造函数创建的实例都共享 greet 方法,节省了内存。


在 JavaScript 中,**原型模式(Prototype Pattern)**是一种创建型设计模式,它通过克隆已有对象来创建新对象,而不是通过实例化类。这种模式充分利用了 JavaScript 的原型机制,使得对象之间可以共享属性和方法,从而提高代码的复用性和效率。


原型模式的常见应用场景

  1. 对象模板与克隆

当需要创建多个结构相似但数据不同的对象时,可以先定义一个原型对象,然后通过克隆该原型对象来创建新对象。

javascript
const employeePrototype = {
  greet() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

const employee1 = Object.create(employeePrototype);
employee1.name = 'Alice';
employee1.greet(); // 输出: Hello, I'm Alice

在上述示例中,employee1 继承了 employeePrototype 的方法 greet,实现了对象之间的属性和方法共享。

  1. 性能优化

对于需要进行复杂初始化的对象,可以先创建一个原型对象,然后通过克隆该原型对象来创建新对象,避免重复的初始化过程,从而提高性能。

javascript
function HeavyObject() {
  this.data = /* 复杂的初始化过程 */;
}

HeavyObject.prototype.clone = function() {
  const clone = Object.create(this);
  // 如果需要,复制或重置特定属性
  return clone;
};

const prototypeObj = new HeavyObject();
const newObj = prototypeObj.clone();

通过这种方式,可以避免每次都进行复杂的初始化过程,提高对象创建的效率。

  1. UI 组件复用

在前端开发中,常常需要创建多个相似的 UI 组件。可以先定义一个组件的原型对象,然后通过克隆该原型对象来创建新组件,实现组件的复用。

javascript
const buttonPrototype = {
  type: 'button',
  render() {
    console.log(`Rendering a ${this.type} with label: ${this.label}`);
  }
};

const submitButton = Object.create(buttonPrototype);
submitButton.label = 'Submit';
submitButton.render(); // 输出: Rendering a button with label: Submit

通过这种方式,可以快速创建多个相似的组件,提高开发效率。


工厂函数

在 JavaScript 中,**工厂函数(Factory Function)**是一种创建对象的函数,它不使用 classconstructor,而是通过返回一个新对象来实现实例化。

工厂函数提供了一种灵活的方式来生成对象,尤其适用于需要封装私有数据或避免使用 thisnew 的场景。


什么是工厂函数?

工厂函数是一个普通的函数,它返回一个新对象。与构造函数不同,工厂函数不需要使用 new 关键字,也不依赖于 this。这使得工厂函数在某些情况下更容易理解和使用。

示例:

javascript
function createPerson(name, age) {
  return {
    name,
    age,
    greet() {
      console.log(`Hello, I'm ${this.name}`);
    }
  };
}

const person1 = createPerson('Alice', 30);
person1.greet(); // 输出: Hello, I'm Alice

工厂函数与闭包

工厂函数可以利用闭包来创建私有变量,从而实现数据的封装。

示例:

javascript
function createCounter() {
  let count = 0;
  return {
    increment() {
      count++;
      console.log(count);
    },
    decrement() {
      count--;
      console.log(count);
    }
  };
}

const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
counter.decrement(); // 输出: 1

工厂函数 vs 构造函数 vs 类

特性工厂函数构造函数类(ES6)
使用 new
使用 this
私有变量支持是(通过闭包)否(需额外手段)是(通过私有字段)
原型方法共享否(每个实例独立)
可读性与简洁性
适用场景简单对象、私有数据封装原型继承、性能优化面向对象编程、继承关系

工厂函数适合用于创建简单对象或需要封装私有数据的场景;构造函数和类则更适合需要原型继承和性能优化的复杂对象结构。


工厂函数的优点

  • 避免使用 newthis:减少了上下文绑定错误的可能性。
  • 支持私有数据:通过闭包实现数据封装。
  • 灵活性高:可以根据需要返回不同结构的对象。
  • 易于测试和维护:函数式风格使得代码更易于理解和测试。

代理

在 JavaScript 中,代理(Proxy) 是一种用于定义对象基本操作行为的机制,比如属性访问、赋值、函数调用等。它允许你拦截和自定义这些行为。


基本语法

javascript
const proxy = new Proxy(target, handler);
  • target: 被代理的对象。
  • handler: 一个对象,包含拦截操作的函数(称为“陷阱”trap)。

示例:拦截属性读取

javascript
const person = {
  name: 'Alice',
  age: 30
};

const proxy = new Proxy(person, {
  get(target, property) {
    console.log(`读取属性:${property}`);
    return target[property];
  }
});

console.log(proxy.name); // 控制台输出:读取属性:name,然后输出 Alice

常用的拦截器(Handler 方法)

拦截器(Trap)用途说明
get读取属性时触发
set修改属性值时触发
has使用 in 操作符时触发
deleteProperty使用 delete 删除属性时触发
ownKeys使用 Object.keys()for...in 时触发
apply函数被调用时触发(函数代理)
construct使用 new 调用构造函数时触发

示例:限制属性写入

javascript
const proxy = new Proxy(person, {
  set(target, property, value) {
    if (property === 'age' && typeof value !== 'number') {
      throw new TypeError('年龄必须是数字');
    }
    target[property] = value;
    return true;
  }
});

proxy.age = 35;       // ✅ 正常赋值
proxy.age = 'thirty'; // ❌ 抛出 TypeError

函数代理示例

javascript
function sum(a, b) {
  return a + b;
}

const proxyFunc = new Proxy(sum, {
  apply(target, thisArg, args) {
    console.log(`调用函数:${args}`);
    return target(...args);
  }
});

proxyFunc(3, 4); // 输出:调用函数:3,4,返回 7

应用场景

  • 数据验证(如 Vue 的响应式系统)
  • 自动填充默认值
  • 访问日志记录
  • 防止非法访问
  • 构建动态 API

示例一:响应式数据绑定(类似 Vue 2/3)

使用 Proxy 可以追踪对象属性的“读取”和“修改”,从而实现响应式数据绑定。

javascript
function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      console.log(`读取属性:${key}`);
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      console.log(`设置属性:${key} = ${value}`);
      const result = Reflect.set(target, key, value);
      // 通常这里可以触发更新 DOM
      return result;
    }
  });
}

// 使用
const state = reactive({ count: 0 });

console.log(state.count); // 打印:读取属性:count,返回 0

state.count = 1;          // 打印:设置属性:count = 1

在 Vue 3 中就是用 Proxy 实现的响应式数据系统,而 Vue 2 用的是 Object.defineProperty


示例二:API 包装器(拦截 API 请求或统一处理)

通过 Proxy,可以封装一个 API 对象,自动处理路径拼接、鉴权、日志、错误处理等。

javascript
const apiBase = 'https://api.example.com/';

const api = new Proxy({}, {
  get(target, prop) {
    return async function(params) {
      const url = `${apiBase}${prop}`;
      console.log(`请求:${url},参数:`, params);
      try {
        const response = await fetch(url, {
          method: 'POST',
          body: JSON.stringify(params),
          headers: { 'Content-Type': 'application/json' }
        });
        return await response.json();
      } catch (err) {
        console.error(`调用 ${prop} 失败`, err);
        throw err;
      }
    };
  }
});

// 使用示例
api.login({ username: 'user', password: '123456' });
// 自动发起 POST 请求到 https://api.example.com/login

优点:

  • 接口统一:所有接口都使用相同的写法,比如 api.xxx(params)
  • 自动拼接 URL
  • 可以集中处理异常、token、缓存等逻辑

this

在 JavaScript 中,this 是一个关键字,表示函数执行时的上下文对象。它的值并不是在函数定义时确定的,而是在函数调用时根据调用方式动态绑定的。


JavaScript 中的 this 主要有以下几种绑定规则,按照优先级从低到高排列:

1. 默认绑定(全局或普通函数调用)

在非严格模式下,独立调用的函数中的 this 指向全局对象(浏览器中为 window,Node.js 中为 global)。在严格模式下,thisundefined

javascript
function show() {
  console.log(this);
}
show(); // 非严格模式下输出: Window; 严格模式下输出: undefined

2. 隐式绑定(对象方法调用)

当函数作为对象的方法被调用时,this 指向该对象。

javascript
const obj = {
  name: 'Alice',
  greet() {
    console.log(this.name);
  }
};
obj.greet(); // 输出: Alice

需要注意的是,如果将方法赋值给另一个变量再调用,this 会丢失原有的绑定。

javascript
const greetFunc = obj.greet;
greetFunc(); // 非严格模式下输出: undefined 或 window.name 的值

3. 显式绑定(callapplybind

使用 callapply 方法可以显式地指定函数执行时的 thisbind 方法则返回一个新的函数,永久绑定指定的 this

javascript
function greet() {
  console.log(this.name);
}
const person = { name: 'Bob' };
greet.call(person); // 输出: Bob
greet.apply(person); // 输出: Bob

const boundGreet = greet.bind(person);
boundGreet(); // 输出: Bob

4. 构造函数绑定(new 关键字)

当使用 new 关键字调用函数时,this 指向新创建的对象。

javascript
function Person(name) {
  this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // 输出: Alice

即使函数内部使用了 callapply 改变 this,在使用 new 调用时,this 仍指向新创建的对象。

5. 箭头函数绑定(词法作用域)

箭头函数没有自己的 this,它的 this 由外层作用域决定。这使得箭头函数在某些情况下非常有用,例如在回调函数中保持 this 的指向。

javascript
const obj = {
  name: 'Charlie',
  greet: () => {
    console.log(this.name);
  }
};
obj.greet(); // 输出: undefined(或 window.name 的值)

在上述示例中,箭头函数的 this 并不指向 obj,而是继承自外层作用域的 this


优先级

当多个规则同时适用时,this 的指向遵循以下优先级(从高到低):

  1. new 绑定
  2. 显式绑定(callapplybind
  3. 隐式绑定
  4. 默认绑定(全局或普通函数调用)

需要注意的是,箭头函数的 this 是在定义时确定的,不受上述规则影响。


注意事项

  • 事件处理函数中的 this:在 DOM 事件处理函数中,this 默认指向触发事件的元素。

    javascript
    document.getElementById('btn').addEventListener('click', function() {
      console.log(this); // 输出: 触发事件的按钮元素
    });
  • 定时器函数中的 this:在 setTimeoutsetInterval 的回调函数中,this 默认指向全局对象。

    javascript
    setTimeout(function() {
      console.log(this); // 非严格模式下输出: Window; 严格模式下输出: undefined
    }, 1000);

可以使用箭头函数或 bind 方法来保持期望的 this 指向。

javascript
setTimeout(() => {
  console.log(this); // 输出: 外层作用域的 this
}, 1000);
  • 方法丢失 this 绑定:当从对象中提取方法并单独调用时,this 的绑定会丢失。

    javascript
    const obj = {
      name: 'Dave',
      greet() {
        console.log(this.name);
      }
    };
    const greetFunc = obj.greet;
    greetFunc(); // 输出: undefined(或 window.name 的值)

可以使用 bind 方法固定 this 的指向。

javascript
const boundGreet = obj.greet.bind(obj);
boundGreet(); // 输出: Dave

数据结构

Array(数组)

数组用于存储有序元素集合,可以是任何类型。

js
const fruits = ["apple", "banana", "cherry"];

// 添加元素
fruits.push("date");     // 添加到末尾
fruits.unshift("avocado"); // 添加到开头

// 删除元素
fruits.pop();     // 删除末尾
fruits.shift();   // 删除开头

// 查找元素
fruits.indexOf("banana"); // 1
fruits.includes("cherry"); // true

// 遍历
fruits.forEach(fruit => console.log(fruit));

// 转换
const upperFruits = fruits.map(fruit => fruit.toUpperCase());

// 过滤
const longFruits = fruits.filter(fruit => fruit.length > 5);

// 归并
const all = fruits.reduce((acc, fruit) => acc + " " + fruit);

// 排序
fruits.sort();
fruits.reverse();

//splice
// start:开始修改的位置索引。
// deleteCount:要删除的元素个数。
// item1, item2, ...:可选,要插入到数组中的元素。
// splice() 会改变原数组,并返回被删除的元素数组。
// array.splice(start[, deleteCount[, item1, item2, ...]])
let arr = [1, 2, 3, 4, 5];
let removed = arr.splice(2, 2); // 从索引2开始,删除2个元素
console.log(arr);    // [1, 2, 5]
console.log(removed); // [3, 4]

let arr1 = ['a', 'b', 'e', 'f'];
arr.splice(2, 0, 'c', 'c1', 'd');// 输出:['a', 'b', 'c', 'c1', 'd', 'e', 'f']

//清空数组
//手动修改 length 属性为更小的值时,JS 引擎会自动移除超出新长度范围的所有元素。
let arr = [1, 2, 3];
arr.length = 0;
console.log(arr); // []

对象数组

js

// 对象数组
const users = [
  { id: 1, name: 'Alice', age: 28 },
  { id: 2, name: 'Bob', age: 34 },
  { id: 3, name: 'Charlie', age: 22 },
  { id: 4, name: 'David', age: 34 }
];

// 1. 查找:年龄为 34 的第一个用户
const firstAge34 = users.find(user => user.age === 34);
console.log('First user with age 34:', firstAge34);

// 2. 过滤:年龄大于 25 的所有用户
const olderThan25 = users.filter(user => user.age > 25);
console.log('Users older than 25:', olderThan25);

// 3. 映射:提取所有用户名
const names = users.map(user => user.name);
console.log('All user names:', names);

// 4. 排序:按年龄升序排列(不修改原数组)
const sortedByAge = [...users].sort((a, b) => a.age - b.age);
console.log('Users sorted by age:', sortedByAge);

// 5. 累加:计算所有用户年龄总和
const totalAge = users.reduce((sum, user) => sum + user.age, 0);
console.log('Total age of all users:', totalAge);

// 6. 组合操作:找出年龄 > 30 的用户名
const namesOver30 = users
  .filter(user => user.age > 30)
  .map(user => user.name);
console.log('Names of users over 30:', namesOver30);

Set(集合)

Set 是一种不允许重复元素的数据结构。

js
const ids = new Set([1, 2, 3, 2, 1]);

ids.add(4);        // 添加
ids.delete(2);     // 删除
ids.has(3);        // 是否存在某值
ids.size;          // 长度

// 遍历
for (let id of ids) {
  console.log(id);
}

注意

Set 不能直接去重“值相同”的对象,因为对象比较的是引用

js
// 创建一个对象数组
const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
];

// Set 不能直接去重“值相同”的对象,因为对象比较的是引用
const userSet = new Set(users);
console.log('Original Set size:', userSet.size); // 3

// 尝试加入重复对象(值相同,但不是同一引用)
userSet.add({ id: 1, name: 'Alice' });
console.log('Set size after adding same value object:', userSet.size); // 4,因为是不同引用

// 如果你想基于 id 去重,可以手动处理
const moreUsers = [
  { id: 2, name: 'Bob' },       // duplicate id
  { id: 4, name: 'David' }
];

// 将原数组与新数组合并,并根据 id 去重
const merged = [...users, ...moreUsers];
const uniqueById = Array.from(
  new Map(merged.map(user => [user.id, user])).values()
);

console.log('Unique users by ID:', uniqueById);

Map(映射)

Map 是键值对集合,键可以是任意类型(包括对象)。

js
const userMap = new Map();

userMap.set("name", "Alice");
userMap.set("age", 25);

console.log(userMap.get("name"));  // "Alice"
console.log(userMap.has("age"));   // true
userMap.delete("age");
userMap.size;                      // 1

// 遍历
for (const [key, value] of userMap) {
  console.log(key, value);
}

对象Map

js
// 创建一个 Map
const userMap = new Map();

// 添加元素:以用户 ID 为 key,用户对象为 value
userMap.set(1, { id: 1, name: 'Alice' });
userMap.set(2, { id: 2, name: 'Bob' });

// 读取某个用户
const user1 = userMap.get(1);
console.log('User with ID 1:', user1); // { id: 1, name: 'Alice' }

// 判断是否存在某个 key
console.log('Has ID 2?', userMap.has(2)); // true

// 更新用户
userMap.set(2, { id: 2, name: 'Robert' });

// 删除用户
userMap.delete(1);

// Map 的大小
console.log('Map size:', userMap.size); // 1

// 遍历 Map
for (const [id, user] of userMap) {
  console.log(`User ID: ${id}, Name: ${user.name}`);
}

mapobject

特性ObjectMap
键类型仅字符串或符号任意类型
键的有序性
迭代性不可直接迭代(for...in 替代)可直接 for..of
性能较慢(键多时)较快
特性for...infor...of
可用于对象吗?是设计来遍历对象的普通对象不能直接用
遍历的内容对象的键名(包括继承的)可迭代对象的元素值
是否会遍历原型链?不会
常用于遍历普通对象遍历数组、Map、Set、字符串等

WeakMap 和 WeakSet

它们是 Map 和 Set 的变体,但只能使用对象作为键(WeakMap)或值(WeakSet),并且是弱引用,不影响垃圾回收。

适合缓存或私有属性存储,但不能遍历。

js
let obj = { id: 1 };
const weakMap = new WeakMap();
weakMap.set(obj, "some value");

TypedArray(类型数组)

用于处理二进制数据,如 Int8Array, Uint8Array, Float32Array 等,适用于性能要求较高的应用。

js
const buffer = new ArrayBuffer(8); // 8 字节的内存
const int32 = new Int32Array(buffer);
int32[0] = 42;
console.log(int32[0]); // 42

数据结构是否有序是否唯一键类型常见用途
Array数字索引有序元素集合
Set值本身唯一集合
Map任意类型更强大的键值存储
WeakMap不可遍历对象私有数据、缓存
WeakSet不可遍历对象(值)追踪对象存在性
TypedArray数字索引高性能数值处理

事件

JavaScript 的事件(Event)是浏览器与用户交互的重要机制,它允许我们对用户的各种行为(点击、输入、键盘、加载等)做出响应。

以下是 JS 事件系统的详细介绍,包括事件的基本概念、类型、事件模型、冒泡与捕获、事件对象、事件委托、移除监听器等方面的知识,配合示例说明。


什么是事件?

事件是用户或浏览器执行的某种行为,如:

  • 用户点击按钮(click
  • 鼠标移入元素(mouseover
  • 输入框内容改变(input
  • 页面加载完成(load
  • 键盘按下(keydown

事件监听方式

  1. 通过 HTML 属性绑定(不推荐)
html
<button onclick="alert('Clicked')">Click me</button>
  1. 使用 DOM 元素的事件属性
js
const btn = document.querySelector("button");
btn.onclick = function () {
  alert("Clicked");
};
  1. addEventListener(推荐)
js
btn.addEventListener("click", () => {
  alert("Clicked via addEventListener");
});

可以添加多个事件处理器,并支持事件捕获阶段。


事件对象 event

每个事件处理器会自动接收一个 event 参数,包含事件的所有信息。

js
btn.addEventListener("click", function (event) {
  console.log(event.type);        // "click"
  console.log(event.target);      // 触发事件的元素
  console.log(event.currentTarget); // 绑定事件的元素
});

常见属性包括:

属性说明
type事件类型,例如 "click"
target实际触发事件的元素
currentTarget当前绑定事件的元素
preventDefault()阻止默认行为(如表单提交)
stopPropagation()阻止事件冒泡

事件传播机制:捕获、目标、冒泡

事件在 DOM 树上会经历三个阶段:

  1. 捕获阶段(Capture Phase)
  2. 目标阶段(Target Phase)
  3. 冒泡阶段(Bubble Phase)
js
parent.addEventListener("click", () => {
  console.log("Parent capture");
}, true); // 捕获阶段

parent.addEventListener("click", () => {
  console.log("Parent bubble");
}, false); // 冒泡阶段

默认是冒泡。设置 useCapture=true 监听捕获阶段。


事件委托(Event Delegation)

事件委托是将子元素的事件绑定到父元素上,利用事件冒泡提高性能。

html
<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
</ul>
js
document.getElementById("list").addEventListener("click", function (e) {
  if (e.target.tagName === "LI") {
    console.log("Clicked:", e.target.textContent);
  }
});

优点:

  • 子元素可以动态添加
  • 节省内存,减少事件绑定

事件解绑

使用 removeEventListener

js
function handleClick() {
  alert("Click");
}

btn.addEventListener("click", handleClick);
btn.removeEventListener("click", handleClick); // 必须是同一个函数引用

常见事件类型

类型示例事件用途
鼠标事件click, dblclick, mousemove, mouseover, mouseout交互响应、UI动态
键盘事件keydown, keyup, keypress表单输入、快捷键
表单事件submit, change, input, focus, blur表单验证与交互
文档事件DOMContentLoaded, load, resize, scroll页面加载控制、UI响应
拖拽事件drag, dragstart, drop, dragover文件拖放、组件拖拽
触摸事件touchstart, touchmove, touchend移动端操作

自定义事件(CustomEvent)

你可以创建并触发自定义事件:

js
const myEvent = new CustomEvent("my-event", {
  detail: { message: "Hello" }
});

element.addEventListener("my-event", e => {
  console.log(e.detail.message); // "Hello"
});

element.dispatchEvent(myEvent);

防抖 & 节流(与事件结合使用)

  • 防抖(debounce):在事件触发n 秒后只执行一次,适用于输入框等场景
js
function debounce(fn, delay) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

input.addEventListener("input", debounce(() => {
  console.log("Search...");
}, 300));
  • 节流(throttle):每隔 n 秒执行一次,适用于 scroll、resize 等高频事件

常见实现方式:


方法一:时间戳版节流(立即执行,固定间隔)

js
function throttle(fn, delay) {
  let lastTime = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      fn.apply(this, args);
      lastTime = now;
    }
  };
}

📦 用法示例:

js
window.addEventListener('resize', throttle(() => {
  console.log('窗口变化了', new Date());
}, 500));

方法二:定时器版节流(延迟执行)

js
function throttle(fn, delay) {
  let timer = null;
  return function (...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        timer = null;
      }, delay);
    }
  };
}

区别:

特性时间戳版定时器版
是否立即执行否(延迟)
是否停止后最后一次执行

方法三:时间戳 + 定时器结合版(兼顾首次立即执行和停止后补执行)

js
function throttle(fn, delay) {
  let lastTime = 0;
  let timer = null;
  return function (...args) {
    const now = Date.now();
    const remaining = delay - (now - lastTime);
    if (remaining <= 0) {
      if (timer) {
        clearTimeout(timer);
        timer = null;
      }
      fn.apply(this, args);
      lastTime = now;
    } else if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args);
        lastTime = Date.now();
        timer = null;
      }, remaining);
    }
  };
}

异步编程

JavaScript 中的 异步编程(Asynchronous Programming) 是一种允许程序在不阻塞主线程的情况下执行耗时操作(如网络请求、定时器、文件读写等)的编程方式。

由于 JavaScript 是单线程运行的,异步机制对保持程序流畅性至关重要。

如果所有任务都是同步的,那么一个耗时操作(如访问服务器)会阻塞整个页面,导致“卡死”。

异步编程的目的是:

  • 避免主线程阻塞
  • 提高程序响应性
  • 实现任务并发或延迟执行

事件循环(Event Loop)机制

JavaScript 的异步编程执行过程,背后主要依赖于 事件循环(Event Loop)机制,它决定了异步任务何时执行。

整体执行流程

  1. 执行全局同步代码

    • 所有同步任务按顺序进栈、出栈。
  2. 遇到异步任务

    • 将异步任务交由 Web APIs 处理。
  3. Web APIs 处理完成后

    • 将回调函数加入任务队列中(宏任务或微任务)。
  4. 同步代码执行完毕,调用栈为空

    • 事件循环开始:

      • 先执行 微任务队列 中所有任务(如 Promise 的 .then()
      • 然后取出一个宏任务(如 setTimeout),放入调用栈中执行
  5. 重复执行 4 步骤 —— 这就是事件循环(Event Loop)


示例:同步 + 异步代码混合执行

javascript
console.log('1');

setTimeout(() => {
  console.log('2');
}, 0);

Promise.resolve().then(() => {
  console.log('3');
});

console.log('4');
步骤行为输出状态说明
1执行同步代码 console.log(1)1立即执行
2遇到 setTimeout-注册回调函数,加入宏任务队列
3执行 Promise.resolve().then-回调注册进 微任务队列
4执行同步代码 console.log(4)4立即执行
5主线程空闲,执行 微任务队列3执行 .then() 内容
6微任务队列清空后执行宏任务2执行 setTimeout 的回调

最终输出顺序是:

1
4
3
2

宏任务 vs 微任务

  • 宏任务

一类在事件循环中每次循环执行一次的任务。每执行一个宏任务,接着就会清空所有微任务。 setTimeout, setInterval, fetch, DOM事件 等执行会将任务放入宏任务队列

  • 微任务

在当前宏任务执行完毕后、下一个宏任务开始前立即执行的任务。执行优先级高于宏任务。 Promise.then/catch/finally, queueMicrotask, MutationObserver等执行会将任务放入微任务队列


异步编程的几种常见方式

1. 回调函数(Callback)

最早期的异步处理方式。通过将函数作为参数传入另一个函数,在异步操作完成后调用。

javascript
function loadData(callback) {
  setTimeout(() => {
    console.log("数据加载完成");
    callback("数据内容");
  }, 1000);
}

loadData((data) => {
  console.log("回调接收数据:", data);
});

🔴 缺点:回调地狱(Callback Hell)

javascript
step1(() => {
  step2(() => {
    step3(() => {
      // ...
    });
  });
});

2. Promise

Promise 是对异步操作结果的抽象表示,它有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)
javascript
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = true;
    if (success) resolve("成功");
    else reject("失败");
  }, 1000);
});

promise
  .then((result) => console.log("结果:", result))
  .catch((error) => console.error("错误:", error));

🌟 优势:

  • 链式调用:避免了回调地狱
  • 错误统一处理(.catch

3. async/await(语法糖)

async/await 是对 Promise 的封装,提供了 同步写法,异步执行 的语法体验,极大提升了代码可读性。

javascript
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function fetchData() {
  console.log("开始加载");
  await delay(1000);  // 等待 1 秒
  console.log("数据加载完成");
}

fetchData();

⚠️ 注意:

  • await 只能在 async 函数内部使用
  • 如果 await 后面不是 Promise,会自动转成一个已完成的 Promise

4. 并发与串行控制

并发(并行执行多个任务):
javascript
async function parallel() {
  const [res1, res2] = await Promise.all([
    fetch("/api/data1"),
    fetch("/api/data2")
  ]);
}
串行(一个一个来):
javascript
async function serial() {
  const res1 = await fetch("/api/data1");
  const res2 = await fetch("/api/data2");
}

5. 常见异步操作类型

  • setTimeout, setInterval
  • fetch, XMLHttpRequest
  • Event 事件监听
  • 浏览器 API(如 Geolocation、File API)
  • Node.js 中的文件操作、网络通信

DOM

JavaScript 的 DOM(Document Object Model,文档对象模型) 是前端开发中最核心的概念之一,它让我们可以用 JavaScript 动态地访问和操作网页的内容、结构和样式


什么是 DOM?

DOM 是一种 树状结构,它将 HTML 文档表示为由节点组成的层级结构,每个 HTML 标签、文本、属性等都是节点。

比如,下面的 HTML:

html
<!DOCTYPE html>
<html>
  <head>
    <title>页面标题</title>
  </head>
  <body>
    <p>Hello World</p>
  </body>
</html>

被浏览器解析成如下的 DOM 结构:

Document
 └── html
     ├── head
     │    └── title
     └── body
          └── p

DOM 中的节点类型

节点类型示例说明
元素节点<div>, <p>HTML 元素
文本节点"Hello"元素内的文字内容
属性节点class="box"元素的属性(较少直接使用)
注释节点<!-- 注释 -->HTML 注释
文档节点document整个文档对象

常用 DOM 操作

  1. 获取元素
javascript
document.getElementById('myId');              // 单个元素
document.getElementsByClassName('myClass');   // HTMLCollection
document.getElementsByTagName('div');         // HTMLCollection
document.querySelector('.myClass');           // 单个元素
document.querySelectorAll('div.myClass');     // NodeList
  1. 修改内容或属性
javascript
const el = document.getElementById('title');
el.textContent = '新标题';
el.innerHTML = '<span>带标签</span>';
el.setAttribute('class', 'new-class');
  1. 修改样式
javascript
el.style.color = 'red';
el.style.fontSize = '20px';
  1. 操作类名(推荐)
javascript
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('visible');
el.classList.contains('open'); // 判断是否包含某个类
  1. 创建与插入元素
javascript
const newEl = document.createElement('div');
newEl.textContent = '我是新元素';
document.body.appendChild(newEl); // 插入到 body 的末尾
  1. 删除元素
javascript
const el = document.getElementById('toDelete');
el.remove();

DOM 事件(交互)

javascript
const btn = document.querySelector('button');

btn.addEventListener('click', function (event) {
  alert('按钮被点击了');
});
常见事件类型描述
click鼠标点击
input输入框内容变化
submit表单提交
keydown键盘按键按下
mouseover鼠标悬停

DOM 的特性

  • DOM 操作是浏览器提供的接口,不是 JavaScript 本身的功能(JavaScript 是操作工具,DOM 是目标结构)
  • DOM 操作相对“慢”,频繁改动会影响性能 → 建议使用 DocumentFragment虚拟 DOM(如 Vue/React) 优化

BOM

JavaScript 中的 BOM(Browser Object Model,浏览器对象模型) 是浏览器提供的一组 API,用于与浏览器窗口进行交互,而不是与网页的内容(DOM)交互。


什么是 BOM?

BOM 提供了一些全局对象和方法,主要用于控制浏览器窗口、导航、弹窗、获取浏览器信息等。 它不是由 ECMAScript 标准定义的,而是由各大浏览器实现的“扩展”。


BOM 的核心对象结构图

text
window
├── location
├── navigator
├── history
├── screen
├── document (这是 DOM 对象)
├── alert(), confirm(), prompt()
└── setTimeout(), setInterval(), clearTimeout() 等

主要 BOM 组件

1. window(顶层对象)

javascript
window.alert('Hello');  // 弹窗
alert('Hello');         // window 可省略

常用功能:

方法描述
alert()弹出提示框(只有确认)
confirm()弹出确认框(返回 true/false)
prompt()输入框(返回输入的字符串)
setTimeout()延迟执行一次
setInterval()每隔一段时间重复执行
clearTimeout()清除延时
clearInterval()清除定时器

2. location(地址栏信息)

javascript
console.log(location.href);  // 当前完整 URL
location.href = 'https://www.example.com'; // 跳转

常用属性:

属性描述
href当前页面完整 URL
hostname主机名(如 www.example.com
pathname路径(如 /index.html
search查询字符串(如 ?q=abc
hash锚点部分(如 #top

3. navigator(浏览器信息)

javascript
console.log(navigator.userAgent);   // 浏览器 UA 字符串
console.log(navigator.language);    // 浏览器语言

常用于判断浏览器类型、平台、语言等(但不建议依赖 UA 判断特性)。


4. history(浏览历史)

javascript
history.back();    // 返回上一页
history.forward(); // 前进一页
history.go(-1);    // 回退 1 步

注意:出于安全考虑,不能访问历史记录的具体 URL 内容。


5. screen(屏幕信息)

javascript
console.log(screen.width);      // 屏幕宽度
console.log(screen.availHeight); // 可用高度

BOM 和 DOM 的区别

项目DOM(Document)BOM(Browser)
对象documentwindow, location, navigator
作用操作网页内容操作浏览器行为
标准ECMAScript + W3C非官方标准(由浏览器实现)

DOM 是网页的“内容”,BOM 是浏览器的“外壳”。


本地存储

在 Web 前端中,浏览器为我们提供了多种本地存储机制,用于在客户端保存数据。主要有以下几种方式:

类型大小限制生命周期与服务器交互示例用途
Cookie~4KB可设定过期时间每次请求自动发送登录状态、跨页面识别
LocalStorage~5MB永久(除非主动清除)保存用户设置、主题色等
SessionStorage~5MB页面会话(关闭失效)多标签隔离、一次性表单数据

  • 每次 HTTP 请求都会自动携带 Cookie(影响性能和安全)
  • 可以通过 JS 访问(document.cookie
  • 支持设置 HttpOnlySecure 等属性提高安全性
javascript
document.cookie = "username=Tom; expires=Fri, 01 Jan 2027 12:00:00 UTC; path=/";

LocalStorage

  • 每个域名下存储空间独立
  • 永久保存(除非手动删除或清空缓存)
javascript
localStorage.setItem('theme', 'dark');
let value = localStorage.getItem('theme'); // "dark"
localStorage.removeItem('theme');
localStorage.clear(); // 清空所有

SessionStorage

  • 和 LocalStorage 类似,但仅在当前标签页/窗口有效
  • 页面关闭或刷新后数据丢失
javascript
sessionStorage.setItem('step', '1');
let step = sessionStorage.getItem('step');

使用建议

需求推荐方式
跨页面登录状态、CSRF 等Cookie(带 HttpOnly)
用户设置、持久数据LocalStorage
页面临时数据,如表单暂存SessionStorage

网络请求

在 JavaScript 中,网络请求是前端与后端通信的核心方式。开发者可以使用原生 API 或第三方库来实现 HTTP 请求。以下是常用的网络请求方式及其实现方式的详细介绍:


原生网络请求方式

1. XMLHttpRequest(XHR)

XMLHttpRequest 是早期实现 AJAX 的核心 API,允许在不刷新页面的情况下与服务器进行通信。尽管现在有了更现代的替代方案,但在某些场景下仍被使用。

基本用法:

javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log(JSON.parse(xhr.responseText));
  }
};
xhr.send();

特点:

  • 支持设置请求头、监听上传/下载进度、设置超时等功能。
  • 使用回调函数处理异步操作,可能导致“回调地狱”。
  • 在现代开发中逐渐被 fetch 替代,但在需要精细控制请求过程时仍有用武之地。

2. Fetch API

fetch 是现代浏览器提供的原生 API,用于替代 XMLHttpRequest,基于 Promise,更加简洁和强大。

基本用法:

javascript
fetch('https://api.example.com/data')
  .then(response => {
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error('Fetch error:', error));

特点:

  • 基于 Promise,支持 async/await,代码更清晰。
  • 默认不发送 cookies,需手动设置 credentials
  • 不支持直接监听上传/下载进度,需要使用其他方式实现。
  • 在现代浏览器中广泛支持,已成为主流的网络请求方式。

常用第三方网络请求库

1. Axios

Axios 是一个基于 Promise 的 HTTP 客户端,可用于浏览器和 Node.js,封装了 XMLHttpRequest,提供了更丰富的功能。

基本用法:

javascript
axios.get('https://api.example.com/data')
  .then(response => console.log(response.data))
  .catch(error => console.error('Axios error:', error));

特点:

  • 自动转换 JSON 数据。
  • 支持请求和响应拦截器。
  • 支持请求取消、超时设置、防止跨站请求伪造(XSRF)攻击等。
  • 在浏览器和 Node.js 中均可使用。

2. Superagent

Superagent 是一个轻量级的 AJAX API,支持 Promise 和回调,适用于浏览器和 Node.js。

基本用法:

javascript
superagent.get('https://api.example.com/data')
  .then(response => console.log(response.body))
  .catch(error => console.error('Superagent error:', error));

特点:

  • API 简洁,易于使用。
  • 支持链式调用,增强代码可读性。
  • 适用于需要快速实现 HTTP 请求的场景。

3. jQuery.ajax

在 jQuery 时代,$.ajax 是实现 AJAX 请求的主要方式,封装了 XMLHttpRequest,提供了简洁的 API。

基本用法:

javascript
$.ajax({
  url: 'https://api.example.com/data',
  method: 'GET',
  success: function(data) {
    console.log(data);
  },
  error: function(error) {
    console.error('jQuery AJAX error:', error);
  }
});

特点:

  • 封装良好,使用方便。
  • 依赖于 jQuery,适用于使用 jQuery 的项目。
  • 在现代开发中逐渐被原生 fetch 和其他库替代。([CSDN博客][1])

总结

特性XMLHttpRequestFetch APIAxiosSuperagentjQuery.ajax
基于 Promise
请求/响应拦截器
自动转换 JSON
上传/下载进度监听
支持取消请求
浏览器支持广泛现代浏览器广泛广泛广泛
Node.js 支持需 polyfill

模块化

JavaScript 的模块化是指将复杂的系统分解为多个独立的模块,以提高代码的可维护性、复用性和组织性。随着前端应用的复杂度增加,模块化成为现代 JavaScript 开发的核心。

在早期,JavaScript 主要通过全局变量和函数来组织代码,容易导致命名冲突、依赖管理混乱等问题。模块化通过封装和接口暴露的方式,解决了这些问题。


主要的模块化规范与实现方式

1. CommonJS(主要用于 Node.js)

  • 特点:同步加载模块,适用于服务器端。
  • 导出模块:使用 module.exportsexports
  • 导入模块:使用 require()

示例

javascript
// math.js
exports.add = function(a, b) {
  return a + b;
};

// main.js
const math = require('./math');
console.log(math.add(2, 3));

CommonJS 规范主要在 Node.js 中使用,适合服务器端的模块化需求。

2. AMD(Asynchronous Module Definition,主要用于浏览器端)

  • 特点:异步加载模块,适用于浏览器端。
  • 定义模块:使用 define()
  • 导入模块:使用 require()

示例

javascript
// math.js
define([], function() {
  return {
    add: function(a, b) {
      return a + b;
    }
  };
});

// main.js
require(['math'], function(math) {
  console.log(math.add(2, 3));
});

AMD 规范主要由 RequireJS 实现,适合浏览器环境下的模块化需求。

3. CMD(Common Module Definition)

  • 特点:依赖就近,延迟执行,适用于浏览器端。
  • 定义模块:使用 define()
  • 导入模块:使用 require()

示例

javascript
// math.js
define(function(require, exports, module) {
  exports.add = function(a, b) {
    return a + b;
  };
});

// main.js
define(function(require) {
  var math = require('./math');
  console.log(math.add(2, 3));
});

CMD 规范主要由 SeaJS 实现,强调依赖的延迟执行。

4. ES6 Modules(ESM,现代浏览器和 Node.js 支持)

  • 特点:静态加载,支持编译时优化。
  • 导出模块:使用 export
  • 导入模块:使用 import

示例

javascript
// math.js
export function add(a, b) {
  return a + b;
}

// main.js
import { add } from './math.js';
console.log(add(2, 3));

ES6 Modules 是 JavaScript 官方标准,现代浏览器和 Node.js 均已支持。

5. UMD(Universal Module Definition)

  • 特点:兼容 CommonJS、AMD 和全局变量,适用于库的发布。
  • 实现方式:通过判断环境来选择模块化方案。

示例

javascript
(function(root, factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD
    define([], factory);
  } else if (typeof module === 'object' && module.exports) {
    // CommonJS
    module.exports = factory();
  } else {
    // 全局变量
    root.myModule = factory();
  }
}(this, function() {
  return {
    add: function(a, b) {
      return a + b;
    }
  };
}));

UMD 适用于需要兼容多种模块化环境的库。


模块化规范对比

特性CommonJSAMDCMDES6 ModulesUMD
加载方式同步异步异步静态加载兼容多种
使用环境Node.js浏览器浏览器浏览器/Node通用
导入语法requirerequirerequireimport自动判断
导出语法exportsreturnexportsexport自动判断
是否支持动态加载

模块化工具与打包器

在实际开发中,常使用以下工具来实现模块化:

  • Webpack:支持多种模块化规范,提供丰富的插件和加载器,适合大型项目。
  • Rollup:专注于打包 ES6 模块,生成体积更小的代码,适合库的开发。
  • Parcel:零配置的打包工具,适合快速开发和小型项目。

这些工具可以将模块化的代码打包成浏览器可识别的格式,解决兼容性和性能问题。


Document Base on VitePress.