JavaScript 基础知识全解析:从入门到精通

本篇文章系统整理了 JavaScript 的核心基础知识,包括变量与数据类型、作用域与闭包、原型与原型链、this 指向、执行上下文与执行栈、事件循环、ES6+ 新特性、函数相关、数组方法、对象与深浅拷贝、错误处理、DOM 操作、BOM 操作以及性能优化。每个知识点都配有详细解释和代码示例,适合初学者系统学习,也适合开发者复习查阅。


文章目录

  • 一、变量与数据类型
  • 二、作用域与闭包
  • 三、原型与原型链
    • [3.1 原型基础](#3.1 原型基础)
    • [3.2 原型链](#3.2 原型链)
    • [3.3 继承实现方式](#3.3 继承实现方式)
      • [方式 1:原型链继承](#方式 1:原型链继承)
      • [方式 2:构造函数继承](#方式 2:构造函数继承)
      • [方式 3:组合继承(推荐)](#方式 3:组合继承(推荐))
      • [方式 4:ES6 Class 继承(最推荐)](#方式 4:ES6 Class 继承(最推荐))
  • [四、this 指向](#四、this 指向)
    • [4.1 this 的四种绑定规则](#4.1 this 的四种绑定规则)
      • [规则 1:默认绑定](#规则 1:默认绑定)
      • [规则 2:隐式绑定](#规则 2:隐式绑定)
      • [规则 3:显式绑定](#规则 3:显式绑定)
      • [规则 4:new 绑定](#规则 4:new 绑定)
    • [4.2 优先级](#4.2 优先级)
    • [4.3 箭头函数的 this](#4.3 箭头函数的 this)
  • 五、执行上下文与执行栈
    • [5.1 执行上下文类型](#5.1 执行上下文类型)
      • [1. 全局执行上下文](#1. 全局执行上下文)
      • [2. 函数执行上下文](#2. 函数执行上下文)
      • [3. eval 执行上下文](#3. eval 执行上下文)
    • [5.2 执行上下文的组成](#5.2 执行上下文的组成)
    • [5.3 变量提升](#5.3 变量提升)
    • [5.4 执行栈](#5.4 执行栈)
  • [六、事件循环(Event Loop)](#六、事件循环(Event Loop))
    • [6.1 任务队列类型](#6.1 任务队列类型)
    • [6.2 执行顺序](#6.2 执行顺序)
    • [6.3 经典面试题](#6.3 经典面试题)
  • [七、ES5 核心特性](#七、ES5 核心特性)
  • [八、ES6+ 新特性](#八、ES6+ 新特性)
    • [8.1 解构赋值](#8.1 解构赋值)
    • [8.2 箭头函数](#8.2 箭头函数)
    • [8.3 模板字符串](#8.3 模板字符串)
    • [8.4 扩展运算符](#8.4 扩展运算符)
    • [8.5 Promise](#8.5 Promise)
    • [8.6 async/await](#8.6 async/await)
    • [8.7 Class 类](#8.7 Class 类)
    • [8.8 Module 模块化](#8.8 Module 模块化)
  • 九、函数相关
  • 十、数组方法
    • [10.1 改变原数组的方法](#10.1 改变原数组的方法)
    • [10.2 不改变原数组的方法](#10.2 不改变原数组的方法)
    • [10.3 reduce() 详解](#10.3 reduce() 详解)
  • 十一、对象与深浅拷贝
    • [11.1 浅拷贝](#11.1 浅拷贝)
      • [方法 1:Object.assign()](#方法 1:Object.assign())
      • [方法 2:扩展运算符](#方法 2:扩展运算符)
      • [方法 3:Array.slice()(数组)](#方法 3:Array.slice()(数组))
    • [11.2 深拷贝](#11.2 深拷贝)
      • [方法 1:JSON 序列化(简单场景)](#方法 1:JSON 序列化(简单场景))
      • [方法 2:递归深拷贝(推荐)](#方法 2:递归深拷贝(推荐))
      • [方法 3:使用第三方库](#方法 3:使用第三方库)
  • 十二、错误处理
    • [12.1 try-catch-finally](#12.1 try-catch-finally)
    • [12.2 throw 抛出错误](#12.2 throw 抛出错误)
    • [12.3 常见错误类型](#12.3 常见错误类型)
  • [十三、DOM 操作](#十三、DOM 操作)
    • [13.1 获取元素](#13.1 获取元素)
    • [13.2 操作元素](#13.2 操作元素)
    • [13.3 创建和插入元素](#13.3 创建和插入元素)
    • [13.4 事件监听](#13.4 事件监听)
  • [十四、BOM 操作](#十四、BOM 操作)
    • [14.1 window 对象](#14.1 window 对象)
    • [14.2 location 对象](#14.2 location 对象)
    • [14.3 history 对象](#14.3 history 对象)
    • [14.4 本地存储](#14.4 本地存储)
  • 十五、性能优化
    • [15.1 代码层面](#15.1 代码层面)
      • [1. 减少重排重绘](#1. 减少重排重绘)
      • [2. 使用事件委托](#2. 使用事件委托)
      • [3. 避免内存泄漏](#3. 避免内存泄漏)
      • [4. 使用防抖节流](#4. 使用防抖节流)
    • [15.2 资源加载](#15.2 资源加载)
      • [1. 懒加载](#1. 懒加载)
      • [2. 预加载](#2. 预加载)
    • [15.3 渲染优化](#15.3 渲染优化)
      • [1. 使用 requestAnimationFrame](#1. 使用 requestAnimationFrame)
      • [2. 虚拟 DOM(React)](#2. 虚拟 DOM(React))
      • [3. SSR 服务端渲染](#3. SSR 服务端渲染)

一、变量与数据类型

1.1 变量声明方式

JavaScript 提供了三种声明变量的方式,它们各有特点:

var(传统方式)

javascript 复制代码
var name = '张三';

特点

  • 函数作用域:只在函数内部有效
  • 变量提升:可以在声明前使用(值为 undefined)
  • 可重复声明:同一作用域内可以多次声明
  • ⚠️ 缺点:容易造成变量污染,现代开发中不推荐使用

let(推荐使用)

javascript 复制代码
let age = 25;

特点

  • 块级作用域 :只在 {} 块内部有效
  • 不存在变量提升:必须先声明后使用
  • 不可重复声明:同一作用域内不能重复声明
  • 暂时性死区(TDZ):从块开始到变量声明期间,无法访问该变量

const(声明常量)

javascript 复制代码
const PI = 3.1415926;

特点

  • 块级作用域 :只在 {} 块内部有效
  • 声明时必须初始化const a; 会报错
  • 不可重新赋值:一旦声明,不能改变指向
  • ⚠️ 注意:如果是引用类型(对象、数组),属性可以修改
javascript 复制代码
// const 引用类型的特殊情况
const person = { name: '张三' };
person.name = '李四';  // ✅ 可以修改属性
person = { name: '王五' };  // ❌ 不能重新赋值

1.2 数据类型详解

JavaScript 的数据类型分为两大类:原始类型和引用类型。

原始类型(7种)

原始类型是不可变 的,存储在栈内存中。

类型 说明 示例
String 字符串 'Hello', "World"
Number 数字(整数、浮点数、NaN、Infinity) 100, 3.14, NaN, Infinity
Boolean 布尔值 true, false
undefined 未定义 let a;(a 的值为 undefined)
null 空值 let b = null;
Symbol 唯一标识符(ES6 新增) Symbol('id')
BigInt 大整数(ES2020 新增) 9007199254740991n

关于 undefined 和 null 的区别

  • undefined 表示"变量已声明但未赋值"
  • null 表示"变量已赋值为空",需要显式赋值
javascript 复制代码
let a;           // undefined
let b = null;    // null
console.log(a);  // undefined
console.log(b);  // null

引用类型

引用类型存储在堆内存中,变量中存储的是内存地址(指针)。

类型 说明
Object 对象,包含 Array、Function、Date、RegExp 等
javascript 复制代码
const arr = [1, 2, 3];      // Array 是 Object 的子类型
const date = new Date();     // Date 对象
const reg = /abc/g;         // RegExp 对象
const fn = function() {};   // Function 对象

1.3 类型转换

隐式转换

JavaScript 会在运算过程中自动转换类型。

javascript 复制代码
// + 运算符:数字转字符串
console.log(1 + '2');        // '12'(字符串拼接)
console.log(1 + 2);         // 3(数字相加)

// - * / 运算符:字符串转数字
console.log('10' - '2');    // 8
console.log('10' * '2');    // 20

// == 会进行类型转换,=== 严格比较不转换
console.log(1 == '1');      // true(自动转换)
console.log(1 === '1');     // false(严格比较)
console.log(null == undefined);  // true(特殊情况)
console.log(null === undefined); // false

显式转换

使用内置函数进行类型转换。

javascript 复制代码
// Number():转为数字
console.log(Number('123'));     // 123
console.log(Number('abc'));     // NaN
console.log(Number(''));        // 0

// String():转为字符串
console.log(String(123));       // '123'
console.log(String(true));      // 'true'

// Boolean():转为布尔值
console.log(Boolean(0));        // false
console.log(Boolean(''));       // false
console.log(Boolean(null));     // false
console.log(Boolean(undefined));// false
console.log(Boolean('abc'));    // true

// parseInt():从字符串中解析整数
console.log(parseInt('123abc'));    // 123
console.log(parseInt('abc123'));    // NaN
console.log(parseInt('10.5'));      // 10

// parseFloat():从字符串中解析浮点数
console.log(parseFloat('10.5'));    // 10.5

** falsy 值**:以下值在布尔转换时会转为 false

  • 0-00n
  • ''(空字符串)
  • null
  • undefined
  • NaN

二、作用域与闭包

2.1 作用域类型

作用域决定了变量和函数的可访问范围。

全局作用域

在函数外部声明的变量,整个程序都可以访问。

javascript 复制代码
const globalVar = '全局变量';

function fn() {
  console.log(globalVar); // ✅ 可以访问
}

fn();
console.log(globalVar);   // ✅ 可以访问

函数作用域

在函数内部声明的变量,只能在函数内部访问。

javascript 复制代码
function fn() {
  const localVar = '局部变量';
  console.log(localVar); // ✅ 可以访问
}

fn();
console.log(localVar);   // ❌ 报错:localVar is not defined

块级作用域

letconst 声明的变量只在 {} 块内部有效。

javascript 复制代码
{
  let blockVar = '块级变量';
  const blockConst = '块级常量';
  console.log(blockVar);    // ✅ 可以访问
  console.log(blockConst);  // ✅ 可以访问
}

console.log(blockVar);      // ❌ 报错:blockVar is not defined
console.log(blockConst);    // ❌ 报错:blockConst is not defined
javascript 复制代码
// 经典案例:for 循环中的 var 和 let
// 使用 var
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出:3, 3, 3
}

// 使用 let(推荐)
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 100); // 输出:0, 1, 2
}

2.2 作用域链

当访问一个变量时,JavaScript 会沿着作用域链逐层向上查找,直到找到该变量或到达全局作用域。

javascript 复制代码
let globalVar = '全局变量';

function outer() {
  let outerVar = '外部变量';

  function inner() {
    let innerVar = '内部变量';
    console.log(innerVar);  // ✅ 在当前作用域找到
    console.log(outerVar);  // ✅ 向上查找,在 outer 作用域找到
    console.log(globalVar); // ✅ 继续向上,在全局作用域找到
    console.log(notExist);  // ❌ 找不到,报错
  }

  inner();
}

outer();

2.3 闭包

什么是闭包?

闭包是指函数能够记住并访问其词法作用域,即使函数在其词法作用域之外执行

简单来说:内部函数引用了外部函数的变量,外部函数执行完毕后,变量不会被销毁,依然存在于内存中

javascript 复制代码
function outer() {
  let count = 0;

  function inner() {
    count++;
    console.log(count);
  }

  return inner;
}

const counter = outer();
counter(); // 输出:1
counter(); // 输出:2
counter(); // 输出:3

闭包的原理

  1. outer 函数执行完毕后,count 变量本应被销毁
  2. inner 函数被返回并赋值给 counter
  3. inner 函数引用了 count 变量
  4. 因此 count 变量不会被垃圾回收,一直存在于内存中

闭包的经典应用场景

场景 1:数据私有化(封装)

javascript 复制代码
function createCounter() {
  let count = 0;  // 私有变量,外部无法直接访问

  return {
    increment() {
      count++;
      console.log('增加后:', count);
      return count;
    },
    decrement() {
      count--;
      console.log('减少后:', count);
      return count;
    },
    getCount() {
      return count;
    }
  };
}

const counter = createCounter();
counter.increment(); // 输出:1
counter.increment(); // 输出:2
counter.decrement(); // 输出:1
console.log('当前值:', counter.getCount()); // 输出:1

// counter.count;  // ❌ 无法直接访问 count 变量

场景 2:模块模式

javascript 复制代码
const module = (function() {
  let privateVar = '我是私有变量';

  function privateMethod() {
    console.log('私有方法');
  }

  return {
    publicMethod() {
      console.log('公共方法');
      console.log(privateVar); // 可以访问私有变量
      privateMethod();         // 可以调用私有方法
    }
  };
})();

module.publicMethod();  // ✅ 可以调用
// module.privateMethod(); // ❌ 无法调用

场景 3:函数柯里化(参数复用)

javascript 复制代码
function multiply(a) {
  return function(b) {
    return a * b;
  };
}

const double = multiply(2); // 创建一个函数,用于计算两倍
const triple = multiply(3); // 创建一个函数,用于计算三倍

console.log(double(5));  // 输出:10
console.log(triple(5));  // 输出:15

场景 4:节流防抖

javascript 复制代码
// 防抖:多次触发只执行最后一次
function debounce(fn, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 节流:固定时间间隔执行
function throttle(fn, delay) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= delay) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

闭包的注意事项

优点

  • ✅ 实现数据私有化,避免全局变量污染
  • ✅ 实现模块化开发
  • ✅ 实现函数柯里化、节流防抖等功能

缺点

  • ⚠️ 内存泄漏风险:闭包会导致外部函数的变量一直存在于内存中,不会被销毁
  • ⚠️ 性能影响:大量闭包可能导致内存占用过高

解决方法

  • 及时将闭包引用置为 null
  • 避免不必要的闭包
javascript 复制代码
const counter = createCounter();
counter.increment(); // 使用完毕
counter = null;      // 手动释放,帮助垃圾回收

三、原型与原型链

3.1 原型基础

JavaScript 的继承是基于原型链的,理解原型是掌握 JavaScript 的关键。

核心概念

1. prototype(显式原型)

  • 每个函数都有一个 prototype 属性
  • prototype 是一个对象,用于实现属性和方法的继承
javascript 复制代码
function Person() {}
console.log(Person.prototype); // Person {}

Person.prototype.name = '默认名字';
Person.prototype.sayHello = function() {
  console.log('Hello');
};

console.log(Person.prototype.name); // '默认名字'

2. proto(隐式原型)

  • 每个对象都有一个 __proto__ 属性
  • __proto__ 指向构造函数的 prototype
javascript 复制代码
function Person() {}
const p = new Person();

console.log(p.__proto__ === Person.prototype); // true

3. constructor(构造器)

  • prototype 对象有一个 constructor 属性
  • constructor 指向构造函数本身
javascript 复制代码
function Person() {}
console.log(Person.prototype.constructor === Person); // true

const p = new Person();
console.log(p.constructor === Person); // true

3.2 原型链

原型链是由 __proto__ 连接而成的链式结构,用于查找属性和方法。

原型链示意图

复制代码
p (实例对象)
  └── __proto__ ──> Person.prototype (原型对象)
                       ├── constructor: Person
                       ├── name: '默认名字'
                       ├── sayHello()
                       └── __proto__ ──> Object.prototype
                                             ├── hasOwnProperty()
                                             ├── toString()
                                             ├── valueOf()
                                             └── __proto__ ──> null

核心关系

javascript 复制代码
function Person() {}
const p = new Person();

// 关系 1:实例对象的 __proto__ 指向构造函数的 prototype
console.log(p.__proto__ === Person.prototype); // true

// 关系 2:构造函数的 prototype 的 __proto__ 指向 Object.prototype
console.log(Person.prototype.__proto__ === Object.prototype); // true

// 关系 3:Object.prototype 的 __proto__ 指向 null(原型链顶端)
console.log(Object.prototype.__proto__ === null); // true

// 关系 4:构造函数的 prototype 的 constructor 指向构造函数本身
console.log(Person.prototype.constructor === Person); // true

属性查找过程

当访问对象的属性或方法时,会沿着原型链逐层向上查找:

javascript 复制代码
function Person() {}

Person.prototype.name = '原型上的名字';
Person.prototype.sayHello = function() {
  console.log('Hello');
};

const p = new Person();
p.name = '实例上的名字';

console.log(p.name); // '实例上的名字'(先在实例上找到)
delete p.name;
console.log(p.name); // '原型上的名字'(实例上没有,去原型上找)

查找顺序

  1. 先在对象本身查找
  2. 如果没有,去 __proto__ 指向的原型对象上查找
  3. 如果还没有,继续向上查找,直到 Object.prototype
  4. 如果 Object.prototype 上也没有,返回 undefined

3.3 继承实现方式

JavaScript 有多种继承方式,各有优缺点。

方式 1:原型链继承

javascript 复制代码
function Parent() {
  this.name = 'parent';
  this.hobbies = ['reading', 'coding'];
}

function Child() {}
Child.prototype = new Parent(); // 关键:将 Child 的原型指向 Parent 的实例

const child1 = new Child();
const child2 = new Child();

child1.hobbies.push('gaming');

console.log(child1.hobbies); // ['reading', 'coding', 'gaming']
console.log(child2.hobbies); // ['reading', 'coding', 'gaming'] ⚠️ 引用类型被共享

缺点

  • ❌ 引用类型的属性会被所有实例共享
  • ❌ 创建子类实例时,无法向父类构造函数传参

方式 2:构造函数继承

javascript 复制代码
function Parent(name) {
  this.name = name;
  this.hobbies = ['reading', 'coding'];
}

function Child(name) {
  Parent.call(this, name); // 关键:调用父类构造函数,绑定 this
}

const child1 = new Child('child1');
const child2 = new Child('child2');

child1.hobbies.push('gaming');

console.log(child1.name);    // 'child1'
console.log(child2.name);    // 'child2'
console.log(child1.hobbies); // ['reading', 'coding', 'gaming']
console.log(child2.hobbies); // ['reading', 'coding'] ✅ 引用类型独立

缺点

  • ❌ 无法继承父类原型上的方法
javascript 复制代码
Parent.prototype.sayHello = function() {
  console.log('Hello');
};

const child = new Child('child');
child.sayHello(); // ❌ 报错:child.sayHello is not a function

方式 3:组合继承(推荐)

结合了原型链继承和构造函数继承的优点。

javascript 复制代码
function Parent(name) {
  this.name = name;
  this.hobbies = ['reading', 'coding'];
}

Parent.prototype.sayHello = function() {
  console.log('Hello, I am', this.name);
};

function Child(name, age) {
  Parent.call(this, name); // 继承实例属性
  this.age = age;
}

Child.prototype = new Parent(); // 继承原型方法
Child.prototype.constructor = Child; // 修正 constructor 指向

const child = new Child('张三', 18);
child.hobbies.push('gaming');
child.sayHello(); // 'Hello, I am 张三' ✅ 可以调用父类方法
console.log(child.hobbies); // ['reading', 'coding', 'gaming']

优点

  • ✅ 可以继承实例属性
  • ✅ 可以继承原型方法
  • ✅ 向父类构造函数传参

缺点

  • ⚠️ 父类构造函数被调用两次(创建子类原型时、创建子类实例时)

方式 4:ES6 Class 继承(最推荐)

javascript 复制代码
class Parent {
  constructor(name) {
    this.name = name;
    this.hobbies = ['reading', 'coding'];
  }

  sayHello() {
    console.log('Hello, I am', this.name);
  }
}

class Child extends Parent {
  constructor(name, age) {
    super(name); // 必须调用 super()
    this.age = age;
  }

  sayAge() {
    console.log('I am', this.age, 'years old');
  }
}

const child = new Child('张三', 18);
child.hobbies.push('gaming');
child.sayHello(); // 'Hello, I am 张三'
child.sayAge();   // 'I am 18 years old'
console.log(child.hobbies); // ['reading', 'coding', 'gaming']

优点

  • ✅ 语法简洁,易于理解
  • ✅ 不会调用两次父类构造函数
  • ✅ 支持静态方法继承

四、this 指向

this 是 JavaScript 中最让人困惑的概念之一,但掌握它的规律后,其实非常清晰。

4.1 this 的四种绑定规则

规则 1:默认绑定

独立函数调用时,this 指向全局对象(非严格模式下)或 undefined(严格模式下)。

javascript 复制代码
function fn() {
  console.log(this);
}

fn();  // 非严格模式:Window 对象
       // 严格模式:undefined

规则 2:隐式绑定

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

javascript 复制代码
const obj = {
  name: '张三',
  fn() {
    console.log(this.name);
  }
};

obj.fn(); // '张三'(this 指向 obj)

隐式丢失 :当方法被赋值给变量或作为参数传递时,this 会丢失。

javascript 复制代码
const obj = {
  name: '张三',
  fn() {
    console.log(this.name);
  }
};

const fn = obj.fn;
fn(); // undefined(this 不再指向 obj)

// 经典案例
const button = document.querySelector('button');
button.addEventListener('click', obj.fn);
// 点击时输出:undefined(this 不指向 obj)

规则 3:显式绑定

使用 call()apply()bind() 方法改变 this 指向。

javascript 复制代码
const obj = { name: '张三' };

function fn(greeting) {
  console.log(greeting + ',', this.name);
}

// call():逐个传递参数
fn.call(obj, '你好'); // '你好, 张三'

// apply():通过数组传递参数
fn.apply(obj, ['你好']); // '你好, 张三'

// bind():返回新函数,不立即执行
const boundFn = fn.bind(obj, '你好');
boundFn(); // '你好, 张三'

区别

  • call()apply() 立即执行
  • bind() 返回新函数,稍后执行
  • call() 逐个传参,apply() 数组传参

规则 4:new 绑定

使用 new 调用构造函数时,this 指向新创建的对象。

javascript 复制代码
function Person(name) {
  this.name = name;
}

const p = new Person('张三');
console.log(p.name); // '张三'(this 指向新创建的对象 p)

4.2 优先级

new > 显式绑定 > 隐式绑定 > 默认绑定

javascript 复制代码
// 优先级示例
function fn() {
  console.log(this.name);
}

const obj1 = { name: 'obj1' };
const obj2 = { name: 'obj2' };

// 隐式绑定
obj1.fn = fn;
obj1.fn(); // 'obj1'

// 显式绑定 > 隐式绑定
obj1.fn.call(obj2); // 'obj2'

// new 绑定 > 显式绑定
const boundFn = fn.bind(obj2);
new boundFn(); // undefined(this 指向新创建的对象,不是 obj2)

4.3 箭头函数的 this

箭头函数没有自己的 this ,它的 this 继承自外层作用域,且无法通过 callapplybind 改变

javascript 复制代码
const obj = {
  name: '张三',
  fn() {
    // 普通函数的 this
    setTimeout(function() {
      console.log(this.name); // undefined(this 指向全局对象)
    }, 100);

    // 箭头函数的 this
    setTimeout(() => {
      console.log(this.name); // '张三'(this 继承自外层 fn 的 this)
    }, 100);
  }
};

obj.fn();

箭头函数适用场景

  • ✅ 回调函数中需要保持 this 指向
  • ✅ 事件监听器中需要保持 this 指向
javascript 复制代码
class Button {
  constructor() {
    this.count = 0;
    this.button = document.querySelector('button');

    // 普通函数:this 会丢失
    // this.button.addEventListener('click', this.handleClick);

    // 箭头函数:this 保持指向 Button 实例
    this.button.addEventListener('click', () => this.handleClick());
  }

  handleClick() {
    this.count++;
    console.log('点击次数:', this.count);
  }
}

五、执行上下文与执行栈

5.1 执行上下文类型

执行上下文是 JavaScript 代码执行时的环境,分为三种类型:

1. 全局执行上下文

  • 程序启动时创建
  • 程序结束时销毁
  • 只有一个
javascript 复制代码
// 整个文件运行时处于全局执行上下文
const globalVar = '全局变量';

function fn() {
  // 函数执行时创建函数执行上下文
  const localVar = '局部变量';
}

2. 函数执行上下文

  • 函数调用时创建
  • 函数执行完毕后销毁
  • 可以有多个

3. eval 执行上下文

  • 不推荐使用
  • eval() 函数执行时创建

5.2 执行上下文的组成

每个执行上下文包含三个部分:

javascript 复制代码
ExecutionContext = {
  ThisBinding: <this value>,           // this 指向
  VariableEnvironment: { ... },        // 变量环境(var、函数声明)
  LexicalEnvironment: { ... }          // 词法环境(let、const、块级作用域)
}

变量环境 vs 词法环境

  • 变量环境 :存储 var 声明的变量和函数声明
  • 词法环境 :存储 letconst 声明的变量和块级作用域
javascript 复制代码
function fn() {
  var a = 1;           // 存储在变量环境
  let b = 2;           // 存储在词法环境
  const c = 3;         // 存储在词法环境

  {
    let d = 4;         // 存储在块级词法环境
    console.log(d);    // 4
  }
  // console.log(d);    // 报错:d is not defined
}

5.3 变量提升

JavaScript 在代码执行前,会先将变量和函数声明提升到当前作用域顶部。

var 的变量提升

javascript 复制代码
console.log(a); // undefined(变量声明提升,但未赋值)
var a = 1;
console.log(a); // 1

// 等价于
var a;
console.log(a); // undefined
a = 1;
console.log(a); // 1

let 和 const 不存在变量提升

javascript 复制代码
console.log(b); // ❌ 报错:Cannot access 'b' before initialization(暂时性死区)
let b = 2;

函数声明提升

javascript 复制代码
console.log(fn1); // [Function: fn1]
fn1(); // 'fn1'

// 函数表达式不会提升
console.log(fn2); // undefined
// fn2(); // ❌ 报错:fn2 is not a function

function fn1() {
  console.log('fn1');
}

const fn2 = function() {
  console.log('fn2');
};

5.4 执行栈

执行栈(调用栈)是用于管理函数调用的 LIFO(后进先出)结构。

javascript 复制代码
function fn1() {
  console.log('fn1 开始');
  fn2();
  console.log('fn1 结束');
}

function fn2() {
  console.log('fn2 开始');
  fn3();
  console.log('fn2 结束');
}

function fn3() {
  console.log('fn3');
}

fn1();

// 执行过程:
// 1. fn1 入栈,输出 'fn1 开始'
// 2. fn2 入栈,输出 'fn2 开始'
// 3. fn3 入栈,输出 'fn3'
// 4. fn3 出栈
// 5. 输出 'fn2 结束',fn2 出栈
// 6. 输出 'fn1 结束',fn1 出栈

// 最终输出:
// fn1 开始
// fn2 开始
// fn3
// fn2 结束
// fn1 结束

栈溢出:递归调用过深时,栈空间不足。

javascript 复制代码
function recursive() {
  recursive();
}

recursive(); // RangeError: Maximum call stack size exceeded

六、事件循环(Event Loop)

事件循环是 JavaScript 异步编程的核心机制。

6.1 任务队列类型

JavaScript 的任务分为两类:

宏任务

  • script(整体代码)
  • setTimeout、setInterval
  • I/O 操作
  • UI 渲染

微任务

  • Promise.then、Promise.catch、Promise.finally
  • MutationObserver(监听 DOM 变化)
  • process.nextTick(Node.js 环境)

6.2 执行顺序

javascript 复制代码
console.log('1. 同步代码');

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

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

console.log('4. 同步代码');

// 输出顺序:
// 1. 同步代码
// 4. 同步代码
// 3. Promise.then
// 2. setTimeout

执行规则

  1. 执行同步代码
  2. 同步代码执行完毕,栈清空
  3. 执行所有微任务(微任务队列清空)
  4. 执行一个宏任务
  5. 重复步骤 3-4

6.3 经典面试题

javascript 复制代码
setTimeout(() => console.log('1. setTimeout 1'), 0);

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

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

console.log('5. 同步代码');

// 输出顺序:
// 5. 同步代码
// 2. Promise 1
// 3. Promise 2(微任务执行过程中产生的微任务,会在当前轮次执行)
// 1. setTimeout 1
// 4. setTimeout 2
javascript 复制代码
setTimeout(() => console.log('1. setTimeout'), 0);

new Promise(resolve => {
  console.log('2. Promise 构造函数');
  resolve();
}).then(() => {
  console.log('3. Promise.then 1');
  Promise.resolve().then(() => console.log('4. Promise.then 2'));
});

console.log('5. 同步代码');

// 输出顺序:
// 2. Promise 构造函数(同步执行)
// 5. 同步代码
// 3. Promise.then 1
// 4. Promise.then 2
// 1. setTimeout

七、ES5 核心特性

ES5(ECMAScript 5)发布于 2009 年,是 JavaScript 的一个重要版本,引入了许多核心特性,为现代 JavaScript 开发奠定了基础。

7.1 严格模式(Strict Mode)

严格模式是 ES5 引入的一种运行模式,它使 JavaScript 在更严格的条件下运行。

开启严格模式

在脚本或函数开头添加 'use strict'; 即可开启严格模式。

javascript 复制代码
// 全局严格模式
'use strict';

function fn() {
  // 函数严格模式
  'use strict';
  // ...
}

严格模式的主要限制

限制 说明 示例
禁止使用未声明的变量 必须先声明后使用 x = 1; ❌ 报错
禁止删除变量或函数 不能删除变量、函数、函数参数 delete x; ❌ 报错
禁止重名参数 函数参数不能重名 function fn(a, a) {} ❌ 报错
禁止八进制字面量 0 开头的八进制语法被禁止 var x = 012; ❌ 报错
禁止 with 语句 with 语句被完全禁止 with(obj) {} ❌ 报错
eval 更严格 eval 不会创建全局变量 eval('var x = 1'); x 不会被创建
this 默认为 undefined 独立函数调用时 this 为 undefined function fn() { console.log(this); } fn(); // undefined
保留字 不能使用保留字作为变量名 var private = 1; ❌ 可能报错
javascript 复制代码
'use strict';

// ❌ 未声明的变量
x = 1; // ReferenceError: x is not defined

// ❌ 删除变量
var y = 1;
delete y; // SyntaxError: Delete of an unqualified identifier

// ❌ 重名参数
function fn(a, a) {} // SyntaxError: Duplicate parameter name

// ❌ 八进制字面量
var octal = 012; // SyntaxError: Octal literals are not allowed

// ❌ with 语句
var obj = { a: 1 };
with (obj) { a = 2; } // SyntaxError: Strict mode code may not include a with statement

// ✅ this 默认为 undefined
function fn() {
  console.log(this); // undefined
}
fn();

// ✅ eval 更严格
eval('var z = 1;');
console.log(z); // ReferenceError: z is not defined

7.2 JSON 方法

ES5 引入了 JSON 对象,用于 JSON 数据的解析和序列化。

JSON.stringify()

将 JavaScript 对象或值转换为 JSON 字符串。

javascript 复制代码
const obj = {
  name: '张三',
  age: 18,
  hobbies: ['reading', 'coding']
};

// 基本用法
const jsonStr = JSON.stringify(obj);
console.log(jsonStr);
// '{"name":"张三","age":18,"hobbies":["reading","coding"]}'

// 格式化输出(第二个参数:缩进空格数)
const formatted = JSON.stringify(obj, null, 2);
console.log(formatted);
// {
//   "name": "张三",
//   "age": 18,
//   "hobbies": [
//     "reading",
//     "coding"
//   ]
// }

// 过滤属性(第二个参数:数组或函数)
const filtered = JSON.stringify(obj, ['name', 'age']);
console.log(filtered); // '{"name":"张三","age":18}'

// 使用函数过滤
const filtered2 = JSON.stringify(obj, (key, value) => {
  if (key === 'hobbies') {
    return undefined; // 过滤掉 hobbies
  }
  return value;
});
console.log(filtered2); // '{"name":"张三","age":18}'

JSON.parse()

将 JSON 字符串解析为 JavaScript 对象。

javascript 复制代码
const jsonStr = '{"name":"张三","age":18}';

// 基本用法
const obj = JSON.parse(jsonStr);
console.log(obj); // { name: '张三', age: 18 }

// 使用 reviver 函数转换值
const obj2 = JSON.parse(jsonStr, (key, value) => {
  if (key === 'age') {
    return value + 1; // 年龄加 1
  }
  return value;
});
console.log(obj2); // { name: '张三', age: 19 }

7.3 数组新增方法

ES5 为数组添加了许多实用方法。

Array.isArray()

判断一个值是否为数组。

javascript 复制代码
Array.isArray([1, 2, 3]); // true
Array.isArray('hello'); // false
Array.isArray(new Array()); // true
Array.isArray({}); // false

// 更好的替代方案(ES5 之前)
function isArray(obj) {
  return Object.prototype.toString.call(obj) === '[object Array]';
}

Array.prototype.forEach()

遍历数组,对每个元素执行回调函数。

javascript 复制代码
const arr = [1, 2, 3];

arr.forEach(function(item, index, array) {
  console.log(item, index, array);
});
// 输出:
// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]

// 注意:forEach 无法 break 或 return
arr.forEach(function(item) {
  if (item === 2) {
    return; // 只跳过当前元素,不是跳出循环
  }
  console.log(item);
});
// 输出:1, 3

Array.prototype.map()

创建一个新数组,包含回调函数的返回值。

javascript 复制代码
const arr = [1, 2, 3];

const doubled = arr.map(function(item, index, array) {
  return item * 2;
});

console.log(doubled); // [2, 4, 6]
console.log(arr); // [1, 2, 3](原数组不变)

Array.prototype.filter()

创建一个新数组,包含通过测试的元素。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

const evens = arr.filter(function(item, index, array) {
  return item % 2 === 0;
});

console.log(evens); // [2, 4]

Array.prototype.reduce()

归约数组,返回一个累加值。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

// 求和
const sum = arr.reduce(function(accumulator, currentValue, index, array) {
  return accumulator + currentValue;
}, 0);

console.log(sum); // 15

// 求最大值
const max = arr.reduce(function(accumulator, currentValue) {
  return Math.max(accumulator, currentValue);
}, -Infinity);

console.log(max); // 5

Array.prototype.reduceRight()

从右向左归约数组。

javascript 复制代码
const arr = ['a', 'b', 'c'];

const result = arr.reduceRight(function(accumulator, currentValue) {
  return accumulator + currentValue;
}, '');

console.log(result); // 'cba'

Array.prototype.every()

测试数组中的所有元素是否都通过测试。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

const allPositive = arr.every(function(item) {
  return item > 0;
});

console.log(allPositive); // true

const allGreaterThan3 = arr.every(function(item) {
  return item > 3;
});

console.log(allGreaterThan3); // false

Array.prototype.some()

测试数组中是否至少有一个元素通过测试。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

const hasEven = arr.some(function(item) {
  return item % 2 === 0;
});

console.log(hasEven); // true

Array.prototype.indexOf()

返回指定元素的首次出现索引,不存在则返回 -1。

javascript 复制代码
const arr = [1, 2, 3, 2, 1];

console.log(arr.indexOf(2)); // 1
console.log(arr.indexOf(4)); // -1
console.log(arr.indexOf(2, 2)); // 3(从索引 2 开始查找)

Array.prototype.lastIndexOf()

返回指定元素的最后一次出现索引。

javascript 复制代码
const arr = [1, 2, 3, 2, 1];

console.log(arr.lastIndexOf(2)); // 3
console.log(arr.lastIndexOf(4)); // -1

7.4 Object 新增方法

Object.create()

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

javascript 复制代码
// 创建一个原型为 null 的对象
const obj1 = Object.create(null);
console.log(obj1); // {}(没有原型)

// 创建一个原型为 Person.prototype 的对象
const Person = {
  greet: function() {
    console.log('Hello, I am', this.name);
  }
};

const person = Object.create(Person);
person.name = '张三';
person.greet(); // 'Hello, I am 张三'

// 使用第二个参数定义属性
const obj2 = Object.create(Person.prototype, {
  name: {
    value: '李四',
    writable: true,
    enumerable: true,
    configurable: true
  },
  age: {
    value: 18,
    writable: true
  }
});

console.log(obj2.name); // '李四'
console.log(obj2.age); // 18

Object.defineProperty()

直接在对象上定义一个新属性,或修改现有属性。

javascript 复制代码
const obj = {};

// 定义属性
Object.defineProperty(obj, 'name', {
  value: '张三',
  writable: true,      // 可写
  enumerable: true,    // 可枚举
  configurable: true   // 可配置
});

console.log(obj.name); // '张三'

// 使用 getter 和 setter
let ageValue = 18;

Object.defineProperty(obj, 'age', {
  get: function() {
    console.log('获取 age');
    return ageValue;
  },
  set: function(value) {
    console.log('设置 age 为', value);
    ageValue = value;
  },
  enumerable: true,
  configurable: true
});

obj.age = 20; // 输出:设置 age 为 20
console.log(obj.age); // 输出:获取 age 20

// 定义只读属性
Object.defineProperty(obj, 'PI', {
  value: 3.1415926,
  writable: false
});

obj.PI = 3.14; // 严格模式下会报错
console.log(obj.PI); // 3.1415926

Object.defineProperties()

一次定义多个属性。

javascript 复制代码
const obj = {};

Object.defineProperties(obj, {
  name: {
    value: '张三',
    writable: true
  },
  age: {
    value: 18,
    writable: true
  },
  city: {
    value: '北京',
    enumerable: false // 不可枚举
  }
});

console.log(obj.name); // '张三'
console.log(obj.age); // 18
console.log(obj.city); // '北京'

for (const key in obj) {
  console.log(key); // name, age(city 不可枚举)
}

Object.getOwnPropertyNames()

返回对象的所有自身属性(包括不可枚举属性)。

javascript 复制代码
const obj = { a: 1, b: 2 };
Object.defineProperty(obj, 'c', {
  value: 3,
  enumerable: false
});

console.log(Object.keys(obj)); // ['a', 'b'](只返回可枚举属性)
console.log(Object.getOwnPropertyNames(obj)); // ['a', 'b', 'c'](包含不可枚举属性)

Object.getPrototypeOf()

返回对象的原型。

javascript 复制代码
const obj = {};
const prototype = Object.getPrototypeOf(obj);

console.log(prototype === Object.prototype); // true
console.log(prototype === Object.getPrototypeOf({})); // true

Object.setPrototypeOf()

设置对象的原型(不推荐使用,可能影响性能)。

javascript 复制代码
const obj = {};
const proto = { greet: function() { console.log('Hello'); } };

Object.setPrototypeOf(obj, proto);
obj.greet(); // 'Hello'

Object.keys()

返回对象的所有可枚举属性。

javascript 复制代码
const obj = { name: '张三', age: 18 };

console.log(Object.keys(obj)); // ['name', 'age']

// 结合数组方法使用
const keys = Object.keys(obj);
keys.forEach(function(key) {
  console.log(key, obj[key]);
});
// name 张三
// age 18

Object.freeze()

冻结对象,使其不可修改。

javascript 复制代码
const obj = { name: '张三', age: 18 };

Object.freeze(obj);

obj.name = '李四'; // 严格模式下报错
delete obj.age; // 严格模式下报错
obj.city = '北京'; // 严格模式下报错

console.log(obj); // { name: '张三', age: 18 }

// 检查是否冻结
console.log(Object.isFrozen(obj)); // true

Object.seal()

密封对象,防止添加或删除属性,但可以修改现有属性。

javascript 复制代码
const obj = { name: '张三', age: 18 };

Object.seal(obj);

obj.name = '李四'; // ✅ 可以修改
delete obj.age; // ❌ 不能删除
obj.city = '北京'; // ❌ 不能添加

console.log(obj); // { name: '李四', age: 18 }

// 检查是否密封
console.log(Object.isSealed(obj)); // true

Object.preventExtensions()

防止向对象添加新属性。

javascript 复制代码
const obj = { name: '张三' };

Object.preventExtensions(obj);

obj.name = '李四'; // ✅ 可以修改
delete obj.name; // ✅ 可以删除
obj.age = 18; // ❌ 不能添加

console.log(obj); // { name: '李四' }

// 检查是否可扩展
console.log(Object.isExtensible(obj)); // false

Object.getOwnPropertyDescriptor()

返回指定属性的属性描述符。

javascript 复制代码
const obj = { name: '张三' };

const descriptor = Object.getOwnPropertyDescriptor(obj, 'name');
console.log(descriptor);
// {
//   value: '张三',
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Object.getOwnPropertyDescriptors()

返回对象所有自身属性的属性描述符。

javascript 复制代码
const obj = { name: '张三', age: 18 };

const descriptors = Object.getOwnPropertyDescriptors(obj);
console.log(descriptors);
// {
//   name: {
//     value: '张三',
//     writable: true,
//     enumerable: true,
//     configurable: true
//   },
//   age: { ... }
// }

7.5 Function.prototype.bind()

创建一个新函数,绑定的 this 指向指定对象。

javascript 复制代码
const obj = { name: '张三' };

function fn(greeting) {
  console.log(greeting + ',', this.name);
}

// 创建绑定函数
const boundFn = fn.bind(obj);
boundFn('你好'); // '你好, 张三'

// 预设参数(部分应用)
const greetHello = fn.bind(obj, '你好');
greetHello(); // '你好, 张三'

// 多次 bind 只有第一次有效
const obj2 = { name: '李四' };
const boundFn2 = fn.bind(obj).bind(obj2);
boundFn2('你好'); // '你好, 张三'(仍然指向 obj)

7.6 String.prototype.trim()

去除字符串两端的空白字符。

javascript 复制代码
const str = '  Hello, World!  ';

console.log(str.trim()); // 'Hello, World!'
console.log(str.trimLeft()); // 'Hello, World!  '(非标准)
console.log(str.trimRight()); // '  Hello, World!'(非标准)

7.7 Date.prototype.toISOString()

返回 ISO 格式的日期字符串。

javascript 复制代码
const date = new Date();

console.log(date.toISOString()); // '2024-01-01T00:00:00.000Z'

7.8 Function.prototype.bind() 的详细说明

bind() 方法创建一个新的函数,当这个新函数被调用时,它的 this 值被永久绑定到 bind() 的第一个参数。

javascript 复制代码
const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // undefined(this 指向全局对象)

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX()); // 42(this 指向 module)

7.9 ES5 的最佳实践

1. 总是使用严格模式

javascript 复制代码
'use strict';

// ... 你的代码

2. 使用 Array.isArray() 检查数组

javascript 复制代码
if (Array.isArray(arr)) {
  // 处理数组
}

3. 使用 forEach/map/filter 等方法代替 for 循环

javascript 复制代码
// ❌ 传统的 for 循环
for (var i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}

// ✅ ES5 的 forEach
arr.forEach(function(item) {
  console.log(item);
});

4. 使用 Object.keys() 遍历对象属性

javascript 复制代码
// ✅ 遍历对象
Object.keys(obj).forEach(function(key) {
  console.log(key, obj[key]);
});

5. 使用 Object.create() 实现原型继承

javascript 复制代码
function Parent(name) {
  this.name = name;
}

Parent.prototype.greet = function() {
  console.log('Hello, I am', this.name);
};

function Child(name, age) {
  Parent.call(this, name);
  this.age = age;
}

Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;

const child = new Child('张三', 18);
child.greet(); // 'Hello, I am 张三'

八、ES6+ 新特性

8.1 解构赋值

数组解构

javascript 复制代码
const arr = [1, 2, 3];

const [a, b, c] = arr;
console.log(a, b, c); // 1 2 3

// 跳过元素
const [x, , z] = arr;
console.log(x, z); // 1 3

// 设置默认值
const [m, n, o = 10] = [1, 2];
console.log(m, n, o); // 1 2 10

// 剩余元素
const [first, ...rest] = arr;
console.log(first, rest); // 1 [2, 3]

对象解构

javascript 复制代码
const obj = { name: '张三', age: 18, city: '北京' };

const { name, age } = obj;
console.log(name, age); // 张三 18

// 重命名
const { name: userName, age: userAge } = obj;
console.log(userName, userAge); // 张三 18

// 设置默认值
const { name, gender = '男' } = obj;
console.log(name, gender); // 张三 男

8.2 箭头函数

javascript 复制代码
// 基本语法
const add = (a, b) => a + b;

// 多行代码需要用 {}
const multiply = (a, b) => {
  const result = a * b;
  return result;
};

// 单个参数可以省略 ()
const square = x => x * x;

// 无参数需要用 ()
const sayHello = () => console.log('Hello');

// 返回对象需要用 ()
const getUser = name => ({ name, age: 18 });
console.log(getUser('张三')); // { name: '张三', age: 18 }

8.3 模板字符串

javascript 复制代码
const name = '张三';
const age = 18;

// 使用 ` ` 和 ${}
const message = `我叫${name},今年${age}岁`;

// 支持多行
const html = `
  <div>
    <h1>${name}</h1>
    <p>年龄:${age}</p>
  </div>
`;

8.4 扩展运算符

javascript 复制代码
// 数组
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const arr3 = [...arr1, ...arr2]; // [1, 2, 3, 4, 5, 6]

// 复制数组(浅拷贝)
const arr4 = [...arr1];

// 转换伪数组为数组
const nodeList = document.querySelectorAll('div');
const arr5 = [...nodeList];

// 对象
const obj1 = { name: '张三', age: 18 };
const obj2 = { city: '北京' };

// 合并对象
const obj3 = { ...obj1, ...obj2 }; // { name: '张三', age: 18, city: '北京' }

// 复制对象(浅拷贝)
const obj4 = { ...obj1 };

8.5 Promise

Promise 是处理异步操作的对象,有三种状态:pending(进行中)、fulfilled(已成功)、rejected(已失败)。

javascript 复制代码
// 创建 Promise
const promise = new Promise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    const success = Math.random() > 0.5;

    if (success) {
      resolve('操作成功');
    } else {
      reject('操作失败');
    }
  }, 1000);
});

// 使用 Promise
promise
  .then(result => {
    console.log('成功:', result);
    return result + ',继续处理';
  })
  .then(result => {
    console.log('链式调用:', result);
  })
  .catch(error => {
    console.error('失败:', error);
  })
  .finally(() => {
    console.log('无论成功失败都会执行');
  });

Promise 静态方法

javascript 复制代码
// Promise.all:所有 Promise 都成功才成功
Promise.all([
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.resolve(3)
]).then(results => {
  console.log(results); // [1, 2, 3]
});

// Promise.race:哪个先完成就返回哪个
Promise.race([
  new Promise(resolve => setTimeout(() => resolve(1), 100)),
  new Promise(resolve => setTimeout(() => resolve(2), 200))
]).then(result => {
  console.log(result); // 1
});

// Promise.allSettled:返回所有 Promise 的结果(无论成功失败)
Promise.allSettled([
  Promise.resolve(1),
  Promise.reject(2),
  Promise.resolve(3)
]).then(results => {
  console.log(results);
  // [
  //   { status: 'fulfilled', value: 1 },
  //   { status: 'rejected', reason: 2 },
  //   { status: 'fulfilled', value: 3 }
  // ]
});

8.6 async/await

async/await 是 Promise 的语法糖,让异步代码看起来像同步代码。

javascript 复制代码
// 定义异步函数
async function fetchData() {
  try {
    const result = await promise; // 等待 Promise 完成
    console.log(result);

    // 可以 await 多次
    const data1 = await fetch('/api/user').then(res => res.json());
    const data2 = await fetch(`/api/user/${data1.id}`).then(res => res.json());

    return data2;
  } catch (error) {
    console.error(error);
    throw error; // 可以选择重新抛出错误
  }
}

// 调用异步函数
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

注意

  • async 函数返回一个 Promise
  • await 只能在 async 函数中使用
  • await 后面必须是一个 Promise 或 thenable 对象

8.7 Class 类

javascript 复制代码
class Person {
  // 静态属性
  static species = '人类';

  // 私有属性(# 开头)
  #privateVar = '私有变量';

  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 实例方法
  sayHello() {
    console.log('Hello, I am', this.name);
    console.log(this.#privateVar); // 可以访问私有属性
  }

  // Getter
  get info() {
    return `${this.name} is ${this.age} years old`;
  }

  // Setter
  set age(newAge) {
    if (newAge < 0) {
      throw new Error('年龄不能为负数');
    }
    this._age = newAge;
  }

  // 静态方法
  static getSpecies() {
    return this.species;
  }
}

const person = new Person('张三', 18);
person.sayHello(); // Hello, I am 张三
console.log(person.info); // 张三 is 18 years old
console.log(Person.getSpecies()); // 人类
// console.log(person.#privateVar); // ❌ 报错:无法访问私有属性

8.8 Module 模块化

javascript 复制代码
// 导出(export)
// 文件:module.js

// 命名导出
export const name = '张三';
export const age = 18;
export function sayHello() {
  console.log('Hello');
}
export class Person {}

// 默认导出(一个模块只能有一个默认导出)
export default function() {
  console.log('默认导出');
}

// 导入(import)
// 文件:main.js

// 导入命名导出
import { name, age, sayHello } from './module.js';

// 导入默认导出
import myFunc from './module.js';

// 导入所有
import * as module from './module.js';

// 导入并重命名
import { name as userName } from './module.js';

九、函数相关

9.1 函数声明 vs 函数表达式

函数声明

javascript 复制代码
function fn() {
  console.log('函数声明');
}
fn();
  • ✅ 会提升,可以在声明前调用
  • ✅ 有 name 属性

函数表达式

javascript 复制代码
const fn = function() {
  console.log('函数表达式');
};
fn();
  • ❌ 不会提升,必须先声明后调用
  • ✅ 匿名函数,没有 name 属性(除非命名函数表达式)

命名函数表达式

javascript 复制代码
const fn = function myFn() {
  console.log('命名函数表达式');
};
console.log(fn.name); // 'myFn'

9.2 高阶函数

高阶函数是接收函数作为参数,或返回函数的函数。

javascript 复制代码
// 接收函数作为参数
function map(arr, fn) {
  const result = [];
  for (const item of arr) {
    result.push(fn(item));
  }
  return result;
}

const doubled = map([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]

// 返回函数
function createMultiplier(multiplier) {
  return function(num) {
    return num * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

9.3 柯里化

柯里化是将多参数函数转换为单参数函数序列的技术。

javascript 复制代码
// 传统函数
function add(a, b, c) {
  return a + b + c;
}

// 柯里化函数
function curriedAdd(a) {
  return function(b) {
    return function(c) {
      return a + b + c;
    };
  };
}

// 箭头函数写法
const curriedAdd2 = a => b => c => a + b + c;

// 使用
console.log(curriedAdd(1)(2)(3)); // 6

// 部分应用(参数复用)
const add1 = curriedAdd(1);
console.log(add1(2)(3)); // 6

const add1And2 = add1(2);
console.log(add1And2(3)); // 6

9.4 防抖与节流

防抖

多次触发,只在最后一次触发后延迟执行。

javascript 复制代码
function debounce(fn, delay) {
  let timer = null;

  return function(...args) {
    // 清除之前的定时器
    clearTimeout(timer);

    // 设置新的定时器
    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  };
}

// 使用场景:搜索框输入、窗口 resize
const search = debounce(function(keyword) {
  console.log('搜索:', keyword);
}, 500);

search('a');
search('ab');
search('abc'); // 只执行最后一次

节流

固定时间间隔执行,无论触发多少次。

javascript 复制代码
function throttle(fn, delay) {
  let lastTime = 0;

  return function(...args) {
    const now = Date.now();

    if (now - lastTime >= delay) {
      lastTime = now;
      fn.apply(this, args);
    }
  };
}

// 使用场景:滚动事件、鼠标移动
const scroll = throttle(function() {
  console.log('滚动');
}, 1000);

window.addEventListener('scroll', scroll);

十、数组方法

10.1 改变原数组的方法

方法 说明 示例
push() 在末尾添加一个或多个元素 arr.push(4)
pop() 删除末尾的一个元素 arr.pop()
shift() 删除第一个元素 arr.shift()
unshift() 在开头添加一个或多个元素 arr.unshift(0)
splice() 删除、替换、插入元素 arr.splice(1, 1, 'a')
sort() 排序(默认按字符串排序) arr.sort((a,b) => a-b)
reverse() 反转数组 arr.reverse()
javascript 复制代码
const arr = [1, 2, 3];

arr.push(4);       // [1, 2, 3, 4]
arr.pop();         // [1, 2, 3]
arr.unshift(0);    // [0, 1, 2, 3]
arr.shift();       // [1, 2, 3]
arr.splice(1, 1);  // [1, 3](从索引 1 开始,删除 1 个元素)

// 排序
const nums = [3, 1, 4, 1, 5];
nums.sort((a, b) => a - b); // [1, 1, 3, 4, 5](升序)
nums.sort((a, b) => b - a); // [5, 4, 3, 1, 1](降序)

10.2 不改变原数组的方法

方法 说明 返回值
slice() 提取子数组 新数组
concat() 合并数组 新数组
map() 映射(转换每个元素) 新数组
filter() 过滤(筛选符合条件的元素) 新数组
reduce() 归约(累加器) 单个值
find() 查找第一个符合条件的元素 元素或 undefined
findIndex() 查找第一个符合条件的元素的索引 索引或 -1
includes() 判断是否包含某个元素 布尔值
some() 判断是否有元素满足条件 布尔值
every() 判断是否所有元素都满足条件 布尔值
flatMap() 先 map 再 flat(扁平化一层) 新数组
javascript 复制代码
const arr = [1, 2, 3, 4, 5];

// map:转换每个元素
const doubled = arr.map(x => x * 2); // [2, 4, 6, 8, 10]

// filter:过滤
const evens = arr.filter(x => x % 2 === 0); // [2, 4]

// find:查找
const found = arr.find(x => x > 3); // 4

// includes:判断包含
const has3 = arr.includes(3); // true

// some:任意满足
const hasEven = arr.some(x => x % 2 === 0); // true

// every:全部满足
const allPositive = arr.every(x => x > 0); // true

// flatMap
const nested = [[1], [2], [3]];
const flattened = nested.flatMap(x => x); // [1, 2, 3]

10.3 reduce() 详解

reduce() 是最强大的数组方法,可以实现几乎所有数组操作。

语法

javascript 复制代码
reduce((accumulator, currentValue, index, array) => {
  // 返回新的累加值
}, initialValue);

参数

  • accumulator:累加器,存储上次操作的结果
  • currentValue:当前元素
  • index:当前索引
  • array:原数组
  • initialValue:初始值(可选)

示例 1:数组求和

javascript 复制代码
const arr = [1, 2, 3, 4, 5];
const sum = arr.reduce((acc, cur) => acc + cur, 0);
console.log(sum); // 15

示例 2:数组去重

javascript 复制代码
const arr = [1, 2, 2, 3, 3, 3];
const unique = arr.reduce((acc, cur) => {
  return acc.includes(cur) ? acc : [...acc, cur];
}, []);
console.log(unique); // [1, 2, 3]

示例 3:对象属性统计

javascript 复制代码
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
const count = fruits.reduce((acc, cur) => {
  acc[cur] = (acc[cur] || 0) + 1;
  return acc;
}, {});
console.log(count); // { apple: 3, banana: 2, orange: 1 }

示例 4:数组转对象

javascript 复制代码
const users = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' }
];

const userMap = users.reduce((acc, user) => {
  acc[user.id] = user;
  return acc;
}, {});

console.log(userMap);
// {
//   1: { id: 1, name: '张三' },
//   2: { id: 2, name: '李四' },
//   3: { id: 3, name: '王五' }
// }

示例 5:按属性分组

javascript 复制代码
const users = [
  { name: '张三', age: 18 },
  { name: '李四', age: 20 },
  { name: '王五', age: 18 }
];

const grouped = users.reduce((acc, user) => {
  const age = user.age;
  if (!acc[age]) {
    acc[age] = [];
  }
  acc[age].push(user);
  return acc;
}, {});

console.log(grouped);
// {
//   18: [{ name: '张三', age: 18 }, { name: '王五', age: 18 }],
//   20: [{ name: '李四', age: 20 }]
// }

十一、对象与深浅拷贝

11.1 浅拷贝

浅拷贝只复制第一层属性,如果属性是引用类型,复制的是引用(地址)。

方法 1:Object.assign()

javascript 复制代码
const obj1 = { name: '张三', hobbies: ['reading', 'coding'] };
const obj2 = Object.assign({}, obj1);

obj2.name = '李四';
obj2.hobbies.push('gaming');

console.log(obj1); // { name: '张三', hobbies: ['reading', 'coding', 'gaming'] }
console.log(obj2); // { name: '李四', hobbies: ['reading', 'coding', 'gaming'] }

// hobbies 是引用类型,被共享

方法 2:扩展运算符

javascript 复制代码
const obj1 = { name: '张三', hobbies: ['reading', 'coding'] };
const obj2 = { ...obj1 };

// 结果同 Object.assign()

方法 3:Array.slice()(数组)

javascript 复制代码
const arr1 = [1, { name: '张三' }];
const arr2 = arr1.slice();

arr2[0] = 2;
arr2[1].name = '李四';

console.log(arr1); // [1, { name: '李四' }]
console.log(arr2); // [2, { name: '李四' }]

11.2 深拷贝

深拷贝会递归复制所有层级的属性,完全独立。

方法 1:JSON 序列化(简单场景)

javascript 复制代码
const obj1 = { name: '张三', hobbies: ['reading', 'coding'] };
const obj2 = JSON.parse(JSON.stringify(obj1));

obj2.name = '李四';
obj2.hobbies.push('gaming');

console.log(obj1); // { name: '张三', hobbies: ['reading', 'coding'] }
console.log(obj2); // { name: '李四', hobbies: ['reading', 'coding', 'gaming'] }

// ✅ 深拷贝成功

缺点

  • ❌ 无法处理 functionundefinedSymbol
  • ❌ 会丢失对象的原型链
  • ❌ 循环引用会报错
javascript 复制代码
const obj = {
  fn: function() {},
  undef: undefined,
  sym: Symbol('id')
};

const copied = JSON.parse(JSON.stringify(obj));
console.log(copied); // {}(fn、undef、sym 都丢失了)

方法 2:递归深拷贝(推荐)

javascript 复制代码
function deepClone(obj) {
  // 如果是 null 或不是对象,直接返回
  if (obj === null || typeof obj !== 'object') {
    return obj;
  }

  // 处理 Date 对象
  if (obj instanceof Date) {
    return new Date(obj);
  }

  // 处理 RegExp 对象
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }

  // 处理 Array 和 Object
  const clone = Array.isArray(obj) ? [] : {};

  for (const key in obj) {
    // 只复制自身属性,不复制原型链上的属性
    if (obj.hasOwnProperty(key)) {
      clone[key] = deepClone(obj[key]); // 递归调用
    }
  }

  return clone;
}

// 测试
const obj1 = {
  name: '张三',
  hobbies: ['reading', 'coding'],
  date: new Date(),
  reg: /abc/g,
  nested: { a: 1, b: { c: 2 } }
};

const obj2 = deepClone(obj1);
console.log(obj2); // 深拷贝成功

方法 3:使用第三方库

javascript 复制代码
// lodash
const _ = require('lodash');
const obj2 = _.cloneDeep(obj1);

十二、错误处理

12.1 try-catch-finally

javascript 复制代码
try {
  // 可能出错的代码
  const result = JSON.parse('invalid json');
} catch (error) {
  // 捕获错误
  console.error('错误类型:', error.constructor.name);
  console.error('错误信息:', error.message);
  console.error('错误堆栈:', error.stack);
} finally {
  // 无论是否出错都会执行
  console.log('finally 块');
}

12.2 throw 抛出错误

javascript 复制代码
// 抛出内置错误
throw new Error('自定义错误');
throw new TypeError('类型错误');
throw new ReferenceError('引用错误');
throw new SyntaxError('语法错误');
throw new RangeError('范围错误');

// 抛出自定义错误
class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = 'CustomError';
  }
}

throw new CustomError('自定义错误');

12.3 常见错误类型

类型 说明 触发场景
Error 基类错误 一般错误
TypeError 类型错误 对 null/undefined 调用方法
ReferenceError 引用错误 使用未声明的变量
SyntaxError 语法错误 代码语法错误
RangeError 范围错误 数组长度超出范围
javascript 复制代码
// TypeError
null.toString(); // TypeError: Cannot read property 'toString' of null

// ReferenceError
console.log(notDefined); // ReferenceError: notDefined is not defined

// SyntaxError
eval('var x = ;'); // SyntaxError: Unexpected token ;

// RangeError
new Array(-1); // RangeError: Invalid array length

十三、DOM 操作

13.1 获取元素

javascript 复制代码
// 通过 ID 获取
document.getElementById('myId');

// 通过类名获取(返回 HTMLCollection)
document.getElementsByClassName('myClass');

// 通过标签名获取(返回 HTMLCollection)
document.getElementsByTagName('div');

// 通过选择器获取单个元素
document.querySelector('.myClass');
document.querySelector('#myId');
document.querySelector('div.active');

// 通过选择器获取多个元素(返回 NodeList)
document.querySelectorAll('.myClass');
document.querySelectorAll('div');

HTMLCollection vs NodeList

  • HTMLCollection:动态更新(DOM 变化时自动更新)
  • NodeList:静态(获取时快照)

13.2 操作元素

javascript 复制代码
const element = document.querySelector('#myElement');

// 修改文本
element.textContent = '文本内容';
element.innerHTML = '<strong>HTML 内容</strong>';

// 修改样式
element.style.color = 'red';
element.style.fontSize = '16px';

// 修改类名
element.className = 'active';
element.classList.add('active');
element.classList.remove('active');
element.classList.toggle('active');
element.classList.contains('active'); // true/false

// 修改属性
element.setAttribute('data-id', '123');
element.getAttribute('data-id');
element.removeAttribute('data-id');

13.3 创建和插入元素

javascript 复制代码
// 创建元素
const div = document.createElement('div');
div.textContent = '新元素';
div.className = 'new-element';

// 插入元素
const parent = document.querySelector('#parent');
parent.appendChild(div); // 在末尾插入

// 在指定位置插入
const firstChild = parent.firstElementChild;
parent.insertBefore(div, firstChild); // 在 firstChild 之前插入

// 替换元素
const oldElement = document.querySelector('#oldElement');
parent.replaceChild(div, oldElement);

// 删除元素
parent.removeChild(div);

13.4 事件监听

javascript 复制代码
const button = document.querySelector('button');

// 添加事件监听
button.addEventListener('click', function(event) {
  console.log('点击事件');
  console.log('事件对象:', event);
  console.log('事件目标:', event.target);
  console.log('当前元素:', event.currentTarget);

  // 阻止事件冒泡
  event.stopPropagation();

  // 阻止默认行为
  event.preventDefault();
});

// 只执行一次
button.addEventListener('click', fn, { once: true });

// 移除事件监听
function fn() {
  console.log('事件');
}
button.addEventListener('click', fn);
button.removeEventListener('click', fn);

事件冒泡与捕获

javascript 复制代码
// 事件冒泡(从子元素到父元素)
div.addEventListener('click', fn, false); // false = 冒泡(默认)

// 事件捕获(从父元素到子元素)
div.addEventListener('click', fn, true); // true = 捕获

十四、BOM 操作

14.1 window 对象

javascript 复制代码
// 获取窗口宽高
console.log(window.innerWidth);  // 窗口内部宽度
console.log(window.innerHeight); // 窗口内部高度
console.log(window.outerWidth);  // 窗口外部宽度
console.log(window.outerHeight); // 窗口外部高度

// 滚动到指定位置
window.scrollTo(0, 0);
window.scrollTo({ top: 0, behavior: 'smooth' });

// 打开新窗口
window.open('https://www.example.com', '_blank');

// 关闭窗口
window.close();

// 定时器
const timer1 = setTimeout(() => console.log('一次性定时器'), 1000);
const timer2 = setInterval(() => console.log('循环定时器'), 1000);

clearTimeout(timer1);
clearInterval(timer2);

14.2 location 对象

javascript 复制代码
// 当前 URL 信息
console.log(location.href);      // 完整 URL
console.log(location.protocol);  // 协议(http:、https:)
console.log(location.host);     // 主机名 + 端口
console.log(location.hostname);  // 主机名
console.log(location.port);     // 端口
console.log(location.pathname);  // 路径
console.log(location.search);   // 查询字符串(?name=value)
console.log(location.hash);     // 哈希(#section)

// 跳转页面
location.href = 'https://www.example.com';
location.assign('https://www.example.com');

// 替换当前页面(无法后退)
location.replace('https://www.example.com');

// 刷新页面
location.reload();

14.3 history 对象

javascript 复制代码
// 历史记录长度
console.log(history.length);

// 后退
history.back();
history.go(-1);

// 前进
history.forward();
history.go(1);

// 跳转
history.go(2);  // 前进 2 步
history.go(-2); // 后退 2 步

14.4 本地存储

javascript 复制代码
// localStorage(持久化存储)
localStorage.setItem('key', 'value');
const value = localStorage.getItem('key');
localStorage.removeItem('key');
localStorage.clear();

// sessionStorage(会话存储,关闭窗口后清除)
sessionStorage.setItem('key', 'value');
const value = sessionStorage.getItem('key');
sessionStorage.removeItem('key');
sessionStorage.clear();

// 注意:只能存储字符串
const obj = { name: '张三', age: 18 };
localStorage.setItem('user', JSON.stringify(obj)); // 存储
const user = JSON.parse(localStorage.getItem('user')); // 读取

localStorage vs sessionStorage

特性 localStorage sessionStorage
生命周期 持久化(手动清除) 会话结束(关闭窗口)
作用域 同源的所有窗口 当前窗口
容量 约 5MB 约 5MB

十五、性能优化

15.1 代码层面

1. 减少重排重绘

javascript 复制代码
// ❌ 频繁修改 DOM(导致多次重排)
element.style.width = '100px';
element.style.height = '100px';
element.style.backgroundColor = 'red';

// ✅ 批量修改 DOM(只重排一次)
element.style.cssText = 'width: 100px; height: 100px; background-color: red;';

// ✅ 或者使用类名
element.className = 'my-class';

2. 使用事件委托

javascript 复制代码
// ❌ 给每个元素绑定事件
const items = document.querySelectorAll('.item');
items.forEach(item => {
  item.addEventListener('click', function() {
    console.log('点击:', this.textContent);
  });
});

// ✅ 使用事件委托(只绑定一次)
const container = document.querySelector('.container');
container.addEventListener('click', function(event) {
  if (event.target.classList.contains('item')) {
    console.log('点击:', event.target.textContent);
  }
});

3. 避免内存泄漏

javascript 复制代码
// ❌ 未清理事件监听
button.addEventListener('click', fn);
// 元素被移除后,事件监听依然存在,导致内存泄漏

// ✅ 清理事件监听
button.addEventListener('click', fn);
button.removeEventListener('click', fn);

// ✅ 清理定时器
const timer = setInterval(() => {}, 1000);
clearInterval(timer);

4. 使用防抖节流

见第八章「防抖与节流」

15.2 资源加载

1. 懒加载

javascript 复制代码
// 图片懒加载
<img data-src="image.jpg" class="lazy" alt="图片">

const lazyImages = document.querySelectorAll('.lazy');

const lazyLoad = () => {
  lazyImages.forEach(img => {
    if (img.getBoundingClientRect().top <= window.innerHeight) {
      img.src = img.dataset.src;
      img.classList.remove('lazy');
    }
  });
};

window.addEventListener('scroll', lazyLoad);
window.addEventListener('load', lazyLoad);

2. 预加载

html 复制代码
<!-- 预加载图片 -->
<link rel="preload" href="image.jpg" as="image">

<!-- DNS 预解析 -->
<link rel="dns-prefetch" href="https://cdn.example.com">

15.3 渲染优化

1. 使用 requestAnimationFrame

javascript 复制代码
// ❌ 使用 setTimeout
function animate() {
  element.style.left = parseInt(element.style.left) + 1 + 'px';
  setTimeout(animate, 16);
}
setTimeout(animate);

// ✅ 使用 requestAnimationFrame(与浏览器刷新率同步)
function animate() {
  element.style.left = parseInt(element.style.left) + 1 + 'px';
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

2. 虚拟 DOM(React)

React 使用虚拟 DOM,最小化真实 DOM 操作。

3. SSR 服务端渲染

提高首屏加载速度,优化 SEO。

相关推荐
wefly20172 小时前
m3u8live.cn:免安装 HLS 在线播放器,流媒体调试效率神器
开发语言·javascript·python·django·ecmascript·hls.js 原理·m3u8 解析
2301_816651222 小时前
C++与Rust交互编程
开发语言·c++·算法
天若有情6732 小时前
前端HTML精讲02:表单高阶用法+原生校验,告别冗余JS,提升开发效率
前端·javascript·html
蜡台2 小时前
Vue 组件通信的 12 种解决方案
前端·javascript·vue.js·props
05大叔2 小时前
Pyhton自带库和三方库
开发语言·python
吴声子夜歌2 小时前
TypeScript——局部类型、联合类型、交叉类型
javascript·git·typescript
va学弟2 小时前
Java 网络通信编程(8):完善 UDP 协议
java·开发语言·udp
Fortune792 小时前
实时操作系统中的C++
开发语言·c++·算法
吠品2 小时前
PyTorch张量维度不匹配?实战排查与修复指南
开发语言·oracle·php