JavaScript
一、基础语法与数据类型
1. JavaScript 数据类型有哪些?
定义
JavaScript 数据类型分为两大类:基本数据类型(简单类型/原始类型)和 引用数据类型(复杂类型/对象类型) 。
原理
基本数据类型 :存储在栈(Stack)中,按值访问,值不可变
引用数据类型 :栈中存储引用地址,实际数据存储在堆(Heap)中,按引用访问
ES5 基本数据类型(5种)
类型
说明
示例
String
字符串
"hello", 'world'
Number
数字(整数和浮点数)
42, 3.14
Boolean
布尔值
true, false
null
空值,表示"空对象指针"
null
undefined
未定义,表示变量已声明但未赋值
undefined
ES6 新增基本数据类型
类型
说明
示例
Symbol
唯一标识符
Symbol('id')
BigInt
任意精度整数
9007199254740991n
引用数据类型
类型
说明
Object
普通对象 {}
Array
数组 []
Function
函数
Date
日期对象
RegExp
正则表达式
Map / Set
ES6 新增集合类型
WeakMap / WeakSet
ES6 弱引用集合
示例代码
javascript
复制代码
// 基本数据类型 - 存储在栈中
let name = "JavaScript"; // String
let age = 30; // Number
let isActive = true; // Boolean
let empty = null; // null
let notDefined; // undefined
// 基本类型按值传递,修改副本不影响原值
let a = 10;
let b = a;
b = 20;
console.log(a); // 10,不受影响
// 引用数据类型 - 栈中存地址,堆中存数据
let obj = { name: "JS", version: "ES6" };
let arr = [1, 2, 3];
let fn = function() { return "Hello"; };
// 引用类型传递的是地址,修改会影响原对象
let obj1 = { a: 1 };
let obj2 = obj1;
obj2.a = 2;
console.log(obj1.a); // 2,被修改了
存储差异对比
特性
基本类型
引用类型
存储位置
栈(Stack)
栈(地址)+ 堆(数据)
访问方式
按值访问
按引用访问
复制方式
复制值本身
复制引用地址
比较方式
值比较
引用地址比较
内存管理
自动回收
需要垃圾回收
可变性
不可变
可变
常见误区
typeof null 返回 "object" 是历史遗留 bug
基本类型不是对象,但 String、Number、Boolean 有包装对象
NaN 是 Number 类型,但 typeof NaN 返回 "number"
2. null 和 undefined 的区别
定义
null :表示"空值"或"不存在的对象",需要显式赋值
undefined :表示"未定义",变量已声明但未赋值时的默认值
对比表格
特性
null
undefined
类型
object(历史 bug)
undefined
含义
空对象指针
未定义
来源
需要手动赋值
系统自动赋值
转换为数字
0
NaN
转换为布尔
false
false
转换为字符串
"null"
"undefined"
原理
null 是 JavaScript 关键字,表示有意的"空值"
undefined 是全局对象的属性,表示"缺少值"
代码示例
javascript
复制代码
// undefined 的场景
let a; // 变量声明但未赋值
let obj = {}; obj.x; // 访问不存在的属性
function fn() {} fn(); // 函数无返回值
let arr = []; arr[5]; // 访问不存在的数组索引
// null 的场景
let element = null; // DOM 元素不存在
let user = null; // 主动清空引用
document.getElementById('nonexist'); // 返回 null
// 类型检测
console.log(typeof null); // "object" (历史 bug)
console.log(typeof undefined); // "undefined"
// 相等性比较
console.log(null == undefined); // true (隐式转换)
console.log(null === undefined); // false (类型不同)
使用场景
javascript
复制代码
// 使用 null 的场景
// 1. 函数参数默认值
function createUser(name = null) {
return { name, age: 0 };
}
// 2. 释放对象引用(帮助垃圾回收)
let largeObject = { /* 大量数据 */ };
largeObject = null; // 解除引用
// 使用 undefined 的场景
// 1. 检查变量是否已赋值
if (value === undefined) { /* 未赋值处理 */ }
// 2. 函数参数可选
function greet(name) {
if (name === undefined) name = "Guest";
return `Hello, ${name}`;
}
常见误区
不要使用 == 判断 null 和 undefined,用 ===
null 是关键字,undefined 是全局变量(ES5 前可被重新赋值)
JSON 中不支持 undefined,序列化时会被忽略
3. == 和 === 的区别
定义
==(相等运算符) :先进行类型转换,再比较值
===(严格相等运算符) :不转换类型,直接比较值和类型
对比表格
特性
==(相等)
===(严格相等)
类型转换
会进行隐式转换
不转换
比较规则
值相等即可
类型和值都必须相等
性能
较慢(需转换)
较快
推荐度
不推荐
强烈推荐
== 隐式转换规则
javascript
复制代码
// 数字与字符串:字符串转数字
console.log(1 == "1"); // true
// 布尔值与其他:布尔转数字(true->1, false->0)
console.log(true == 1); // true
console.log(false == 0); // true
console.log(true == "1"); // true
// null 和 undefined
console.log(null == undefined); // true
console.log(null == 0); // false
console.log(undefined == 0); // false
// 对象与原始值:对象转原始值
console.log([1] == 1); // true (数组toString后是"1")
console.log([] == 0); // true
console.log({} == "[object Object]"); // true
特殊情况(永远为 false)
javascript
复制代码
NaN == NaN // false(NaN 不等于任何值,包括自己)
null == 0 // false
undefined == 0 // false
null == false // false
undefined == false // false
代码示例
javascript
复制代码
// 推荐使用 ===
console.log(0 === false); // false
console.log("" === false); // false
console.log(null === undefined); // false
console.log(NaN === NaN); // false
// 判断 null 或 undefined 的简便写法
// 只有 null 和 undefined 满足 == null
if (value == null) {
// 相当于 value === null || value === undefined
}
选择策略
始终使用 === ,除非你明确需要类型转换
判断 null 或 undefined 时可用 == null(最简洁)
使用 Object.is() 处理特殊场景(如 NaN、+0、-0)
javascript
复制代码
// Object.is 的特殊之处
Object.is(NaN, NaN); // true(=== 为 false)
Object.is(+0, -0); // false(=== 为 true)
Object.is(0, -0); // false
4. NaN 特性及检测方法
定义
NaN(Not-a-Number) 是一个特殊的 Number 类型值,表示非数字。通常由无效的数学运算产生。
特性
typeof NaN 返回 "number"
NaN 不等于任何值,包括自己:NaN === NaN 为 false
任何涉及 NaN 的运算都返回 NaN
NaN 是唯一一个不自等的值
产生场景
javascript
复制代码
// 无效的数学运算
Math.sqrt(-1); // NaN
Math.log(-1); // NaN
0 / 0; // NaN
Infinity - Infinity; // NaN
Infinity / Infinity; // NaN
// 类型转换失败
Number("abc"); // NaN
parseInt("abc"); // NaN
parseFloat("xyz"); // NaN
检测方法
javascript
复制代码
// 方法1:全局 isNaN(有缺陷)
isNaN("abc"); // true(先转换为数字,失败则返回true)
isNaN("123"); // false
isNaN(true); // false(true转换为1)
// 方法2:Number.isNaN(推荐)
Number.isNaN(NaN); // true
Number.isNaN("abc"); // false(不转换类型)
Number.isNaN(123); // false
// 方法3:自不等特性
function isNaN2(val) {
return val !== val;
}
isNaN2(NaN); // true
// 方法4:ES6 之前兼容写法
function isNaN3(val) {
return typeof val === "number" && isNaN(val);
}
isNaN 与 Number.isNaN 对比
特性
isNaN()
Number.isNaN()
类型转换
会转换
不转换
引入版本
ES1
ES6
准确性
有缺陷
准确
推荐度
不推荐
推荐
javascript
复制代码
// 对比示例
isNaN("NaN"); // true(字符串"NaN"转换为数字NaN)
Number.isNaN("NaN"); // false(类型不同)
isNaN(undefined); // true(undefined转换为NaN)
Number.isNaN(undefined); // false
5. 数据类型检测方法
四种检测方法对比
1. typeof 运算符
javascript
复制代码
// 返回值
typeof "" // "string"
typeof 42 // "number"
typeof true // "boolean"
typeof undefined // "undefined"
typeof Symbol() // "symbol"
typeof 9007199254740991n // "bigint"
typeof function(){} // "function"
typeof {} // "object"
typeof [] // "object"
typeof null // "object"(历史 bug)
适用场景 :检测基本类型和函数 局限性 :无法区分对象、数组、null(都返回 "object")
2. instanceof 运算符
javascript
复制代码
[] instanceof Array; // true
{} instanceof Object; // true
function(){} instanceof Function; // true
/new Date() instanceof Date; // true
适用场景 :检测引用类型 局限性 :
不能检测基本类型
跨 iframe 时失效(不同全局对象)
原型链被修改时结果可能错误
3. Object.prototype.toString.call()
javascript
复制代码
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(42); // "[object Number]"
Object.prototype.toString.call(true); // "[object Boolean]"
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(function(){}); // "[object Function]"
Object.prototype.toString.call(/regex/); // "[object RegExp]"
Object.prototype.toString.call(new Date()); // "[object Date]"
Object.prototype.toString.call(new Map()); // "[object Map]"
Object.prototype.toString.call(new Set()); // "[object Set]"
Object.prototype.toString.call(Symbol()); // "[object Symbol]"
Object.prototype.toString.call(BigInt(1)); // "[object BigInt]"
适用场景 :最准确的类型检测方法 原理 :每个对象内部都有 [[Class]] 属性,toString() 会读取该属性
4. 通用类型判断函数
javascript
复制代码
function typeOf(val) {
const typeMap = {
'[object String]': 'string',
'[object Number]': 'number',
'[object Boolean]': 'boolean',
'[object Null]': 'null',
'[object Undefined]': 'undefined',
'[object Object]': 'object',
'[object Array]': 'array',
'[object Function]': 'function',
'[object RegExp]': 'regexp',
'[object Date]': 'date',
'[object Map]': 'map',
'[object Set]': 'set',
'[object Symbol]': 'symbol',
'[object BigInt]': 'bigint',
'[object Promise]': 'promise',
'[object Error]': 'error',
'[object Arguments]': 'arguments',
};
return typeMap[Object.prototype.toString.call(val)] || 'unknown';
}
检测方式对比表
方法
基本类型
引用类型
null
跨iframe
推荐度
typeof
✅
❌(只识别function)
❌(返回object)
✅
中
instanceof
❌
✅
❌
❌
低
Object.prototype.toString
✅
✅
✅
✅
高
constructor
✅
✅
❌
❌
中
6. typeof 和 instanceof 的区别
对比表格
特性
typeof
instanceof
返回值
字符串(类型名)
布尔值
检测基本类型
✅
❌
检测引用类型
❌(只识别function)
✅
null 检测
❌(返回"object")
❌(null 不是任何对象实例)
数组检测
❌(返回"object")
✅
跨 iframe
✅
❌
原理
读取内部类型标签
检查原型链
使用场景
javascript
复制代码
// typeof 适合检测基本类型
if (typeof value === "string") { /* ... */ }
if (typeof value === "number") { /* ... */ }
if (typeof value === "function") { /* ... */ }
// instanceof 适合检测引用类型
if (value instanceof Array) { /* ... */ }
if (value instanceof Date) { /* ... */ }
if (value instanceof RegExp) { /* ... */ }
// 最佳实践:组合使用
function isObject(value) {
return value !== null && typeof value === "object";
}
function isArray(value) {
return Array.isArray(value); // 或使用 instanceof
}
7. 数据类型转换
显式类型转换
javascript
复制代码
// 转字符串
String(123); // "123"
(123).toString(); // "123"
(123).toString(2); // "1111011"(二进制)
(123).toString(16); // "7b"(十六进制)
// 转数字
Number("123"); // 123
Number("12.34"); // 12.34
Number(""); // 0
Number("abc"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
// 整数解析
parseInt("123px"); // 123
parseInt("0x10"); // 16(十六进制)
parseInt("12.34"); // 12
parseInt("abc"); // NaN
parseInt("10", 2); // 2(二进制)
// 浮点数解析
parseFloat("12.34px"); // 12.34
parseFloat("12.00"); // 12
// 转布尔值
Boolean(1); // true
Boolean(0); // false
Boolean(""); // false
Boolean("abc"); // true
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
Boolean([]); // true
Boolean({}); // true
隐式类型转换规则
javascript
复制代码
// 1. 字符串 + 任意类型 -> 字符串
"1" + 2; // "12"
"1" + true; // "1true"
"1" + null; // "1null"
"1" + undefined; // "1undefined"
// 2. 数字运算 -> 转数字
"5" - 2; // 3
"5" * 2; // 10
"5" / 2; // 2.5
"5" - "2"; // 3
+"123"; // 123(一元+运算符)
// 3. 比较运算
"5" > 3; // true(转数字)
"5" > "3"; // true(字符串比较,逐字符)
"10" > "3"; // false("1" < "3")
5 > null; // true(null转0)
5 > undefined; // false(undefined转NaN)
// 4. 布尔上下文
if ([]) { /* 执行,[]转布尔为true */ }
if ({}) { /* 执行,{}转布尔为true */ }
if ("") { /* 不执行,空字符串为false */ }
// 5. == 比较时的转换
0 == false; // true(false转0)
"" == 0; // true(空字符串转0)
"0" == false; // true
null == undefined; // true
// 6. 对象转原始值
// 先调用 valueOf(),再调用 toString()
[1, 2] == "1,2"; // true
{} + []; // 0({}是代码块,+[]转数字0)
常见转换陷阱
javascript
复制代码
// 1. 空数组转数字
[] == 0; // true
[] == false; // true
Number([]); // 0
// 2. 多元素数组
[1] == 1; // true
[1, 2] == "1,2"; // true
[1, 2] == 12; // false
// 3. 对象比较
{} == "[object Object]"; // true
// 4. null 和 undefined
null == 0; // false
undefined == 0; // false
null >= 0; // true(比较时null转0)
undefined >= 0; // false
8. 基本类型与引用类型的区别
定义
基本类型 :简单的数据段,存储在栈中
引用类型 :由多个值构成的对象,引用在栈,数据在堆
对比表格
特性
基本类型
引用类型
存储位置
栈
栈(引用)+ 堆(数据)
大小
固定
不固定
复制
值复制(独立副本)
引用复制(共享数据)
比较
值比较
引用地址比较
参数传递
值传递
引用传递
属性/方法
无(有包装对象)
有
垃圾回收
作用域结束
引用计数/标记清除
示例代码
javascript
复制代码
// 基本类型:值复制
let a = 10;
let b = a;
b = 20;
console.log(a); // 10(独立副本)
// 引用类型:引用复制
let obj1 = { name: "Alice" };
let obj2 = obj1;
obj2.name = "Bob";
console.log(obj1.name); // "Bob"(共享数据)
// 函数参数传递
function modifyValue(val) {
val = 100;
}
function modifyObj(obj) {
obj.name = "Modified";
}
let num = 5;
let person = { name: "Alice" };
modifyValue(num);
console.log(num); // 5(基本类型不受影响)
modifyObj(person);
console.log(person.name); // "Modified"(引用类型受影响)
包装对象
javascript
复制代码
// 基本类型可以临时调用方法(自动装箱)
let str = "hello";
str.toUpperCase(); // "HELLO"(临时创建 String 对象)
str.length; // 5
// 等价于
let temp = new String("hello");
temp.toUpperCase();
temp = null; // 立即销毁
// 注意:基本类型不能添加属性
let name = "JS";
name.version = "ES6";
console.log(name.version); // undefined(包装对象已销毁)
二、函数与作用域
9. JavaScript 作用域类型
定义
作用域(Scope) 是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期。
作用域类型
1. 全局作用域(Global Scope)
javascript
复制代码
// 全局变量
var globalVar = "I'm global";
function fn() {
console.log(globalVar); // 可访问
}
// 不在任何函数/块内声明的变量都是全局的
window.browserGlobal = "browser global"; // 浏览器环境
global.nodeGlobal = "node global"; // Node.js 环境
2. 函数作用域(Function Scope)
javascript
复制代码
// var 声明的变量只在函数内部可见
function myFunction() {
var localVar = "I'm local";
console.log(localVar); // "I'm local"
}
console.log(localVar); // ReferenceError
3. 块级作用域(Block Scope)- ES6
javascript
复制代码
// let 和 const 声明的变量只在 {} 块内可见
{
let blockLet = "block let";
const blockConst = "block const";
console.log(blockLet); // "block let"
}
console.log(blockLet); // ReferenceError
// if 语句块
if (true) {
let ifVar = "inside if";
}
console.log(ifVar); // ReferenceError
// for 循环块
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}
console.log(i); // ReferenceError
4. 词法作用域(Lexical Scope)
javascript
复制代码
// 函数在定义时确定作用域(静态作用域)
let x = 1;
function outer() {
let x = 2;
function inner() {
console.log(x); // 2(读取定义时的作用域)
}
return inner;
}
let fn = outer();
fn(); // 2
5. 模块作用域(Module Scope)- ES6
javascript
复制代码
// 每个 ES6 模块有独立作用域
// moduleA.js
let moduleVar = "I'm in module A";
export function getVar() { return moduleVar; }
// moduleB.js
let moduleVar = "I'm in module B"; // 不会与 A 冲突
import { getVar } from './moduleA.js';
console.log(getVar()); // "I'm in module A"
作用域对比
作用域类型
声明方式
边界
提升
全局
var/let/const
整个程序
是
函数
var
函数体
是
块级
let/const
{} 块
否(暂时性死区)
词法
-
函数定义位置
-
模块
import/export
模块文件
否
10. 作用域与作用域链
定义
作用域 :变量的可访问范围
作用域链 :当访问变量时,从当前作用域开始,逐级向外查找的链条
原理
JavaScript 采用词法作用域(静态作用域),函数在定义时就确定了其作用域链。作用域链的查找规则:
从当前执行环境的作用域开始查找
如果未找到,沿着作用域链向上查找
直到全局作用域,如果仍未找到则抛出 ReferenceError
代码示例
javascript
复制代码
let globalVar = "global";
function outer() {
let outerVar = "outer";
function inner() {
let innerVar = "inner";
console.log(innerVar); // "inner"(当前作用域)
console.log(outerVar); // "outer"(向上级作用域查找)
console.log(globalVar); // "global"(继续向上查找)
}
inner();
}
outer();
作用域链示意图
sql
复制代码
inner 作用域
│
├─ innerVar: "inner"
│
▼
outer 作用域
│
├─ outerVar: "outer"
│
▼
全局作用域
│
├─ globalVar: "global"
作用域链与性能
javascript
复制代码
// 频繁访问外层变量性能较差
function slow() {
let count = 0;
return function() {
count++; // 需要沿作用域链查找
return count;
};
}
// 优化:缓存到局部变量
function fast() {
let count = 0;
return function(localCount) {
localCount++;
return localCount;
};
}
11. 变量提升(Hoisting)
定义
变量提升 是指 JavaScript 引擎在代码执行前,将变量和函数的声明提升到作用域顶部的行为。
原理
JavaScript 执行分为两个阶段:
编译阶段 :解析代码,收集变量和函数声明
执行阶段 :执行代码,进行赋值和操作
var 变量提升
javascript
复制代码
// 实际代码
console.log(a); // undefined(不会报错)
var a = 10;
console.log(a); // 10
// 等价于
var a; // 声明被提升
console.log(a); // undefined
a = 10; // 赋值不提升
console.log(a); // 10
函数提升
javascript
复制代码
// 函数声明提升
fn(); // "Called"(可以正常执行)
function fn() {
console.log("Called");
}
// 函数表达式不提升
fn2(); // TypeError: fn2 is not a function
var fn2 = function() {
console.log("Called");
};
// 等价于
var fn2;
fn2(); // undefined 不是函数
fn2 = function() { ... };
let/const 不提升
javascript
复制代码
// 实际有提升,但存在"暂时性死区"
console.log(a); // ReferenceError
let a = 10;
// const 同样
console.log(b); // ReferenceError
const b = 20;
提升优先级
javascript
复制代码
// 函数提升 > 变量提升
console.log(typeof foo); // "function"
function foo() {}
var foo = 123;
// 等价于
function foo() {} // 函数提升优先
var foo; // 变量提升被忽略
console.log(typeof foo);
foo = 123;
// 同名变量和函数
var a;
function a() {}
console.log(typeof a); // "number"(执行到赋值时覆盖)
var a = 1;
最佳实践
始终在作用域顶部声明变量
优先使用 let 和 const 避免提升问题
函数声明优先于函数表达式
12. 函数声明与函数表达式的区别
对比表格
特性
函数声明
函数表达式
语法
function name() {}
var name = function() {}
提升
完整提升(声明+定义)
变量提升,函数不提升
名称
必须有名称
可匿名
调用时机
声明前可调用
声明后才能调用
条件创建
不能(部分环境支持但不推荐)
可以
name属性
函数名
变量名或匿名
代码示例
javascript
复制代码
// 函数声明 - 可提前调用
console.log(add(2, 3)); // 5
function add(a, b) {
return a + b;
}
// 函数表达式 - 必须声明后调用
// console.log(sub(5, 2)); // TypeError
var sub = function(a, b) {
return a - b;
};
console.log(sub(5, 2)); // 3
// 命名函数表达式
var multiply = function mult(a, b) {
return a * b;
};
console.log(multiply.name); // "mult"
// 条件创建函数
if (true) {
var fn1 = function() { return 1; }; // ✅ 可以
}
// IIFE(立即执行函数表达式)
(function() {
console.log("IIFE");
})();
// 箭头函数表达式
const divide = (a, b) => a / b;
13. this 指向规则
定义
this 是 JavaScript 中的一个关键字,它在函数执行时自动创建,指向函数的执行上下文。
五种绑定规则
1. 默认绑定(全局/独立函数调用)
javascript
复制代码
function fn() {
console.log(this);
}
fn(); // 浏览器:window,Node:global
// 严格模式下:undefined
var a = 1;
function test() {
console.log(this.a);
}
test(); // 1(非严格模式)
2. 隐式绑定(对象方法调用)
javascript
复制代码
let obj = {
name: "Alice",
greet: function() {
console.log(this.name);
}
};
obj.greet(); // "Alice"(this 指向 obj)
// 隐式丢失
let fn = obj.greet;
fn(); // undefined(this 指向全局对象)
// 链式调用
let user = {
profile: {
name: "Bob",
getName: function() {
return this.name;
}
}
};
console.log(user.profile.getName()); // "Bob"(this 指向 profile)
3. 显式绑定(call/apply/bind)
javascript
复制代码
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
let user = { name: "Alice" };
greet.call(user, "Hello", "!"); // "Hello, Alice!"
greet.apply(user, ["Hello", "!"]); // "Hello, Alice!"
let boundGreet = greet.bind(user);
boundGreet("Hello", "!"); // "Hello, Alice!"
4. new 绑定(构造函数调用)
javascript
复制代码
function Person(name) {
this.name = name;
}
let p = new Person("Alice");
console.log(p.name); // "Alice"(this 指向新对象)
// new 的执行过程:
// 1. 创建空对象 {}
// 2. 将 this 指向该对象
// 3. 执行函数体
// 4. 返回该对象(如果函数无返回值)
5. 箭头函数绑定(词法 this)
javascript
复制代码
// 箭头函数没有自己的 this,继承外层 this
let obj = {
name: "Alice",
greet: () => {
console.log(this); // 指向定义时的外层作用域
}
};
obj.greet(); // window/global(箭头函数在对象中指向全局)
// 正确用法:在回调中保持 this
let counter = {
count: 0,
start() {
setInterval(() => {
this.count++; // this 指向 counter
console.log(this.count);
}, 1000);
}
};
counter.start(); // 1, 2, 3...
this 优先级
arduino
复制代码
new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
箭头函数与普通函数对比
特性
普通函数
箭头函数
this 指向
调用时确定
定义时确定(继承外层)
arguments
有
无(用 rest 参数)
new
可以
不可以
prototype
有
无
call/apply/bind
可修改 this
无效(this 固定)
作为方法
适合
不适合
作为回调
需要 bind
适合
14. call、apply、bind 的区别
定义
这三个方法都用于修改函数的 this 指向,但使用方式不同。
对比表格
特性
call
apply
bind
参数形式
逐个传入
数组传入
逐个传入
执行时机
立即执行
立即执行
返回新函数(稍后执行)
返回值
函数执行结果
函数执行结果
绑定 this 后的新函数
柯里化
不支持
不支持
支持
代码示例
javascript
复制代码
function greet(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
return this.name;
}
let user = { name: "Alice" };
// call - 逐个参数
let result1 = greet.call(user, "Hello", "!");
// "Hello, Alice!"
// apply - 数组参数
let result2 = greet.apply(user, ["Hello", "!"]);
// "Hello, Alice!"
// bind - 返回新函数
let boundGreet = greet.bind(user, "Hello");
let result3 = boundGreet("!");
// "Hello, Alice!"
// 实际应用场景
// 1. 借用方法
let arrayLike = { 0: "a", 1: "b", length: 2 };
let arr = Array.prototype.slice.call(arrayLike);
// ["a", "b"]
// 2. Math.max 求数组最大值
let nums = [1, 5, 3, 9, 2];
Math.max.apply(null, nums); // 9
Math.max(...nums); // 9(ES6 展开运算符)
// 3. 柯里化
function add(a, b, c) {
return a + b + c;
}
let add5 = add.bind(null, 5);
add5(3, 2); // 10
// 4. 定时器中保持 this
let timer = {
count: 0,
start() {
setTimeout(function() {
this.count++; // this 指向 window
}.bind(this), 1000); // 绑定到 timer
}
};
手写实现
javascript
复制代码
// 手写 call
Function.prototype.myCall = function(context, ...args) {
if (typeof this !== 'function') {
throw new TypeError('Not a function');
}
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
// 手写 apply
Function.prototype.myApply = function(context, args = []) {
if (typeof this !== 'function') {
throw new TypeError('Not a function');
}
context = context || window;
context.fn = this;
const result = context.fn(...args);
delete context.fn;
return result;
};
// 手写 bind
Function.prototype.myBind = function(context, ...args1) {
if (typeof this !== 'function') {
throw new TypeError('Not a function');
}
const fn = this;
function BoundFn(...args2) {
// new 调用时 this 指向实例
return fn.apply(
this instanceof BoundFn ? this : context,
[...args1, ...args2]
);
}
BoundFn.prototype = Object.create(fn.prototype);
return BoundFn;
};
三、闭包与原型链
15. 闭包
定义
闭包(Closure) 是指有权访问另一个函数作用域中变量的函数。闭包由函数及其引用的外部词法环境组合而成。
原理
当函数被创建时,会保存其创建时的作用域链。即使外部函数已执行完毕,内部函数仍能访问外部函数的变量,这些变量不会被垃圾回收。
代码示例
javascript
复制代码
// 基本闭包
function outer() {
let count = 0;
return function inner() {
count++;
return count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count 变量被闭包保存,不会丢失
闭包的用途
1. 数据私有化
javascript
复制代码
function createCounter() {
let count = 0; // 私有变量
return {
increment() { return ++count; },
decrement() { return --count; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment(); // 1
counter.getCount(); // 1
// counter.count; // undefined(无法直接访问)
2. 函数工厂
javascript
复制代码
function createMultiplier(multiplier) {
return function(x) {
return x * multiplier;
};
}
const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
3. 延迟执行
javascript
复制代码
function delay(fn, ms) {
return function(...args) {
setTimeout(() => fn(...args), ms);
};
}
const log = delay(console.log, 1000);
log("Delayed message"); // 1秒后输出
4. 事件处理器
javascript
复制代码
function setupButton() {
let clickCount = 0;
document.getElementById('btn').addEventListener('click', function() {
clickCount++;
console.log(`Clicked ${clickCount} times`);
});
}
5. 模块模式
javascript
复制代码
const myModule = (function() {
let privateVar = "secret";
function privateFn() {
console.log("Private function");
}
return {
publicVar: "public",
publicFn() {
console.log(privateVar);
privateFn();
}
};
})();
myModule.publicFn(); // 可访问私有变量和函数
闭包的优缺点
优点
缺点
数据私有化,封装性强
内存占用大(变量不被回收)
避免全局变量污染
使用不当会导致内存泄漏
创建函数工厂
调试困难
实现柯里化
性能开销
内存泄漏问题
javascript
复制代码
// 问题:闭包持有大对象引用
function createLeak() {
let largeData = new Array(1000000).fill("data");
return function() {
return "small"; // 只需要返回值,但持有 largeData
};
}
// 解决:手动释放
function createSafe() {
let largeData = new Array(1000000).fill("data");
return function() {
let result = "small";
largeData = null; // 手动释放
return result;
};
}
经典面试题
javascript
复制代码
// 问题:输出什么?
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3, 3, 3
}, 100);
}
// 解决方案1:使用 let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100); // 0, 1, 2
}
// 解决方案2:使用 IIFE 创建闭包
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(() => console.log(j), 100);
})(i);
}
// 解决方案3:setTimeout 第三个参数
for (var i = 0; i < 3; i++) {
setTimeout((j) => console.log(j), 100, i);
}
16. 原型与原型链
定义
原型(Prototype) :每个函数都有一个 prototype 属性,指向其原型对象
原型链(Prototype Chain) :对象通过 __proto__ 链接到其原型,形成查找链
核心概念
javascript
复制代码
function Person(name) {
this.name = name;
}
// prototype 属性(函数独有)
Person.prototype; // 原型对象
Person.prototype.constructor === Person; // true
// 实例对象
let p = new Person("Alice");
// __proto__ 属性(所有对象都有)
p.__proto__ === Person.prototype; // true
// 原型链
p.__proto__.__proto__ === Object.prototype; // true
p.__proto__.__proto__.__proto__ === null; // true
原型链查找机制
javascript
复制代码
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};
let p = new Person("Alice");
// 属性查找顺序:
// 1. 先在对象自身查找
console.log(p.name); // "Alice"(自身属性)
// 2. 在原型上查找
p.sayHi(); // "Hi, I'm Alice"(原型方法)
// 3. 在原型链上查找
p.toString(); // "[object Object]"(Object.prototype)
// 4. 查找失败
p.nonExist; // undefined
原型链示意图
scss
复制代码
p (实例)
│ __proto__
▼
Person.prototype
│ sayHi()
│ __proto__
▼
Object.prototype
│ toString()
│ valueOf()
│ __proto__
▼
null
prototype vs proto vs constructor
属性
所属
说明
prototype
函数
指向原型对象,用于实例共享属性
__proto__
对象
指向创建该对象的函数的原型
constructor
原型对象
指回构造函数本身
javascript
复制代码
function Person() {}
let p = new Person();
p.constructor === Person; // true
p.__proto__ === Person.prototype; // true
Person.prototype.constructor === Person; // true
17. JavaScript 继承的实现方式
1. 原型链继承
javascript
复制代码
function Parent() {
this.name = "Parent";
this.colors = ["red", "blue"];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child() {}
// 核心:将子类的原型指向父类的实例
Child.prototype = new Parent();
Child.prototype.constructor = Child;
let child = new Child();
console.log(child.getName()); // "Parent"
// 问题1:引用类型共享
child.colors.push("green");
let child2 = new Child();
console.log(child2.colors); // ["red", "blue", "green"]
// 问题2:无法传参
function Child2(age) {
this.age = age;
}
Child2.prototype = new Parent(); // 无法传递参数给 Parent
2. 构造函数继承(经典继承)
javascript
复制代码
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
function Child(name, age) {
// 核心:在子类构造函数中调用父类
Parent.call(this, name);
this.age = age;
}
let child = new Child("Alice", 10);
console.log(child.name); // "Alice"
console.log(child.age); // 10
// 优点:解决了引用类型共享和传参问题
child.colors.push("green");
let child2 = new Child("Bob", 8);
console.log(child2.colors); // ["red", "blue"]
// 缺点:无法继承原型上的方法
// child.getName(); // TypeError
3. 组合继承(最常用)
javascript
复制代码
function Parent(name) {
this.name = name;
this.colors = ["red", "blue"];
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent
Child.prototype.constructor = Child;
Child.prototype.getAge = function() {
return this.age;
};
let child = new Child("Alice", 10);
console.log(child.getName()); // "Alice"
console.log(child.getAge()); // 10
// 优点:结合了两种继承方式的优点
// 缺点:调用了两次父类构造函数
4. 寄生组合继承(最优)
javascript
复制代码
function inheritPrototype(Child, Parent) {
// 创建父类原型的副本
let prototype = Object.create(Parent.prototype);
prototype.constructor = Child;
Child.prototype = prototype;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
};
function Child(name, age) {
Parent.call(this, name); // 只调用一次
this.age = age;
}
// 核心:继承原型而不是父类实例
inheritPrototype(Child, Parent);
Child.prototype.getAge = function() {
return this.age;
};
5. ES6 Class 继承
javascript
复制代码
class Parent {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
static sayHello() {
return "Hello";
}
}
class Child extends Parent {
constructor(name, age) {
super(name); // 必须先调用 super
this.age = age;
}
getAge() {
return this.age;
}
// 方法重写
getName() {
return `Child: ${super.getName()}`;
}
}
let child = new Child("Alice", 10);
console.log(child.getName()); // "Child: Alice"
console.log(Child.sayHello()); // "Hello"(继承静态方法)
// ES6 继承的本质
// class Child extends Parent
// 等价于
// Child.prototype = Object.create(Parent.prototype)
// Child.prototype.constructor = Child
继承方式对比
方式
继承实例属性
继承原型方法
支持传参
调用次数
推荐度
原型链
✅
✅
❌
1次
⭐
构造函数
✅
❌
✅
1次
⭐⭐
组合继承
✅
✅
✅
2次
⭐⭐⭐
寄生组合
✅
✅
✅
1次
⭐⭐⭐⭐⭐
ES6 Class
✅
✅
✅
1次
⭐⭐⭐⭐⭐
四、事件机制与事件委托
18. 事件冒泡与事件捕获
定义
事件冒泡(Event Bubbling) :事件从目标元素向上传播到根元素
事件捕获(Event Capturing) :事件从根元素向下传播到目标元素
DOM 事件流三阶段
css
复制代码
1. 捕获阶段(Capture Phase)
document → html → body → parent → target
2. 目标阶段(Target Phase)
target 元素
3. 冒泡阶段(Bubbling Phase)
target → parent → body → html → document
代码示例
html
复制代码
<div id="outer">
<div id="inner">
<button id="btn">Click</button>
</div>
</div>
javascript
复制代码
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');
// 冒泡阶段(默认)
btn.addEventListener('click', () => console.log('btn bubble'));
inner.addEventListener('click', () => console.log('inner bubble'));
outer.addEventListener('click', () => console.log('outer bubble'));
// 点击 btn 输出:btn bubble → inner bubble → outer bubble
// 捕获阶段
outer.addEventListener('click', () => console.log('outer capture'), true);
inner.addEventListener('click', () => console.log('inner capture'), true);
btn.addEventListener('click', () => console.log('btn capture'), true);
// 点击 btn 输出:outer capture → inner capture → btn capture
// 混合使用
// 点击 btn 输出:outer capture → inner capture → btn capture → btn bubble → inner bubble → outer bubble
阻止事件传播
javascript
复制代码
// 阻止冒泡
element.addEventListener('click', function(e) {
e.stopPropagation();
});
// 阻止捕获
element.addEventListener('click', function(e) {
e.stopImmediatePropagation(); // 阻止同一元素上其他监听器
}, true);
不冒泡的事件
javascript
复制代码
// 不冒泡的事件
focus, blur
load, unload
mouseenter, mouseleave
resize, scroll
mouseenter vs mouseover
特性
mouseenter
mouseover
冒泡
不冒泡
冒泡
触发
只在进入元素时
进入元素和子元素时都触发
适用
简单悬停效果
需要捕获子元素事件
19. 事件委托
定义
事件委托(Event Delegation) 是利用事件冒泡原理,将子元素的事件监听器设置在父元素上,通过事件对象判断实际触发元素。
原理
在父元素上绑定一个事件监听器
利用事件冒泡,子元素事件会传播到父元素
通过 event.target 判断实际触发元素
根据判断结果执行相应处理
代码示例
javascript
复制代码
// 未使用事件委托(性能差)
const items = document.querySelectorAll('.item');
items.forEach(item => {
item.addEventListener('click', function() {
console.log(this.textContent);
});
});
// 使用事件委托(推荐)
const list = document.getElementById('list');
list.addEventListener('click', function(e) {
if (e.target.classList.contains('item')) {
console.log(e.target.textContent);
}
});
优势
内存优化 :减少事件监听器数量
动态元素 :新添加的子元素自动具有事件处理
代码简洁 :只需一个监听器
完整实现
javascript
复制代码
// 通用事件委托函数
function delegate(element, eventType, selector, handler) {
element.addEventListener(eventType, function(e) {
const target = e.target;
if (target.matches(selector)) {
handler.call(target, e);
} else {
// 向上查找匹配元素
let parent = target.parentElement;
while (parent && parent !== element) {
if (parent.matches(selector)) {
handler.call(parent, e);
break;
}
parent = parent.parentElement;
}
}
});
}
// 使用
const container = document.getElementById('container');
delegate(container, 'click', '.btn', function(e) {
console.log('Button clicked:', this.textContent);
});
20. DOM 事件级别
DOM0 级事件
javascript
复制代码
// 直接赋值,只能绑定一个处理函数
element.onclick = function() {
console.log('Clicked');
};
// 移除
element.onclick = null;
DOM2 级事件
javascript
复制代码
// 可绑定多个处理函数
element.addEventListener('click', function() {
console.log('Handler 1');
});
element.addEventListener('click', function() {
console.log('Handler 2');
});
// 移除(必须使用相同函数引用)
function handler() { console.log('Clicked'); }
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
// 第三个参数:capture(布尔值)或 options(对象)
element.addEventListener('click', handler, false); // 冒泡
element.addEventListener('click', handler, {
capture: false,
once: true, // 只触发一次
passive: true // 不会调用 preventDefault
});
DOM3 级事件
javascript
复制代码
// 增加了更多事件类型
// UI事件、焦点事件、鼠标事件、滚轮事件、键盘事件、输入事件等
// 自定义事件
const event = new CustomEvent('myEvent', {
detail: { data: 'custom data' },
bubbles: true,
cancelable: true
});
element.dispatchEvent(event);
element.addEventListener('myEvent', function(e) {
console.log(e.detail.data);
});
addEventListener vs attachEvent
特性
addEventListener
attachEvent
浏览器
标准(现代浏览器)
IE8及以下
事件类型
'click'
'onclick'
this 指向
触发元素
window
执行顺序
添加顺序
逆序
事件对象
第一个参数
window.event
五、异步编程
21. 回调函数与回调地狱
回调函数
javascript
复制代码
// 同步回调
[1, 2, 3].forEach(item => console.log(item));
// 异步回调
setTimeout(function() {
console.log('Delayed');
}, 1000);
// Node.js 风格回调
fs.readFile('file.txt', function(err, data) {
if (err) {
console.error(err);
return;
}
console.log(data);
});
回调地狱
javascript
复制代码
// 问题:嵌套回调难以阅读和维护
getUser(function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
updateOrder(details, function(result) {
sendNotification(result, function() {
console.log('Done');
});
});
});
});
});
// 解决方案1:命名函数
function handleNotification() { console.log('Done'); }
function handleUpdate(result) { sendNotification(result, handleNotification); }
function handleDetails(details) { updateOrder(details, handleUpdate); }
function handleOrders(orders) { getOrderDetails(orders[0].id, handleDetails); }
function handleUser(user) { getOrders(user.id, handleOrders); }
getUser(handleUser);
// 解决方案2:Promise
getUser()
.then(user => getOrders(user.id))
.then(orders => getOrderDetails(orders[0].id))
.then(details => updateOrder(details))
.then(result => sendNotification(result))
.then(() => console.log('Done'))
.catch(err => console.error(err));
// 解决方案3:async/await
async function processOrder() {
try {
const user = await getUser();
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
const result = await updateOrder(details);
await sendNotification(result);
console.log('Done');
} catch (err) {
console.error(err);
}
}
22. Promise
定义
Promise 是 JavaScript 中处理异步操作的对象,表示一个异步操作的最终完成或失败。
三种状态
状态
说明
可转换
pending
进行中
→ fulfilled 或 rejected
fulfilled
已成功
不可变
rejected
已失败
不可变
基本用法
javascript
复制代码
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('Success!');
} else {
reject(new Error('Failed!'));
}
}, 1000);
});
promise
.then(result => console.log(result))
.catch(error => console.error(error))
.finally(() => console.log('Done'));
Promise 链式调用
javascript
复制代码
// then 返回 Promise,实现链式调用
fetchUser(1)
.then(user => fetchOrders(user.id))
.then(orders => fetchOrderDetails(orders[0].id))
.then(details => console.log(details))
.catch(err => console.error(err));
// then 的返回值
Promise.resolve(1)
.then(val => val + 1) // 返回普通值
.then(val => Promise.resolve(val * 2)) // 返回 Promise
.then(val => console.log(val)); // 4
Promise 静态方法
javascript
复制代码
// Promise.resolve - 创建成功 Promise
Promise.resolve('value');
Promise.resolve(promise); // 返回原 Promise
// Promise.reject - 创建失败 Promise
Promise.reject(new Error('error'));
// Promise.all - 所有 Promise 都成功
Promise.all([p1, p2, p3])
.then(results => console.log(results)) // [r1, r2, r3]
.catch(err => console.error(err)); // 任一失败则失败
// Promise.race - 第一个完成的 Promise
Promise.race([p1, p2, p3])
.then(first => console.log(first))
.catch(err => console.error(err));
// Promise.allSettled - 等待所有完成(不论成功失败)
Promise.allSettled([p1, p2, p3])
.then(results => {
results.forEach(r => {
if (r.status === 'fulfilled') console.log(r.value);
else console.error(r.reason);
});
});
// Promise.any - 第一个成功的 Promise
Promise.any([p1, p2, p3])
.then(first => console.log(first))
.catch(err => console.error(err)); // 全部失败
Promise 错误处理
javascript
复制代码
// 方法1:catch 捕获
promise
.then(result => process(result))
.catch(err => console.error(err));
// 方法2:then 第二个参数
promise.then(
result => process(result),
err => console.error(err)
);
// 方法3:async/await try-catch
async function handle() {
try {
const result = await promise;
process(result);
} catch (err) {
console.error(err);
}
}
// 注意:catch 之后的 then 仍会执行
promise
.then(() => { throw new Error('Oops'); })
.catch(err => console.log('Caught:', err.message))
.then(() => console.log('Still runs')); // 会执行
23. async/await
定义
async/await 是基于 Promise 的语法糖,使异步代码看起来像同步代码。
原理
async/await 基于 Generator 函数和自动执行器实现:
async 函数返回 Promise
await 暂停函数执行,等待 Promise 完成
使用 Generator 的 yield 实现暂停
使用自动执行器实现自动恢复
基本用法
javascript
复制代码
// async 函数总是返回 Promise
async function fetchUser() {
return { name: 'Alice' };
// 等价于 return Promise.resolve({ name: 'Alice' });
}
fetchUser().then(user => console.log(user));
// await 必须在 async 函数内
async function getUser() {
const response = await fetch('/api/user');
const data = await response.json();
return data;
}
// 错误处理
async function safeFetch() {
try {
const response = await fetch('/api/user');
const data = await response.json();
return data;
} catch (err) {
console.error('Fetch failed:', err);
return null;
}
}
并发控制
javascript
复制代码
// 串行(顺序执行)
async function serial() {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
return orders;
}
// 并行(同时执行)
async function parallel() {
const [user, orders] = await Promise.all([
fetchUser(),
fetchOrders(1)
]);
return { user, orders };
}
// 限制并发数量
async function limitConcurrency(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const p = task().then(r => r);
results.push(p);
if (limit <= tasks.length) {
const e = p.then(() =>
executing.splice(executing.indexOf(e), 1)
);
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
async/await vs Promise
特性
Promise
async/await
语法
链式调用
同步风格
错误处理
.catch()
try-catch
并发
Promise.all
Promise.all + await
调试
困难
容易(同步堆栈)
兼容性
ES6
ES2017
返回值
Promise
Promise
24. 事件循环(Event Loop)
定义
事件循环(Event Loop) 是 JavaScript 实现异步的核心机制,负责协调调用栈、任务队列和回调的执行。
浏览器事件循环
javascript
复制代码
调用栈(Call Stack)
│
▼
微任务队列(Microtask Queue)
├─ Promise.then/catch/finally
├─ MutationObserver
├─ queueMicrotask
│
▼
宏任务队列(Macrotask Queue)
├─ setTimeout
├─ setInterval
├─ I/O
├─ UI 渲染
├─ setImmediate (Node.js)
执行顺序
javascript
复制代码
console.log('1'); // 同步代码
setTimeout(() => {
console.log('2'); // 宏任务
}, 0);
Promise.resolve().then(() => {
console.log('3'); // 微任务
});
console.log('4'); // 同步代码
// 输出顺序:1 → 4 → 3 → 2
详细执行流程
javascript
复制代码
// 复杂示例
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(() => console.log('setTimeout'), 0);
async1();
new Promise(resolve => {
console.log('promise executor');
resolve();
}).then(() => console.log('promise then'));
console.log('script end');
// 输出顺序:
// script start
// async1 start
// async2
// promise executor
// script end
// async1 end
// promise then
// setTimeout
微任务与宏任务对比
特性
宏任务(Macrotask)
微任务(Microtask)
包含
setTimeout, setInterval, I/O, UI渲染
Promise, MutationObserver, queueMicrotask
优先级
低
高
执行时机
每次事件循环执行一个
清空所有微任务
队列数量
多个
一个
执行规则
执行同步代码(调用栈)
清空所有微任务
执行一个宏任务
清空所有微任务
重复 3-4
javascript
复制代码
// 验证规则
setTimeout(() => console.log('timeout1'), 0);
setTimeout(() => console.log('timeout2'), 0);
Promise.resolve()
.then(() => console.log('promise1'))
.then(() => console.log('promise2'));
// 输出:promise1 → promise2 → timeout1 → timeout2
// 微任务全部执行完才执行下一个宏任务
六、DOM 操作
25. DOM 操作方法
查找元素
javascript
复制代码
// 旧方法
document.getElementById('id');
document.getElementsByClassName('class');
document.getElementsByTagName('tag');
document.getElementsByName('name');
// 新方法(推荐)
document.querySelector('.class');
document.querySelectorAll('.class');
// 元素内查找
element.querySelector('button');
element.querySelectorAll('li');
创建元素
javascript
复制代码
// 创建元素
const div = document.createElement('div');
const text = document.createTextNode('Hello');
const fragment = document.createDocumentFragment();
// 设置属性
div.setAttribute('id', 'myDiv');
div.setAttribute('data-value', '123');
div.id = 'myDiv';
div.className = 'my-class';
div.classList.add('class1', 'class2');
div.classList.remove('class3');
div.classList.toggle('active');
// 设置内容
div.textContent = 'Text content';
div.innerHTML = '<span>HTML</span>';
div.innerText = 'Visible text';
添加/删除元素
javascript
复制代码
// 添加
parent.appendChild(child);
parent.insertBefore(newChild, referenceChild);
parent.append(...nodes); // ES6,可添加多个
parent.prepend(...nodes); // ES6,插入到开头
element.before(...nodes); // ES6,插入到元素前
element.after(...nodes); // ES6,插入到元素后
element.replaceWith(...nodes); // ES6,替换元素
// 删除
parent.removeChild(child);
element.remove();
element.parentNode.removeChild(element);
// 克隆
let clone = element.cloneNode(false); // 浅克隆
let deepClone = element.cloneNode(true); // 深克隆
innerHTML vs outerHTML
特性
innerHTML
outerHTML
内容
元素内部 HTML
元素自身+内部 HTML
赋值影响
替换内部内容
替换整个元素
安全性
XSS 风险
XSS 风险
javascript
复制代码
// innerHTML
<div id="box"><p>Original</p></div>
document.getElementById('box').innerHTML = '<span>New</span>';
// 结果:<div id="box"><span>New</span></div>
// outerHTML
<div id="box"><p>Original</p></div>
document.getElementById('box').outerHTML = '<div>New</div>';
// 结果:<div>New</div>(原元素被替换)
innerText vs textContent
特性
innerText
textContent
可见性
只获取可见文本
获取所有文本
性能
较慢(触发重排)
较快
格式
保留换行和空格
不保留格式
隐藏元素
不包含隐藏文本
包含隐藏文本
七、ES5 核心特性
26. 严格模式
定义
严格模式(Strict Mode) 是一种限制性更强的 JavaScript 执行模式,用于消除不合理语法、提高安全性。
启用方式
javascript
复制代码
// 全局启用
"use strict";
// 函数内启用
function fn() {
"use strict";
}
// 模块自动启用严格模式
严格模式的特点
javascript
复制代码
// 1. 变量必须声明
"use strict";
x = 1; // ReferenceError
// 2. 禁止删除变量
"use strict";
var x = 1;
delete x; // SyntaxError
// 3. 对象属性不能重复
"use strict";
var obj = { a: 1, a: 2 }; // SyntaxError
// 4. 函数参数不能重名
"use strict";
function fn(a, a) {} // SyntaxError
// 5. this 指向 undefined
"use strict";
function fn() { console.log(this); }
fn(); // undefined(非严格模式是 window)
// 6. 禁止八进制字面量
"use strict";
var n = 010; // SyntaxError
// 7. eval 有独立作用域
"use strict";
eval("var x = 1");
console.log(x); // ReferenceError
// 8. 禁止 arguments.callee
"use strict";
function fn() { arguments.callee; } // TypeError
27. Object.defineProperty
定义
Object.defineProperty 用于精确控制对象属性的行为,可定义数据属性和访问器属性。
参数说明
javascript
复制代码
Object.defineProperty(obj, prop, descriptor);
数据属性
javascript
复制代码
let person = {};
Object.defineProperty(person, 'name', {
value: 'Alice', // 属性值
writable: true, // 是否可写
enumerable: true, // 是否可枚举
configurable: true // 是否可删除/修改描述符
});
console.log(person.name); // "Alice"
person.name = 'Bob';
console.log(person.name); // "Bob"
访问器属性(getter/setter)
javascript
复制代码
let product = {
_price: 100
};
Object.defineProperty(product, 'price', {
get() {
console.log('Getting price');
return this._price;
},
set(value) {
if (value < 0) {
throw new Error('Price cannot be negative');
}
console.log('Setting price to', value);
this._price = value;
},
enumerable: true,
configurable: true
});
console.log(product.price); // Getting price → 100
product.price = 200; // Setting price to 200
数据属性与访问器属性互斥
javascript
复制代码
// 错误:不能同时有 value 和 get
Object.defineProperty(obj, 'prop', {
value: 1,
get() { return 2; } // TypeError
});
// 正确:二选一
Object.defineProperty(obj, 'prop', {
value: 1 // 数据属性
});
Object.defineProperty(obj, 'prop', {
get() { return 1; }, // 访问器属性
set(val) { /* ... */ }
});
Vue 2 中的响应式实现
javascript
复制代码
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log(`Getting ${key}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`Setting ${key} to ${newVal}`);
val = newVal;
// 通知更新
notifyUpdate();
}
}
});
}
局限性
无法检测对象新增属性
无法检测数组索引变化
Vue 2 使用 Vue.set 解决此问题
Vue 3 使用 Proxy 替代
28. Object.defineProperty vs Proxy
特性
Object.defineProperty
Proxy
监听范围
单个属性
整个对象
新增属性
无法监听
可以监听
数组索引
无法监听
可以监听
性能
较好
稍差
兼容性
IE9+
ES6+
拦截操作
get/set
13种拦截
javascript
复制代码
// Proxy 示例
let obj = {};
let proxy = new Proxy(obj, {
get(target, key) {
console.log(`Getting ${key}`);
return target[key];
},
set(target, key, value) {
console.log(`Setting ${key} to ${value}`);
target[key] = value;
return true;
},
deleteProperty(target, key) {
console.log(`Deleting ${key}`);
delete target[key];
return true;
},
has(target, key) {
console.log(`Checking ${key}`);
return key in target;
}
});
proxy.name = "Alice"; // Setting name to Alice
console.log(proxy.name); // Getting name → "Alice"
八、JavaScript 设计模式
29. 单例模式
定义
单例模式(Singleton) 确保一个类只有一个实例,并提供全局访问点。
实现方式
javascript
复制代码
// 方式1:简单实现
const singleton = {
method1() { /* ... */ },
method2() { /* ... */ }
};
// 方式2:惰性单例
class Singleton {
constructor(name) {
if (Singleton.instance) {
return Singleton.instance;
}
this.name = name;
Singleton.instance = this;
}
}
const s1 = new Singleton('A');
const s2 = new Singleton('B');
console.log(s1 === s2); // true
// 方式3:闭包实现
const Singleton2 = (function() {
let instance;
function createInstance(name) {
return { name, getName() { return name; } };
}
return {
getInstance(name) {
if (!instance) {
instance = createInstance(name);
}
return instance;
}
};
})();
const a = Singleton2.getInstance('A');
const b = Singleton2.getInstance('B');
console.log(a === b); // true
30. 观察者模式 vs 发布订阅模式
观察者模式(Observer)
javascript
复制代码
// 主题(被观察者)
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
this.observers = this.observers.filter(o => o !== observer);
}
notify(data) {
this.observers.forEach(o => o.update(data));
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} received:`, data);
}
}
// 使用
const subject = new Subject();
const obs1 = new Observer('A');
const obs2 = new Observer('B');
subject.addObserver(obs1);
subject.addObserver(obs2);
subject.notify('Hello'); // 两者都收到通知
发布订阅模式(Pub/Sub)
javascript
复制代码
class EventEmitter {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(...args));
}
}
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
// 使用
const emitter = new EventEmitter();
emitter.on('data', (data) => console.log('Handler 1:', data));
emitter.on('data', (data) => console.log('Handler 2:', data));
emitter.emit('data', 'Hello');
对比
特性
观察者模式
发布订阅模式
耦合度
紧耦合
松耦合
调度中心
无(直接通知)
有(事件中心)
灵活性
低
高
适用场景
对象间一对多
组件间通信
示例
MVC 模型-视图
Node.js EventEmitter
31. 工厂模式
javascript
复制代码
// 简单工厂
class Car {
constructor(type) {
this.type = type;
}
}
class CarFactory {
static create(type) {
switch(type) {
case 'sedan': return new Car('sedan');
case 'suv': return new Car('suv');
case 'truck': return new Car('truck');
default: throw new Error('Unknown type');
}
}
}
const sedan = CarFactory.create('sedan');
// 工厂方法模式
class Vehicle {
drive() { console.log('Driving'); }
}
class Car2 extends Vehicle {
drive() { console.log('Driving car'); }
}
class Truck extends Vehicle {
drive() { console.log('Driving truck'); }
}
class VehicleFactory {
create(type) {
switch(type) {
case 'car': return new Car2();
case 'truck': return new Truck();
}
}
}
32. 模块模式
javascript
复制代码
// IIFE 模块
const myModule = (function() {
// 私有成员
let privateVar = 0;
function privateMethod() { return privateVar; }
// 公共接口
return {
increment() { privateVar++; },
getValue() { return privateMethod(); }
};
})();
myModule.increment();
console.log(myModule.getValue()); // 1
// ES6 模块
// utils.js
export function helper() { /* ... */ }
export const constant = 42;
export default class MyClass { /* ... */ }
// main.js
import MyClass, { helper, constant } from './utils.js';
九、性能优化与防抖节流
33. 防抖(Debounce)
定义
防抖 :在事件被触发后等待一段时间,如果这段时间内再次触发,则重新计时。只有最后一次触发才会执行。
实现
javascript
复制代码
// 基础防抖
function debounce(fn, delay) {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 支持立即执行
function debounce(fn, delay, immediate = false) {
let timer = null;
let result;
return function(...args) {
clearTimeout(timer);
if (immediate) {
const callNow = !timer;
timer = setTimeout(() => { timer = null; }, delay);
if (callNow) result = fn.apply(this, args);
} else {
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
}
return result;
};
}
// 支持取消
function debounce(fn, delay) {
let timer = null;
function debounced(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
}
debounced.cancel = function() {
clearTimeout(timer);
timer = null;
};
return debounced;
}
应用场景
javascript
复制代码
// 搜索框输入
const searchInput = document.getElementById('search');
const search = debounce(function(query) {
fetchResults(query);
}, 300);
searchInput.addEventListener('input', (e) => search(e.target.value));
// 窗口大小调整
window.addEventListener('resize', debounce(function() {
updateLayout();
}, 200));
// 滚动事件
window.addEventListener('scroll', debounce(function() {
checkVisibility();
}, 100));
34. 节流(Throttle)
定义
节流 :在一段时间内只执行一次函数,控制函数执行的频率。
实现
javascript
复制代码
// 时间戳版(立即执行)
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 定时器版(最后执行)
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
};
}
// 完整版(支持前后执行)
function throttle(fn, delay, options = { leading: true, trailing: true }) {
let timer = null;
let lastTime = 0;
return function throttled(...args) {
const now = Date.now();
// 处理首次执行
if (!lastTime && options.leading === false) {
lastTime = now;
}
const remaining = delay - (now - lastTime);
if (remaining <= 0 || remaining > delay) {
if (timer) {
clearTimeout(timer);
timer = null;
}
lastTime = now;
fn.apply(this, args);
} else if (!timer && options.trailing !== false) {
timer = setTimeout(() => {
lastTime = options.leading === false ? 0 : Date.now();
timer = null;
fn.apply(this, args);
}, remaining);
}
};
throttled.cancel = function() {
clearTimeout(timer);
timer = null;
lastTime = 0;
};
}
应用场景
javascript
复制代码
// 滚动加载
window.addEventListener('scroll', throttle(function() {
checkAndLoadMore();
}, 200));
// 按钮点击限制
const submitBtn = document.getElementById('submit');
submitBtn.addEventListener('click', throttle(function() {
submitForm();
}, 1000, { leading: true, trailing: false }));
// 鼠标移动追踪
document.addEventListener('mousemove', throttle(function(e) {
updatePosition(e.clientX, e.clientY);
}, 50));
防抖 vs 节流对比
特性
防抖
节流
执行时机
最后一次触发后
固定时间间隔
特点
合并多次为一次
控制执行频率
适用
搜索输入
滚动、点击限制
比喻
电梯关门(等人齐)
红绿灯(固定间隔)
35. 深拷贝与浅拷贝
浅拷贝
javascript
复制代码
// 方法1:Object.assign
let obj = { a: 1, b: { c: 2 } };
let copy1 = Object.assign({}, obj);
copy1.b.c = 3;
console.log(obj.b.c); // 3(嵌套对象仍共享)
// 方法2:展开运算符
let copy2 = { ...obj };
// 方法3:Array.slice
let arr = [1, { a: 2 }];
let copy3 = arr.slice();
// 方法4:Array.from
let copy4 = Array.from(arr);
// 方法5:手写浅拷贝
function shallowCopy(obj) {
if (typeof obj !== 'object' || obj === null) return obj;
const copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
深拷贝
javascript
复制代码
// 方法1:JSON(有缺陷)
let obj = { a: 1, b: new Date(), c: undefined };
let copy = JSON.parse(JSON.stringify(obj));
// Date 变字符串,undefined 丢失
// JSON 缺陷:
// 1. 函数丢失
// 2. undefined 丢失
// 3. Symbol 丢失
// 4. Date 变字符串
// 5. RegExp 变空对象
// 6. Map/Set 丢失
// 7. 循环引用报错
// 方法2:手写深拷贝
function deepClone(obj, map = new WeakMap()) {
// 处理基本类型
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 处理循环引用
if (map.has(obj)) {
return map.get(obj);
}
// 处理特殊对象
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (obj instanceof Map) {
const copy = new Map();
map.set(obj, copy);
obj.forEach((val, key) => copy.set(key, deepClone(val, map)));
return copy;
}
if (obj instanceof Set) {
const copy = new Set();
map.set(obj, copy);
obj.forEach(val => copy.add(deepClone(val, map)));
return copy;
}
// 处理对象和数组
const copy = Array.isArray(obj) ? [] : {};
map.set(obj, copy);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = deepClone(obj[key], map);
}
}
return copy;
}
// 测试
let original = {
a: 1,
b: { c: 2 },
d: [1, 2, 3],
e: new Date(),
f: /regex/g,
g: new Map([['key', 'value']]),
h: undefined,
i: Symbol('sym')
};
let cloned = deepClone(original);
cloned.b.c = 100;
console.log(original.b.c); // 2(不受影响)
36. 数组去重
javascript
复制代码
// 方法1:Set(推荐)
function unique1(arr) {
return [...new Set(arr)];
}
// 方法2:filter + indexOf
function unique2(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// 方法3:reduce + includes
function unique3(arr) {
return arr.reduce((acc, cur) => {
return acc.includes(cur) ? acc : [...acc, cur];
}, []);
}
// 方法4:Map
function unique4(arr) {
const map = new Map();
return arr.filter(item => !map.has(item) && map.set(item, true));
}
// 方法5:对象键
function unique5(arr) {
const obj = {};
return arr.filter(item => {
const key = typeof item + item;
return obj.hasOwnProperty(key) ? false : (obj[key] = true);
});
}
// 包含对象的去重
function uniqueWithObjects(arr) {
const seen = new Map();
return arr.filter(item => {
const key = JSON.stringify(item);
return seen.has(key) ? false : seen.set(key, true);
});
}
37. 数组扁平化
javascript
复制代码
// 方法1:flat(ES6)
[1, [2, [3, [4]]]].flat(Infinity); // [1, 2, 3, 4]
// 方法2:递归
function flatten(arr) {
return arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
// 方法3:迭代 + 栈
function flatten(arr) {
const result = [];
const stack = [...arr];
while (stack.length) {
const item = stack.pop();
if (Array.isArray(item)) {
stack.push(...item);
} else {
result.unshift(item);
}
}
return result;
}
// 方法4:toString + split(仅数字)
function flattenNums(arr) {
return arr.toString().split(',').map(Number);
}
// 方法5:Generator
function* flattenGenerator(arr) {
for (const item of arr) {
if (Array.isArray(item)) {
yield* flattenGenerator(item);
} else {
yield item;
}
}
}
[...flattenGenerator([1, [2, [3]]])]; // [1, 2, 3]
十、核心操作符与特性
38. new 操作符
执行过程
创建一个新对象
将新对象的 __proto__ 指向构造函数的 prototype
将 this 绑定到新对象,执行构造函数
返回新对象(如果构造函数没有返回对象)
手写实现
javascript
复制代码
function myNew(Constructor, ...args) {
// 1. 创建新对象
const obj = {};
// 2. 设置原型链
obj.__proto__ = Constructor.prototype;
// 3. 绑定 this 并执行
const result = Constructor.apply(obj, args);
// 4. 返回对象
return result instanceof Object ? result : obj;
}
// ES6 实现
function myNew2(Constructor, ...args) {
const obj = Object.create(Constructor.prototype);
const result = Constructor.apply(obj, args);
return result instanceof Object ? result : obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function() {
return `Hi, I'm ${this.name}`;
};
const p = myNew(Person, 'Alice', 25);
console.log(p.name); // "Alice"
console.log(p.sayHi()); // "Hi, I'm Alice"
39. instanceof 原理
原理
instanceof 用于检测构造函数的 prototype 属性是否出现在对象的原型链上。
手写实现
javascript
复制代码
function myInstanceof(obj, Constructor) {
if (typeof Constructor !== 'function') {
throw new TypeError('Right-hand side is not a function');
}
let proto = Object.getPrototypeOf(obj);
const prototype = Constructor.prototype;
while (proto !== null) {
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
return false;
}
// 测试
function Person() {}
const p = new Person();
console.log(myInstanceof(p, Person)); // true
console.log(myInstanceof(p, Object)); // true
console.log(myInstanceof(p, Array)); // false
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof({}, Object)); // true
console.log(myInstanceof(42, Number)); // false(基本类型)
40. 其他常用操作符
Object.create
javascript
复制代码
// 创建以指定对象为原型的对象
let person = { name: 'Alice', sayHi() { return `Hi, ${this.name}`; } };
let student = Object.create(person);
student.age = 20;
console.log(student.name); // "Alice"(继承自 person)
console.log(student.sayHi()); // "Hi, Alice"
// 第二个参数定义属性
let obj = Object.create(null, {
name: { value: 'Alice', writable: true, enumerable: true }
});
Object.assign
javascript
复制代码
// 浅拷贝/合并对象
let target = { a: 1 };
let source = { b: 2, c: 3 };
Object.assign(target, source);
console.log(target); // { a: 1, b: 2, c: 3 }
// 多个源对象
Object.assign({}, obj1, obj2, obj3);
// 数组合并
Object.assign([1, 2], [3, 4]); // [3, 4]
delete 操作符
javascript
复制代码
let obj = { a: 1, b: 2 };
delete obj.a;
console.log(obj); // { b: 2 }
// 只能删除自身属性,不能删除原型链属性
// 不能删除 var 声明的变量
// 不能删除函数声明
in 操作符
javascript
复制代码
let obj = { a: 1 };
console.log('a' in obj); // true
console.log('toString' in obj); // true(原型链)
console.log('b' in obj); // false
// 检查属性存在性(包括原型)
// hasOwnProperty 只检查自身
void 操作符
javascript
复制代码
// void 表达式返回 undefined
void 0; // undefined
void(0); // undefined
void function() {}; // undefined
// 常见用法:避免返回值的链接
<a href="javascript:void(0)">Click</a>
十一、内存管理与垃圾回收
41. 垃圾回收机制
垃圾回收算法
1. 引用计数
javascript
复制代码
// 原理:记录每个值被引用的次数
let a = { name: 'Alice' }; // 对象引用计数:1
let b = a; // 对象引用计数:2
b = null; // 对象引用计数:1
a = null; // 对象引用计数:0 → 垃圾回收
// 问题:循环引用
function problem() {
let a = {};
let b = {};
a.ref = b;
b.ref = a;
}
problem(); // a 和 b 永远不会被回收
2. 标记清除(Mark and Sweep)
javascript
复制代码
// 原理:
// 1. 标记阶段:从根对象开始,标记所有可达对象
// 2. 清除阶段:清除未标记的对象
// 现代浏览器使用此算法
// 优点:解决循环引用问题
// 缺点:内存碎片化
3. 标记整理(Mark and Compact)
javascript
复制代码
// 在标记清除基础上增加整理阶段
// 将存活对象移动到内存一端,解决碎片化
4. 分代收集(Generational Collection)
javascript
复制代码
// V8 引擎使用
// 新生代(Young Generation):短命对象,频繁回收
// 老生代(Old Generation):长命对象,较少回收
V8 垃圾回收
javascript
复制代码
// 新生代:Scavenge 算法
// - 使用复制算法
// - 分为 From 和 To 空间
// - 存活对象从 From 复制到 To
// 老生代:标记清除 + 标记整理
// - 标记阶段识别可达对象
// - 清除阶段回收不可达对象
// - 整理阶段整理内存碎片
42. 内存泄漏
常见内存泄漏场景
1. 全局变量
javascript
复制代码
function fn() {
leakedVar = "I'm global"; // 忘记 var/let/const
}
// 解决:使用严格模式
2. 未清除的定时器
javascript
复制代码
// 泄漏
function start() {
setInterval(function() {
const data = new Array(1000000).fill('data');
console.log(data);
}, 1000);
}
// 解决
let timer = setInterval(/* ... */);
clearInterval(timer); // 手动清除
3. 未移除的事件监听器
javascript
复制代码
// 泄漏
function setup() {
document.getElementById('btn').addEventListener('click', function() {
const largeData = new Array(1000000).fill('data');
// ...
});
}
// 解决
const handler = function() { /* ... */ };
element.addEventListener('click', handler);
element.removeEventListener('click', handler);
4. 闭包持有
javascript
复制代码
// 泄漏
function createLeak() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log('small'); // 持有 largeData
};
}
// 解决:手动释放
function createSafe() {
let largeData = new Array(1000000).fill('data');
return function() {
largeData = null; // 释放引用
console.log('small');
};
}
5. DOM 泄漏
javascript
复制代码
// 泄漏
function removeElement() {
const el = document.getElementById('myElement');
el.remove();
// 如果其他地方仍持有 el 的引用,不会被回收
}
// 解决:确保没有引用
function removeElementSafe() {
const el = document.getElementById('myElement');
el.remove();
el = null; // 明确释放
}
6. 控制台日志
javascript
复制代码
// 大型对象被控制台引用
const largeObj = { /* ... */ };
console.log(largeObj); // 在开发者工具打开时不会被回收
检测方法
javascript
复制代码
// Chrome DevTools Memory 面板
// 1. Heap Snapshot:堆快照
// 2. Allocation Timeline:分配时间线
// 3. Memory:内存使用监控
// performance.memory API(Chrome)
console.log(performance.memory.usedJSHeapSize);
console.log(performance.memory.totalJSHeapSize);
十二、模块化
43. CommonJS vs ES Modules
特性
CommonJS
ES Modules
语法
require() / module.exports
import / export
加载时机
运行时加载
编译时加载
输出
值的拷贝(缓存)
值的引用(只读)
动态加载
支持
import() 支持
this 指向
module 对象
undefined
环境
Node.js
浏览器 / 现代 Node.js
循环依赖
可能返回未完成
正确处理
CommonJS
javascript
复制代码
// math.js
module.exports = {
add: (a, b) => a + b,
subtract: (a, b) => a - b
};
// app.js
const math = require('./math');
console.log(math.add(1, 2)); // 3
// 单个导出
module.exports = function() { /* ... */ };
exports.helper = function() { /* ... */ };
ES Modules
javascript
复制代码
// math.js
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export default function multiply(a, b) { return a * b; }
// app.js
import multiply, { add, PI } from './math.js';
console.log(add(1, 2)); // 3
// 动态导入
const module = await import('./math.js');
// 重命名导入
import { add as sum } from './math.js';
// 命名空间导入
import * as MathUtils from './math.js';
UMD(通用模块定义)
javascript
复制代码
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['dependency'], factory);
} else if (typeof module === 'object' && module.exports) {
// CommonJS
module.exports = factory(require('dependency'));
} else {
// 全局变量
root.myModule = factory(root.dependency);
}
})(this, function(dependency) {
// 模块代码
return {};
});
十三、函数式编程
44. 函数柯里化
定义
柯里化(Currying) 是将接受多个参数的函数转换为接受单一参数的函数,并返回接受余下参数的新函数。
实现
javascript
复制代码
// 基础柯里化
function add(a, b, c) {
return a + b + c;
}
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
}
return function(...moreArgs) {
return curried.apply(this, [...args, ...moreArgs]);
};
};
}
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6
curriedAdd(1, 2)(3); // 6
curriedAdd(1)(2, 3); // 6
// 实用柯里化
function curry2(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn(...args);
}
return (...moreArgs) => curried(...args, ...moreArgs);
};
}
应用场景
javascript
复制代码
// 1. 参数复用
function match(pattern, str) {
return str.match(new RegExp(pattern));
}
const matchEmail = curry(match)(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
matchEmail('test@example.com'); // 匹配
// 2. 延迟执行
function ajax(method, url, data) {
return fetch(url, { method, body: JSON.stringify(data) });
}
const post = curry(ajax)('POST');
const postUser = post('/api/users');
postUser({ name: 'Alice' });
// 3. 函数组合
const compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));
const double = x => x * 2;
const increment = x => x + 1;
const transform = compose(double, increment);
transform(5); // 12
45. 纯函数
定义
纯函数(Pure Function) 是相同输入始终返回相同输出,且没有副作用的函数。
特性
确定性 :相同输入 → 相同输出
无副作用 :不修改外部状态
引用透明 :可用返回值替换函数调用
示例
javascript
复制代码
// 纯函数
function add(a, b) { return a + b; }
function square(x) { return x * x; }
function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); }
// 非纯函数
let count = 0;
function increment() { return ++count; } // 依赖外部状态
function random() { return Math.random(); } // 不确定输出
function saveToDB(data) { db.save(data); } // 副作用
副作用类型
javascript
复制代码
// 1. 修改全局变量
// 2. I/O 操作(网络请求、文件读写)
// 3. DOM 操作
// 4. 修改传入参数
// 5. console.log
// 6. Math.random(), Date.now()
46. 高阶函数
定义
高阶函数(Higher-Order Function) 是接受函数作为参数或返回函数的函数。
示例
javascript
复制代码
// 接受函数作为参数
function forEach(arr, fn) {
for (let i = 0; i < arr.length; i++) {
fn(arr[i], i, arr);
}
}
// 返回函数
function multiplier(factor) {
return function(x) { return x * factor; };
}
// 常见高阶函数
[1, 2, 3].map(x => x * 2); // [2, 4, 6]
[1, 2, 3].filter(x => x > 1); // [2, 3]
[1, 2, 3].reduce((acc, x) => acc + x, 0); // 6
[1, 2, 3].every(x => x > 0); // true
[1, 2, 3].some(x => x > 2); // true
十四、正则表达式
47. 正则表达式基础
创建方式
javascript
复制代码
// 字面量(推荐)
const re1 = /pattern/flags;
// 构造函数
const re2 = new RegExp('pattern', 'flags');
修饰符
修饰符
说明
g
全局匹配
i
忽略大小写
m
多行模式
s
dotAll 模式(. 匹配换行)
u
Unicode 模式
y
粘连模式
元字符
元字符
说明
.
除换行外的任意字符
\d
数字 [0-9]
\D
非数字
\w
单词字符 [a-zA-Z0-9_]
\W
非单词字符
\s
空白字符
\S
非空白字符
^
开头
$
结尾
*
0 或多次
+
1 或多次
?
0 或 1 次
{n}
恰好 n 次
{n,}
至少 n 次
{n,m}
n 到 m 次
分组与捕获
javascript
复制代码
// 捕获分组
const re = /(\d{4})-(\d{2})-(\d{2})/;
const match = re.exec('2024-01-15');
console.log(match[1]); // "2024"(年)
console.log(match[2]); // "01"(月)
// 非捕获分组
const re2 = /(?:\d{3})-(\d{4})/;
// 命名捕获组(ES2018)
const re3 = /(?<year>\d{4})-(?<month>\d{2})/;
const match3 = re3.exec('2024-01');
console.log(match3.groups.year); // "2024"
// 前瞻/后瞻
const re4 = /(?<=\$)\d+/; // 后瞻:美元符号后的数字
const re5 = /\d+(?=%)/; // 前瞻:百分号前的数字
const re6 = /(?<!\$)\d+/; // 负向后瞻
const re7 = /\d+(?!%)/; // 负向前瞻
贪婪与非贪婪
javascript
复制代码
// 贪婪(默认)
'aaa'.match(/a+/); // ["aaa"]
'<div>content</div>'.match(/<.*>/); // ["<div>content</div>"]
// 非贪婪
'aaa'.match(/a+?/); // ["a"]
'<div>content</div>'.match(/<.*?>/); // ["<div>"]
常用方法
javascript
复制代码
const re = /abc/i;
// test - 返回布尔值
re.test('ABC'); // true
// exec - 返回匹配数组
re.exec('ABC'); // ["ABC", index: 0, input: "ABC"]
// 字符串方法
'abc'.match(/abc/); // ["abc"]
'abc'.search(/bc/); // 1(位置)
'abc'.replace(/b/, 'B'); // "aBc"
'abc'.split(/b/); // ["a", "c"]
十五、常用工具函数手写
48. URL 参数解析
javascript
复制代码
// 方法1:简单实现
function parseQueryString(url) {
const params = {};
const queryString = url.split('?')[1] || url;
queryString.split('&').forEach(pair => {
const [key, value] = pair.split('=');
if (key) {
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
}
});
return params;
}
// 方法2:完整实现(支持数组)
function parseQueryString2(url) {
const params = {};
const queryString = url.split('?')[1] || url;
queryString.split('&').forEach(pair => {
const [key, value = ''] = pair.split('=');
const decodedKey = decodeURIComponent(key);
const decodedValue = decodeURIComponent(value);
if (decodedKey.includes('[')) {
// 数组参数:key[]=1&key[]=2
const arrKey = decodedKey.match(/(\w+)\[\]/)[1];
if (!params[arrKey]) params[arrKey] = [];
params[arrKey].push(decodedValue);
} else {
params[decodedKey] = decodedValue;
}
});
return params;
}
// 测试
const url = 'https://example.com?name=John&age=30&city=NYC';
console.log(parseQueryString(url));
// { name: "John", age: "30", city: "NYC" }
49. 深比较
javascript
复制代码
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (typeof a !== typeof b) return false;
// 对象比较
if (typeof a === 'object') {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
return keysA.every(key =>
deepEqual(a[key], b[key]) && keysB.includes(key)
);
}
return false;
}
50. 函数 once
javascript
复制代码
function once(fn) {
let called = false;
let result;
return function(...args) {
if (!called) {
called = true;
result = fn.apply(this, args);
}
return result;
};
}
// 使用
const init = once(function() {
console.log('Initializing...');
return { status: 'ready' };
});
init(); // "Initializing..."
init(); // 无输出
init(); // 无输出
51. 函数记忆(Memoize)
javascript
复制代码
function memoize(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用
const fibonacci = memoize(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
fibonacci(100); // 瞬间完成(缓存结果)
十六、常用内置方法
52. 常用数组方法
方法
说明
是否修改原数组
push()
末尾添加
✅
pop()
末尾删除
✅
shift()
开头删除
✅
unshift()
开头添加
✅
splice()
增删改
✅
sort()
排序
✅
reverse()
反转
✅
fill()
填充
✅
copyWithin()
复制
✅
map()
映射
❌
filter()
过滤
❌
reduce()
归并
❌
forEach()
遍历
❌
find()
查找
❌
findIndex()
查找索引
❌
some()
任一满足
❌
every()
全部满足
❌
includes()
包含
❌
indexOf()
查找索引
❌
slice()
截取
❌
concat()
合并
❌
flat()
扁平化
❌
flatMap()
映射+扁平化
❌
join()
连接字符串
❌
53. 常用字符串方法
方法
说明
charAt(index)
获取指定位置字符
charCodeAt(index)
获取字符 Unicode
concat(...strs)
连接字符串
includes(str)
是否包含
startsWith(str)
是否以...开头
endsWith(str)
是否以...结尾
indexOf(str)
查找位置
lastIndexOf(str)
最后出现位置
match(regex)
正则匹配
replace(regex, str)
替换
search(regex)
正则搜索
slice(start, end)
截取
split(separator)
分割为数组
substr(start, len)
截取(已废弃)
substring(start, end)
截取
toLowerCase()
转小写
toUpperCase()
转大写
trim()
去首尾空白
padStart(len, str)
头部填充
padEnd(len, str)
尾部填充
repeat(n)
重复
54. 常用对象方法
方法
说明
Object.keys(obj)
可枚举属性名数组
Object.values(obj)
可枚举属性值数组
Object.entries(obj)
键值对数组
Object.assign(target, ...sources)
合并对象
Object.create(proto)
创建对象(指定原型)
Object.defineProperty()
定义属性
Object.defineProperties()
定义多个属性
Object.getOwnPropertyDescriptor()
获取属性描述符
Object.getOwnPropertyNames()
所有属性名(不含Symbol)
Object.getOwnPropertySymbols()
Symbol 属性名
Object.freeze(obj)
冻结对象
Object.seal(obj)
密封对象
Object.preventExtensions()
禁止扩展
Object.isFrozen()
是否冻结
Object.isSealed()
是否密封
Object.is(a, b)
严格相等
Object.hasOwn(obj, key)
是否有自身属性(ES2022)
十七、虚拟 DOM
55. 虚拟 DOM 的优缺点
定义
虚拟 DOM(Virtual DOM) 是用 JavaScript 对象表示的 DOM 树结构,是真实 DOM 的轻量级副本。
实现原理
javascript
复制代码
// 虚拟节点
const vNode = {
tag: 'div',
attrs: { id: 'app' },
children: [
{ tag: 'p', attrs: {}, children: ['Hello'] },
{ tag: 'span', attrs: {}, children: ['World'] }
]
};
// 渲染为真实 DOM
function render(vNode) {
if (typeof vNode === 'string') {
return document.createTextNode(vNode);
}
const el = document.createElement(vNode.tag);
Object.keys(vNode.attrs || {}).forEach(key => {
el.setAttribute(key, vNode.attrs[key]);
});
(vNode.children || []).forEach(child => {
el.appendChild(render(child));
});
return el;
}
优点
性能优化 :减少 DOM 操作(批量更新)
跨平台 :可渲染到非浏览器环境(React Native)
函数式编程 :UI = render(state)
简化开发 :声明式编程,自动 Diff
缺点
首次渲染慢 :需要构建虚拟 DOM 树
内存占用 :维护两份 DOM 树
过度优化 :简单场景可能不需要
内存对比计算 :Diff 算法本身有开销
与真实 DOM 的区别
特性
虚拟 DOM
真实 DOM
类型
JavaScript 对象
浏览器 API 对象
操作速度
快(内存操作)
慢(触发重排/重绘)
更新方式
批量更新
立即更新
内存占用
轻量
重量
十八、其他核心面试题
56. let 与 const 作用域差异
特性
let
const
作用域
块级
块级
变量提升
有(暂时性死区)
有(暂时性死区)
重新赋值
✅
❌
必须初始化
❌
✅
重复声明
❌
❌
修改对象属性
✅
✅
javascript
复制代码
// let 可重新赋值
let x = 1;
x = 2; // ✅
// const 不可重新赋值
const y = 1;
y = 2; // TypeError
// const 对象属性可修改
const obj = { a: 1 };
obj.a = 2; // ✅
obj = {}; // TypeError
// 暂时性死区
console.log(z); // ReferenceError
let z = 10;
57. 箭头函数与普通函数的区别
特性
普通函数
箭头函数
this 指向
动态(调用时确定)
静态(定义时确定)
arguments
有
无(用 rest 参数)
new
可以
不可以
prototype
有
无
call/apply/bind
this 可改变
this 不可改变
作为方法
适合
不适合
作为构造器
可以
不可以
58. callee 和 caller
javascript
复制代码
// arguments.callee - 指向当前执行函数
function factorial(n) {
if (n <= 1) return 1;
return n * arguments.callee(n - 1);
}
// 严格模式下不可用
// 替代方案:命名函数表达式
const factorial2 = function fact(n) {
if (n <= 1) return 1;
return n * fact(n - 1);
};
// function.caller - 指向调用当前函数的函数
function outer() {
inner();
}
function inner() {
console.log(inner.caller); // 指向 outer
}
outer();
// 严格模式下不可用
59. cookie、sessionStorage、localStorage 区别
特性
cookie
localStorage
sessionStorage
生命周期
可设置过期时间
永久
会话结束
存储大小
4KB
5-10MB
5-10MB
通信
每次请求携带
不参与
不参与
作用域
同源窗口
同源窗口
同源同标签页
API
document.cookie
localStorage API
sessionStorage API
javascript
复制代码
// localStorage
localStorage.setItem('key', 'value');
localStorage.getItem('key');
localStorage.removeItem('key');
localStorage.clear();
// sessionStorage
sessionStorage.setItem('key', 'value');
sessionStorage.getItem('key');