JavaScript高级核心:原型链、继承与事件循环机制深度解析
目录
- 一、核心概念回顾
- [1.1 垃圾回收机制](#1.1 垃圾回收机制)
- [引用计数(Reference Counting)](#引用计数(Reference Counting))
- 标记清除(Mark-and-Sweep)
- [1.2 函数高级特性](#1.2 函数高级特性)
- [1.2.1 执行上下文对象(Execution Context)](#1.2.1 执行上下文对象(Execution Context))
- [1.2.2 闭包函数(Closure)](#1.2.2 闭包函数(Closure))
- [1.1 垃圾回收机制](#1.1 垃圾回收机制)
- 二、对象高级与原型链
- [2.1 原型链深度解析](#2.1 原型链深度解析)
- [2.1.1 原型(Prototype)基础概念](#2.1.1 原型(Prototype)基础概念)
- [2.1.2 原型与构造函数关系](#2.1.2 原型与构造函数关系)
- [2.1.3 不同类型对象的__proto__与prototype](#2.1.3 不同类型对象的__proto__与prototype)
- [2.1.4 原型链完整结构](#2.1.4 原型链完整结构)
- [2.1.5 特殊现象解析](#2.1.5 特殊现象解析)
- [2.2 面向对象继承实现](#2.2 面向对象继承实现)
- [2.2.1 传统面向对象语言的继承](#2.2.1 传统面向对象语言的继承)
- [2.2.2 JavaScript原型继承特点](#2.2.2 JavaScript原型继承特点)
- [2.2.3 父类与子类的原型关系](#2.2.3 父类与子类的原型关系)
- [2.2.4 实现继承的核心步骤](#2.2.4 实现继承的核心步骤)
- [2.2.5 完整继承实现示例](#2.2.5 完整继承实现示例)
- [2.3 实战案例与名词解释](#2.3 实战案例与名词解释)
- [2.1 原型链深度解析](#2.1 原型链深度解析)
- 三、单线程与事件轮询机制
- [3.1 进程与线程基础](#3.1 进程与线程基础)
- [3.2 JavaScript单线程设计](#3.2 JavaScript单线程设计)
- [3.3 同步与异步任务](#3.3 同步与异步任务)
- [3.4 事件轮询机制深度剖析](#3.4 事件轮询机制深度剖析)
- [四、Web Worker多线程实现](#四、Web Worker多线程实现)
- [4.1 Worker API详解](#4.1 Worker API详解)
- [4.2 实战应用场景](#4.2 实战应用场景)
- 五、核心知识点总结
- 六、经典工程场景实战
- [6.1 闭包实战:防抖、节流、记忆化](#6.1 闭包实战:防抖、节流、记忆化)
- [6.2 闭包实战:模块化与私有状态](#6.2 闭包实战:模块化与私有状态)
- [6.3 原型链实战:观察者模式(EventEmitter)](#6.3 原型链实战:观察者模式(EventEmitter))
- [6.4 原型链实战:策略模式与混入(Mixin)](#6.4 原型链实战:策略模式与混入(Mixin))
- [6.5 继承实战:组件基类封装](#6.5 继承实战:组件基类封装)
- [6.6 事件循环实战:请求并发控制](#6.6 事件循环实战:请求并发控制)
- [6.7 事件循环实战:分时渲染长列表](#6.7 事件循环实战:分时渲染长列表)
- [6.8 Web Worker 实战:CSV 解析与数据聚合](#6.8 Web Worker 实战:CSV 解析与数据聚合)
- [6.9 综合实战:虚拟任务调度器](#6.9 综合实战:虚拟任务调度器)
- 参考资源
一、核心概念回顾
1.1 垃圾回收机制
JavaScript作为一门高级编程语言,拥有自动的垃圾回收(Garbage Collection)机制,这使得开发者无需手动管理内存。主流浏览器主要采用两种垃圾回收算法:
引用计数(Reference Counting)
原理:记录每个对象被引用的次数,当引用次数降为0时,该对象即可被回收。
javascript
// 【代码注释】演示引用计数原理
let obj1 = { name: 'Object 1' }; // 引用计数: 1
let obj2 = obj1; // 引用计数: 2
obj1 = null; // 引用计数: 1
obj2 = null; // 引用计数: 0 → 可被回收
⚠️ 循环引用问题:
javascript
// 【代码注释】循环引用导致内存泄漏的经典案例
function createCycle() {
let objA = {};
let objB = {};
objA.ref = objB; // objA 引用 objB
objB.ref = objA; // objB 引用 objA
// 函数结束后,objA和objB的引用计数都为1,无法被回收
}
【代码注释】
核心逻辑
obj1 = { name: 'Object 1' }:堆上创建对象,栈上变量obj1持有引用,计数为 1。obj2 = obj1:不新建对象,仅复制引用地址,同一对象计数变为 2。obj1 = null:断开obj1这条引用,计数降为 1;对象仍被obj2持有。obj2 = null:最后一条引用断开,计数为 0,对象进入可回收状态。
关键概念
- 引用计数 :每多一个变量/属性指向该对象,计数 +1;赋
null或变量出作用域则 -1。 - 循环引用 :
objA.ref → objB且objB.ref → objA时,即使外部变量已失效,两者互相维持计数 ≥ 1,导致内存泄漏。
注意点
- 现代浏览器不再单独依赖引用计数作为主 GC 算法,因其无法处理循环引用。
- 面试常问:「JS 用什么 GC?」------答 标记清除 + V8 分代优化,并补充引用计数的缺陷。
实战场景
- 理解为何
element.onclick = handler且 DOM 未移除时可能泄漏:DOM 与 handler 互相引用。
标记清除(Mark-and-Sweep)
原理:从根对象(如全局对象)开始,遍历所有可访问的对象并标记,未被标记的对象将被回收。
根对象
Global
对象1
标记✓
对象2
标记✓
对象3
标记✓
孤立对象
未标记✗
孤立对象2
未标记✗
现代浏览器的分代回收策略:
- 新生代(New Generation):存放短生命周期的对象,使用Scavenge算法
- 老生代(Old Generation):存放长生命周期的对象,使用标记清除+整理算法
1.2 函数高级特性
1.2.1 执行上下文对象(Execution Context)
名词解释:执行上下文是JavaScript代码执行时的环境信息,包含变量对象、作用域链和this指向。
函数调用
函数调用
嵌套调用
全局执行上下文
函数执行上下文1
函数执行上下文2
函数执行上下文3
执行栈(Call Stack):
javascript
// 【代码注释】演示执行栈的压栈和出栈过程
function first() {
console.log('First function start');
second();
console.log('First function end');
}
function second() {
console.log('Second function');
third();
}
function third() {
console.log('Third function');
}
first();
/*
执行栈变化过程:
1. first() 入栈
2. second() 入栈
3. third() 入栈
4. third() 出栈
5. second() 出栈
6. first() 出栈
*/
【代码注释】
核心逻辑
- 调用
first()时,为其创建函数执行上下文 并压入执行栈(Call Stack)。 first内部调用second()→second入栈;second再调用third()→third入栈(栈顶 = 当前正在执行的函数)。third执行完毕出栈 →second继续 →second出栈 →first打印First function end→first出栈。- 控制台输出顺序:
First function start→Second function→Third function→First function end。
关键 API / 概念
- 执行栈:后进先出(LIFO),同一时刻只有一个栈顶函数在跑(单线程)。
- 执行上下文:每个函数调用对应一份「变量环境 + 作用域链 + this」,调用结束即销毁(闭包例外,见下文)。
注意点
- 递归无终止条件会导致栈不断入栈 →
Maximum call stack size exceeded(栈溢出)。 - Chrome DevTools → Sources → 断点后左侧 Call Stack 面板即为此栈的可视化。
实战场景
- 报错堆栈
at foo (app.js:12)从栈顶到栈底即调用链;React 错误边界、Sentry 上报都依赖此结构。
1.2.2 闭包函数(Closure)
名词解释:闭包是指有权访问另一个函数作用域中变量的函数。创建闭包的常见方式是在一个函数内部创建另一个函数。
闭包的三个特性:
- 函数嵌套函数
- 内部函数引用外部函数的变量
- 外部函数返回内部函数
javascript
// 【代码注释】闭包的基本实现
function createCounter() {
let count = 0; // 私有变量
return {
increment: function() {
count++;
console.log(count);
},
decrement: function() {
count--;
console.log(count);
},
getCount: function() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // 输出: 1
counter.increment(); // 输出: 2
counter.decrement(); // 输出: 1
// count变量无法直接访问,只能通过提供的方法操作
经典应用场景:
- 模块化封装:创建私有变量和方法
- 函数柯里化:预设函数参数
- 缓存/记忆化:存储计算结果
- 事件处理器:保存状态
javascript
// 【代码注释】闭包实现函数柯里化
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.concat(moreArgs));
};
};
}
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 输出: 6
console.log(curriedAdd(1, 2)(3)); // 输出: 6
【代码注释】
核心逻辑(createCounter)
createCounter执行时创建局部变量count = 0,并return一个包含三个方法的对象。- 外部保存
const counter = createCounter()后,createCounter的 EC 已出栈,但返回的方法仍通过闭包 引用count,故count不会被 GC。 increment/decrement读写的是同一份count,实现跨调用持久化的私有状态。
核心逻辑(curry)
curried(...args)每次收集参数;若args.length >= fn.length则执行原函数,否则返回新函数继续收集moreArgs。args.concat(moreArgs)合并历次调用参数,实现add(1)(2)(3)与add(1,2)(3)等价。
关键概念
- 闭包三要素:嵌套函数、内层引用外层变量、内层在外层外部仍被使用。
- 柯里化:把多参函数转为多次单参(或分批)调用,便于复用与组合。
注意点
- 闭包会延长外层变量生命周期,滥用(如全局缓存、未解绑的 DOM 回调)易内存泄漏。
- 箭头函数做监听器时
this不指向元素,需用event.currentTarget或普通函数。
实战场景
- 计数器、模块私有变量、防抖/节流、React Hooks 闭包快照,均为同一机制的不同形态。
二、对象高级与原型链
2.1 原型链深度解析
2.1.1 原型(Prototype)基础概念
名词解释:
- Prototype:函数的prototype属性,指向一个对象,该对象的属性和方法会被该函数创建的实例所共享
- proto:对象的内部属性,指向创建该对象的构造函数的prototype
- Constructor:指向创建该对象的构造函数
prototype
constructor
proto
proto
构造函数 Function
原型对象 Prototype
实例 Instance
实例 Instance2
2.1.2 原型与构造函数关系
核心规则:
构造函数.prototype可以获取该构造函数实例的原型- 相同构造函数创建的对象,原型也相同
__proto__是实例访问原型的桥梁(非标准属性,但浏览器普遍支持)
javascript
// 【代码注释】验证原型的共享性
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
const person1 = new Person('Alice');
const person2 = new Person('Bob');
console.log(person1.__proto__ === person2.__proto__); // true
console.log(person1.__proto__ === Person.prototype); // true
console.log(Person.prototype.constructor === Person); // true
【代码注释】
核心逻辑
new Person('Alice')会:① 创建空对象 ② 将对象[[Prototype]](即__proto__)指向Person.prototype③ 执行构造函数给this.name赋值 ④ 返回该对象。person1与person2的__proto__都指向同一个Person.prototype,故sayHello方法在原型上只存一份,两实例共享。Person.prototype.constructor === Person:prototype上的constructor默认指回构造函数,用于instanceof与类型识别。
关键 API
| 属性 | 挂在谁身上 | 含义 |
|---|---|---|
prototype |
函数 | 该函数 new 出来的实例的原型对象 |
__proto__ |
对象 | 指向创建它的构造函数的 prototype |
constructor |
原型对象 | 默认等于对应构造函数 |
注意点
__proto__是历史属性,标准写法为Object.getPrototypeOf(obj);面试两者都要会答。- 不要混淆:
obj.prototype对普通对象是undefined,只有函数才有prototype。
实战场景
- 所有内置类型(
Array、Date)方法都在各自Xxx.prototype上,故[].map与多个数组实例共享同一套方法。
示例演示:constructor属性
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Constructor属性演示</title>
</head>
<body>
<script>
// 【代码注释】演示constructor属性的指向关系
// 数组的构造函数是Array
var arr = [];
// 原型链:arr -> Array.prototype -> Object.prototype
// 构造函数链:Array -> Object
console.log(arr.constructor); // 输出: Array
console.log(Array.prototype.constructor); // 输出: Array
console.log(arr.__proto__ === Array.prototype); // true
// 【代码注释】验证原型链结构
console.log(arr);
console.log(Array.prototype);
</script>
</body>
</html>
【代码注释】
核心逻辑
arr = []由Array构造,arr.__proto__ === Array.prototype,故arr.constructor指向Array(沿原型链上的constructor属性查找)。Array.prototype.constructor === Array:原型对象上的constructor指回构造函数,形成闭环。- 打印
arr与Array.prototype可直观看到实例与原型对象是两个不同对象,实例通过[[Prototype]]委托访问原型方法。
注意点
arr是数组实例 ,没有prototype属性;只有函数 才有prototype。- 修改
Array.prototype会影响所有数组实例,生产环境禁止污染内置原型。
2.1.3 不同类型对象的__proto__与prototype
javascript
// 【代码注释】函数类型对象的双重身份
function Foo() {}
console.log(typeof Foo.__proto__); // "object" - Foo作为对象,有自己的原型
console.log(typeof Foo.prototype); // "object" - Foo作为构造函数,有prototype属性
console.log(Foo.__proto__ === Function.prototype); // true
// 【代码注释】非函数类型对象
const obj = {};
console.log(obj.prototype); // undefined - 非函数对象没有prototype属性
console.log(obj.__proto__ === Object.prototype); // true
【代码注释】
核心逻辑(函数的双重身份)
- 作为对象 :
Foo是函数也是对象,Foo.__proto__ === Function.prototype(由Function构造)。 - 作为构造函数 :
Foo.prototype是显式对象,供new Foo()的实例链接;与Foo.__proto__是两条不同的链。 - 普通对象
obj = {}等价于new Object(),只有__proto__ → Object.prototype,无prototype。
记忆口诀
- 函数看
prototype(给实例用);所有对象看__proto__(找上级原型)。
2.1.4 原型链完整结构
javascript
// 【代码注释】构建原型链示例
function Foo() {}
var f1 = new Foo();
var f2 = new Foo();
var o1 = {};
var o2 = {};
/*
原型链结构:
f1, f2 → Foo.prototype → Object.prototype → null
o1, o2 → Object.prototype → null
Foo → Function.prototype → Object.prototype → null
Object → Function.prototype → Object.prototype → null
*/
// 【代码注释】验证原型链的末端
console.log(Object.prototype.__proto__ === null); // true
【代码注释】
核心逻辑
f1、f2共享Foo.prototype;o1、o2共享Object.prototype。- 构造函数
Foo自身也是对象,其原型链为Foo → Function.prototype → Object.prototype → null。 Object.prototype.__proto__ === null是原型链终点 ,属性查找到此停止并返回undefined。
属性查找顺序(以 f1.toString 为例)
f1自身 → 2.Foo.prototype→ 3.Object.prototype(找到toString)→ 4.null停止。
实战场景
instanceof沿原型链判断;in/for...in会遍历整条链(需配合hasOwnProperty)。
proto
proto
proto
proto
proto
proto
proto
f1/f2实例
Foo.prototype
Object.prototype
null
o1/o2实例
Foo构造函数
Function.prototype
Object构造函数
2.1.5 特殊现象解析
注意:这些是JavaScript设计的结果,不是规则,重点在于理解原因。
javascript
// 【代码注释】特殊现象1:Object的原型是Function.prototype
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.prototype.constructor === Object); // true
// 【代码注释】特殊现象2:Function的构造函数是自身
console.log(Function.constructor === Function); // true
console.log(Function.prototype === Function.__proto__); // true
/*【原理解析】
在JavaScript中,Function是所有函数的构造函数,包括它自己。
这是因为Function在语言初始化时就被创建为自引用的构造函数。
*/
【代码注释】
特殊现象 1:Object 与 Function
Object.__proto__ === Function.prototype:Object作为函数由Function构造,故其「对象原型」指向Function.prototype。Function.prototype.constructor === Object:历史设计,Function.prototype上constructor指向Object(面试记结论即可)。
特殊现象 2:Function 自引用
Function.constructor === Function:Function既是构造函数也是自身的实例类型。Function.prototype === Function.__proto__:函数的prototype与作为对象的__proto__指向同一对象。
注意点
- 这些是引擎初始化时的特例,不要用来推导一般规则;日常开发以「实例 → 构造函数.prototype → Object.prototype → null」为主。
2.2 面向对象继承实现
2.2.1 传统面向对象语言的继承
php
// 【对比代码】传统面向对象语言(如PHP)的类继承
class Foo {
private $name;
private $age;
public function getInfo() { }
}
class Product extends Foo {
private $address;
}
class Shopcart extends Foo {
// 继承Foo的所有属性和方法
}
2.2.2 JavaScript原型继承特点
核心特点:
- 对象可以继承其原型上的属性和方法
- 通过原型链实现属性查找的委托机制
- 一个对象只能有一个直接原型(单继承)
- 一个原型可以被多个对象共享
继承
继承
子类实例
父类实例作为原型
Object.prototype
多个子类实例1
多个子类实例2
2.2.3 父类与子类的原型关系
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS中的父类子类关系</title>
</head>
<body>
<script>
// 【代码注释】演示JavaScript中的父子类关系
// 数组:构造函数是Array
var arr = [];
// 原型链:arr -> Array.prototype -> Object.prototype
// Array是子类,Object是父类
// 字符串对象:构造函数是String
var msg = new String();
// 原型链:msg -> String.prototype -> Object.prototype
// String是子类,Object是父类
// 【代码注释】DOM元素的原型链
var box = document.querySelector('#box');
// 完整原型链:
// box -> HTMLDivElement.prototype
// -> HTMLElement.prototype
// -> Element.prototype
// -> Node.prototype
// -> EventTarget.prototype
// -> Object.prototype
// 【代码注释】验证继承关系
console.log(arr instanceof Array); // true
console.log(arr instanceof Object); // true
console.log(msg instanceof String); // true
console.log(msg instanceof Object); // true
</script>
</body>
</html>
【代码注释】
核心逻辑
- 内置类型继承 :
arr的原型链arr → Array.prototype → Object.prototype;instanceof Array与instanceof Object均为true(沿链能找到对应prototype)。 - DOM 多级原型 :
#box从HTMLDivElement一路委托到Object.prototype,故 div 既有click(EventTarget)也有appendChild(Node/Element)。 instanceof判断的是:右侧构造函数的prototype是否出现在左侧对象的原型链上。
实战场景
- 调试
element.focus is not a function时查原型链;Polyfill 时判断if (!Array.prototype.at)再扩展。
2.2.4 实现继承的核心步骤
继承实现的两个关键步骤:
javascript
// 【代码注释】实现继承的标准模式
function Parent() {}
function Child() {}
// 步骤1:设置子类的实例的原型是父类的一个实例
Child.prototype = new Parent();
// 步骤2:修正constructor指向,保持语义正确性
Child.prototype.constructor = Child;
【代码注释】
核心逻辑
- 步骤 1
Child.prototype = new Parent():用父类实例作为子类原型的对象,子类实例通过原型链可访问Parent.prototype上的方法。 - 步骤 2
Child.prototype.constructor = Child:new Parent()会把Child.prototype.constructor变成Parent,不修正则child instanceof Child等语义错乱。 - 仅做原型继承时,父类构造函数不会自动执行 ,实例属性需在子类构造函数里用
Parent.call(this, ...)借用。
注意点 / 易错
Child.prototype = new Parent()会共享 一份父类实例上的属性(若父构造函数写了this.x = 1),多子类实例可能互相污染------课堂组合继承用call+ 原型就是为了解决此问题。- ES6 推荐
class Child extends Parent { constructor() { super(); } },底层仍是原型链,但语法更安全。
实战场景
- 理解 jQuery 插件、早期 Backbone
extend、手写组件基类前的继承套路;阅读 Vue 2 选项 API 与类组件源码时的基础。
继承原理图解:
prototype
proto
proto
proto
Child构造函数
Child.prototype
new Parent
Parent.prototype
Object.prototype
new Child实例
2.2.5 完整继承实现示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>继承实现演示</title>
</head>
<body>
<script>
// 【代码注释】父类:商品类
function Product(price, nums) {
// 给实例设置属性
this.price = price;
this.nums = nums;
}
// 【代码注释】在Product原型上定义方法,所有实例共享
Product.prototype.discount = function(num) {
this.price *= num;
console.log(`折扣后价格:${this.price}`);
};
Product.prototype.buy = function() {
if (this.nums > 0) {
this.nums -= 1;
console.log(`购买成功,剩余库存:${this.nums}`);
} else {
console.log('库存不足');
}
};
// 【代码注释】子类:汽车商品类
function CarProduct(price, nums, speed) {
// 【代码注释】使用call继承父类的属性(构造函数继承)
Product.call(this, price, nums);
// 添加子类特有属性
this.speed = speed;
}
// 【代码注释】设置原型链继承(原型继承)
// 设置CarProduct的实例的原型是Product的一个实例
CarProduct.prototype = new Product();
// 【代码注释】修正constructor指向
// 这一步很重要,否则constructor会指向Product
CarProduct.prototype.constructor = CarProduct;
// 【代码注释】在子类原型上添加特有方法
CarProduct.prototype.driver = function() {
console.log(`这辆车的最高时速是${this.speed}km/h,可以驾驶!`);
};
// 【代码注释】创建汽车实例
var c1 = new CarProduct(19.8, 45, 100);
console.log(c1);
console.log(`价格:${c1.price}万`);
// 【代码注释】调用继承的方法
c1.discount(0.9); // 调用父类方法
c1.driver(); // 调用子类方法
c1.buy(); // 调用父类方法
// 【代码注释】创建另一个实例,验证原型共享
var c2 = new CarProduct(27.8, 178, 250);
console.log(c2);
/*
完整原型链:
c1/c2 -> CarProduct.prototype (Product实例)
-> Product.prototype
-> Object.prototype
-> null
*/
</script>
</body>
</html>
【代码注释】
核心逻辑(组合继承)
- 属性继承 :
Product.call(this, price, nums)在子类构造函数内执行父类构造函数,把price、nums挂到当前实例 上(每个CarProduct实例独立一份)。 - 方法继承 :
CarProduct.prototype = new Product()建立原型链,实例可调用discount、buy等定义在Product.prototype上的方法。 - 修正 constructor :保证
c1.constructor === CarProduct,调试与instanceof正确。 - 子类扩展 :
driver写在CarProduct.prototype上,仅汽车商品拥有。
完整原型链(以 c1 为例)
c1 实例
→ CarProduct.prototype(实为 Product 的某个实例)
→ Product.prototype(discount、buy)
→ Object.prototype(toString 等)
→ null
注意点
c1.discount(0.9)查找顺序:自身 →CarProduct.prototype→Product.prototype→ ...,方法在原型上时this仍指向c1。- 若只写
CarProduct.prototype = Product.prototype(不new),则父子共用同一原型对象,子类改原型会影响父类------严禁。
实战场景
- 电商 SKU(通用商品 + 汽车/图书子类)、游戏实体(角色 + 怪物子类)建模;Bootstrap 组件扩展思想与此类似(共享基础原型方法)。
2.3 实战案例与名词解释
名词解释表
| 术语 | 英文 | 解释 |
|---|---|---|
| 原型 | Prototype | 函数的prototype属性,用于共享方法和属性 |
| 原型链 | Prototype Chain | 对象通过__proto__连接形成的链式结构 |
| 构造函数 | Constructor | 创建对象的函数,通过new调用 |
| 实例 | Instance | 通过构造函数创建的对象 |
| 继承 | Inheritance | 子类获取父类属性和方法的机制 |
| 闭包 | Closure | 能访问外部函数作用域的内部函数 |
| 作用域 | Scope | 变量的可访问范围 |
| 执行上下文 | Execution Context | 代码执行时的环境信息 |
经典应用场景
1. 方法扩展与兼容性处理
javascript
// 【代码注释】为Array原型添加方法
Array.prototype.first = function() {
return this[0];
};
Array.prototype.last = function() {
return this[this.length - 1];
};
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.first()); // 1
console.log(numbers.last()); // 5
2. 插件/库开发中的原型使用
javascript
// 【代码注释】简单的库封装模式
function MyLibrary(selector) {
// 处理不同参数类型
if (typeof selector === 'string') {
this.elements = document.querySelectorAll(selector);
} else if (selector instanceof HTMLElement) {
this.elements = [selector];
}
}
// 【代码注释】在原型上添加工具方法
MyLibrary.prototype.addClass = function(className) {
this.elements.forEach(el => el.classList.add(className));
return this; // 链式调用
};
MyLibrary.prototype.removeClass = function(className) {
this.elements.forEach(el => el.classList.remove(className));
return this;
};
// 【代码注释】工厂函数
function $(selector) {
return new MyLibrary(selector);
}
// 使用示例
$('.box').addClass('active').removeClass('hidden');
【代码注释】
示例 1:扩展 Array 原型
- 在
Array.prototype上挂first/last,所有数组实例共享;风险 :与将来 ES 标准方法名冲突、破坏for...in枚举,仅作理解,生产慎用。
示例 2:MyLibrary 工厂
$(selector)内部return new MyLibrary(selector),省略new的写法与 jQuery 一致。- 原型方法
return this实现链式调用;this.elements为实例私有数据,方法通过this访问。
实战场景
- 理解 jQuery/Zepto 设计;现代项目用 ES Module + 类,而非改内置原型。
三、单线程与事件轮询机制
3.1 进程与线程基础
核心概念
名词解释:
- 进程(Process):程序的一次执行,占用独立的内存空间,是资源分配的基本单位
- 线程(Thread):CPU的基本调度单位,是程序执行的一个完整流程,同一进程的线程共享内存空间
操作系统
进程1 - 浏览器
进程2 - 编辑器
进程3 - 其他应用
主线程
UI渲染/JS执行
网络线程
定时器线程
事件处理线程
进程与线程的关系
javascript
/*
【关键要点】
1. 一个进程中至少有一个主线程
2. 一个进程可以同时运行多个线程(多线程)
3. 同一进程内的线程可以直接共享数据
4. 不同进程之间的数据不能直接共享(需要IPC)
*/
// 【代码注释】浏览器多进程架构示意
/*
Chrome浏览器多进程架构:
- 浏览器主进程:协调其他进程
- 渲染进程:每个标签页一个,执行HTML/CSS/JS
- 网络进程:处理网络请求
- GPU进程:处理3D渲染
- 插件进程:管理插件
*/
3.2 JavaScript单线程设计
为什么JavaScript是单线程?
核心原因:
- 避免DOM操作冲突:JavaScript主要用于操作DOM,多线程会导致复杂的同步问题
- 简化模型:单线程简化了编程模型,减少了死锁和竞争条件
- 历史原因:JavaScript最初设计用于简单的脚本任务
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>JS单线程验证</title>
</head>
<body>
<script>
// 【代码注释】验证JavaScript单线程特性
console.log('=== 验证1:基本单线程执行 ===');
setTimeout(function() {
console.log('定时器执行');
}, 0);
console.log(100);
console.log(200);
/*
输出顺序:
100
200
定时器执行
原因:即使定时器延迟为0,回调函数也需要等待主线程
空闲后才能执行。这证明了JavaScript的单线程特性。
*/
// 【代码注释】验证2:长时间任务阻塞
console.log('\n=== 验证2:主线程阻塞 ===');
// 创建两个几乎同时触发的定时器
setTimeout(function() {
console.log('定时器1执行(1ms延迟)');
}, 1);
setTimeout(function() {
console.log('定时器2执行(2ms延迟)');
}, 2);
// 【代码注释】执行大量计算,阻塞主线程
console.log('开始计算...');
for (var i = 0; i <= 100000000; i++) {
Math.random() * i * i + i; // 模拟计算
}
console.log('计算结束');
/*
输出顺序:
开始计算...
计算结束
定时器1执行(1ms延迟)
定时器2执行(2ms延迟)
原因:即使定时器时间已到,回调函数也必须等待
主线程完成for循环后才执行。
*/
</script>
</body>
</html>
【代码注释】
验证 1:setTimeout(0) 仍晚于同步代码
- 定时器由定时器线程 计时,回调入宏任务队列;主线程必须先跑完同步
console.log(100/200),Event Loop 才会取回调。 - 说明 JS 执行是单线程,不等于浏览器只有一条线程。
验证 2:长循环阻塞
- 1ms/2ms 定时器时间到后,回调仍排队,直到
for一亿次循环结束主线程空闲才执行。 - 页面会卡死、动画停帧------长任务必须拆分(Worker、requestAnimationFrame 分片、Scheduler)。
实战场景
- 首屏大数据渲染、同步 JSON.parse 大文件,都会触发同类卡顿;需异步化或 Worker。
经典应用场景:
- React Fiber架构:将大任务分解为小任务,避免长时间阻塞
- requestIdleCallback:在浏览器空闲时执行低优先级任务
- 时间分片(Time Slicing):将长任务拆分到多个帧中执行
3.3 同步与异步任务
任务类型详解
同步任务(Synchronous):
- 按照代码顺序,一步一步执行
- 上一个任务完成才能执行下一个任务
- 阻塞后续代码执行
异步任务(Asynchronous):
- 需要满足条件且主线程空闲才执行
- 在等待异步条件时,同步任务继续执行
- 以回调函数形式存在
JavaScript中的异步任务类型
javascript
/*
【异步任务分类】
1. 定时器回调:setTimeout、setInterval
2. DOM事件回调:click、input、scroll等
3. 网络请求回调:Ajax、fetch
4. Promise回调:then、catch、finally
5. MutationObserver:DOM变化监听
6. MessageChannel:跨文档通信
*/
同步异步任务执行示例
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>同步与异步任务</title>
</head>
<body>
<script>
// 【代码注释】演示同步任务和异步任务的执行顺序
console.log('1. 同步代码开始');
// 【代码注释】定时器回调是异步任务
setTimeout(function() {
console.log('3. 定时器回调执行');
}, 1000);
console.log('2. 同步代码结束');
/*
输出顺序:
1. 同步代码开始
2. 同步代码结束
(等待1秒)
3. 定时器回调执行
分析:同步代码先执行,异步回调等待条件满足
且主线程空闲后执行。
*/
</script>
</body>
</html>
【代码注释】
核心逻辑
setTimeout(..., 1000)注册异步任务:浏览器启动 1s 计时,不阻塞 中间的同步console.log('2. 同步代码结束')。- 1 秒后回调进入宏任务队列;仅当主线程空闲 (同步栈空、微任务清空)才执行
3. 定时器回调执行。
对比
| 同步 | 异步(本例 setTimeout) | |
|---|---|---|
| 执行时机 | 立即、按书写顺序 | 条件满足 + 主线程空闲 |
| 是否阻塞后续同步代码 | 是 | 否 |
实战场景
- 所有「先打印 A 再打印 B,但 B 在 Promise/setTimeout 里」的面试题,都建立在此模型上。
3.4 事件轮询机制深度剖析
事件循环架构
事件轮询模块 回调队列 异步任务管理模块 主线程/执行栈 事件轮询模块 回调队列 异步任务管理模块 主线程/执行栈 注册异步任务 执行同步代码 条件满足,推入队列 同步代码执行完毕 检查队列 取出回调执行 执行回调 持续监听循环
事件循环核心组件
javascript
/*
【事件循环四大组件】
1. 执行栈(Call Stack)
- 主线程中的执行栈
- 所有同步任务在此执行
- 后进先出(LIFO)
2. 异步任务管理模块
- 定时器管理模块:管理setTimeout/setInterval
- DOM事件管理模块:管理各类DOM事件
- 网络请求管理模块:管理Ajax/fetch请求
- 条件满足后将回调推入回调队列
3. 回调队列(Callback Queue / Task Queue)
- 先进先出(FIFO)数据结构
- 存放等待执行的异步任务回调
- 分为宏任务队列和微任务队列
4. 事件轮询模块(Event Loop)
- 监听主线程状态
- 主线程空闲时从队列取任务
- 将任务放入主线程执行
*/
// 【代码注释】事件循环执行示意
console.log('Start');
setTimeout(() => console.log('Timeout 1'), 0);
Promise.resolve().then(() => console.log('Promise 1'));
setTimeout(() => console.log('Timeout 2'), 0);
Promise.resolve().then(() => console.log('Promise 2'));
console.log('End');
/*
输出顺序:
Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2
解析:
1. 同步代码:Start、End
2. 微任务队列:Promise的then回调优先执行
3. 宏任务队列:setTimeout回调后执行
*/
【代码注释】
核心逻辑(单轮事件循环)
- 同步:
Start、End立即入栈执行并打印。 - 遇到
setTimeout/Promise.then:浏览器注册 异步任务,回调进入对应队列,不阻塞后续同步代码。 - 同步清空后,Event Loop 先清空微任务队列 (全部
Promise.then),再取一个 宏任务(setTimeout)执行;若宏任务执行中又产生微任务,本轮微任务仍会先跑完。
关键分类
| 类型 | 示例 | 队列 |
|---|---|---|
| 微任务 | Promise.then、queueMicrotask、MutationObserver |
Microtask Queue |
| 宏任务 | setTimeout、setInterval、I/O、UI 渲染 |
Macrotask Queue |
注意点
setTimeout(fn, 0)不是 0ms 立即执行,只是尽快放入宏任务队列。async/await中await之后的代码等价于Promise.then,属于微任务。- 面试扩展:
async function f(){ await 1; console.log(2); } f(); console.log(3)→ 3 再 2。
实战场景
- Vue
nextTick、React 18 自动批处理、axios 链式then的调度顺序,都建立在微任务优先于宏任务之上。
异步任务面试题解析
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>异步任务面试题</title>
</head>
<body>
<script>
// 【代码注释】经典闭包+异步任务面试题
var value1 = 0, value2 = 0, value3 = 0;
for (var i = 1; i <= 3; i++) {
var i2 = i; // 【代码注释】每次循环创建新变量
(function () {
var i3 = i; // 【代码注释】IIFE创建闭包
// 【代码注释】定时器回调在循环结束后执行
setTimeout(function () {
value1 += i; // 引用全局变量i
value2 += i2; // 引用外层函数变量
value3 += i3; // 引用闭包变量
}, 1);
})();
}
// 【代码注释】循环结束后,全局变量i的值是4,i2的值是3
setTimeout(function () {
console.log('value1:', value1); // 12 = 3次累加i(4)
console.log('value2:', value2); // 9 = 1+2+3
console.log('value3:', value3); // 6 = 1+2+3
}, 100);
/*
详细解析:
- value1: 所有回调引用同一个全局i,循环结束时i=4,3次回调每次+4,结果12
- value2: 每次循环创建新的i2,分别为1,2,3,但闭包捕获的是外层作用域
- value3: IIFE创建独立作用域,每次i3的值为1,2,3
原型链查找顺序:
1. 先在函数内部查找i3 → 找到(闭包捕获的值)
2. 再在外层函数查找i2 → 找到
3. 最后在全局查找i → 找到
*/
</script>
</body>
</html>
【代码注释】
核心逻辑
for (var i = 1; i <= 3; i++)中var无块级作用域,循环结束后全局i === 4。- 每次循环
var i2 = i:在当次迭代 的作用域里保存 1、2、3(若用let i则每次迭代独立,与i3效果类似)。 - IIFE
(function(){ var i3 = i; ... })():为每次迭代创建独立 词法环境,i3锁定为 1、2、3。 setTimeout回调在循环结束后才执行(宏任务),此时读取的i已是 4,故value1累加三次得 12 ;value2为 1+2+3=9 ;value3为 6。
易错点
- 误以为
var i2 = i在异步里也会变------i2是数字拷贝,不会随全局i变;但全局i在闭包中引用的是同一个绑定。 - 现代修复:
for (let i = 1; ...)或setTimeout(() => {...}, 1, i)传参。
面试口述
- 「循环 + 异步 + var」是闭包与事件循环的组合题;先答同步阶段变量终值,再答异步回调执行时查的是哪一层作用域。
实战场景
- 选项卡
for绑定click全部高亮最后一项、批量setTimeout打印相同索引,根因与此题相同。
四、Web Worker多线程实现
4.1 Worker API详解
虽然JavaScript是单线程的,但Web Worker API允许在后台线程中执行脚本,实现真正的多线程。
Worker核心API
javascript
/*
【Web Worker API清单】
构造函数:
- new Worker(scriptURL): 创建新的Worker线程
Worker实例方法:
- worker.postMessage(data): 向Worker发送数据
- worker.terminate(): 立即终止Worker
Worker事件:
- worker.onmessage: 接收Worker消息
- worker.onerror: 处理Worker错误
Worker内部API:
- self.postMessage(data): 向主线程发送数据
- self.onmessage: 接收主线程消息
- self.close(): 关闭Worker
- importScripts(): 加载外部脚本
*/