Day18_JavaScript高级核心:原型链、继承与事件循环机制深度解析(上)

JavaScript高级核心:原型链、继承与事件循环机制深度解析

目录

  • 一、核心概念回顾
    • [1.1 垃圾回收机制](#1.1 垃圾回收机制)
    • [1.2 函数高级特性](#1.2 函数高级特性)
      • [1.2.1 执行上下文对象(Execution Context)](#1.2.1 执行上下文对象(Execution Context))
      • [1.2.2 闭包函数(Closure)](#1.2.2 闭包函数(Closure))
  • 二、对象高级与原型链
    • [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 实战案例与名词解释)
  • 三、单线程与事件轮询机制
  • [四、Web Worker多线程实现](#四、Web Worker多线程实现)
  • 五、核心知识点总结
    • [5.1 原型链总结](#5.1 原型链总结)
    • [5.2 事件循环机制总结](#5.2 事件循环机制总结)
    • [5.3 最佳实践建议](#5.3 最佳实践建议)
    • [5.4 知识点对比表](#5.4 知识点对比表)
  • 六、经典工程场景实战
    • [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 → objBobjB.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 endfirst 出栈。
  • 控制台输出顺序:First function startSecond functionThird functionFirst 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)

名词解释:闭包是指有权访问另一个函数作用域中变量的函数。创建闭包的常见方式是在一个函数内部创建另一个函数。

闭包的三个特性

  1. 函数嵌套函数
  2. 内部函数引用外部函数的变量
  3. 外部函数返回内部函数
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 原型与构造函数关系

核心规则

  1. 构造函数.prototype 可以获取该构造函数实例的原型
  2. 相同构造函数创建的对象,原型也相同
  3. __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 赋值 ④ 返回该对象。
  • person1person2__proto__ 都指向同一个 Person.prototype,故 sayHello 方法在原型上只存一份,两实例共享。
  • Person.prototype.constructor === Personprototype 上的 constructor 默认指回构造函数,用于 instanceof 与类型识别。

关键 API

属性 挂在谁身上 含义
prototype 函数 该函数 new 出来的实例的原型对象
__proto__ 对象 指向创建它的构造函数的 prototype
constructor 原型对象 默认等于对应构造函数

注意点

  • __proto__ 是历史属性,标准写法为 Object.getPrototypeOf(obj);面试两者都要会答。
  • 不要混淆:obj.prototype 对普通对象是 undefined,只有函数才有 prototype

实战场景

  • 所有内置类型(ArrayDate)方法都在各自 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 指回构造函数,形成闭环。
  • 打印 arrArray.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

【代码注释】

核心逻辑

  • f1f2 共享 Foo.prototypeo1o2 共享 Object.prototype
  • 构造函数 Foo 自身也是对象,其原型链为 Foo → Function.prototype → Object.prototype → null
  • Object.prototype.__proto__ === null 是原型链终点 ,属性查找到此停止并返回 undefined

属性查找顺序(以 f1.toString 为例)

  1. 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.prototypeObject 作为函数由 Function 构造,故其「对象原型」指向 Function.prototype
  • Function.prototype.constructor === Object:历史设计,Function.prototypeconstructor 指向 Object(面试记结论即可)。

特殊现象 2:Function 自引用

  • Function.constructor === FunctionFunction 既是构造函数也是自身的实例类型。
  • 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原型继承特点

核心特点

  1. 对象可以继承其原型上的属性和方法
  2. 通过原型链实现属性查找的委托机制
  3. 一个对象只能有一个直接原型(单继承)
  4. 一个原型可以被多个对象共享

继承
继承
子类实例
父类实例作为原型
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.prototypeinstanceof Arrayinstanceof Object 均为 true(沿链能找到对应 prototype)。
  • DOM 多级原型#boxHTMLDivElement 一路委托到 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 = Childnew 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>

【代码注释】

核心逻辑(组合继承)

  1. 属性继承Product.call(this, price, nums) 在子类构造函数内执行父类构造函数,把 pricenums 挂到当前实例 上(每个 CarProduct 实例独立一份)。
  2. 方法继承CarProduct.prototype = new Product() 建立原型链,实例可调用 discountbuy 等定义在 Product.prototype 上的方法。
  3. 修正 constructor :保证 c1.constructor === CarProduct,调试与 instanceof 正确。
  4. 子类扩展driver 写在 CarProduct.prototype 上,仅汽车商品拥有。

完整原型链(以 c1 为例)

复制代码
c1 实例
  → CarProduct.prototype(实为 Product 的某个实例)
    → Product.prototype(discount、buy)
      → Object.prototype(toString 等)
        → null

注意点

  • c1.discount(0.9) 查找顺序:自身 → CarProduct.prototypeProduct.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是单线程?

核心原因

  1. 避免DOM操作冲突:JavaScript主要用于操作DOM,多线程会导致复杂的同步问题
  2. 简化模型:单线程简化了编程模型,减少了死锁和竞争条件
  3. 历史原因: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回调后执行
*/

【代码注释】

核心逻辑(单轮事件循环)

  1. 同步:StartEnd 立即入栈执行并打印。
  2. 遇到 setTimeout / Promise.then:浏览器注册 异步任务,回调进入对应队列,不阻塞后续同步代码。
  3. 同步清空后,Event Loop 先清空微任务队列 (全部 Promise.then),再取一个 宏任务(setTimeout)执行;若宏任务执行中又产生微任务,本轮微任务仍会先跑完。

关键分类

类型 示例 队列
微任务 Promise.thenqueueMicrotaskMutationObserver Microtask Queue
宏任务 setTimeoutsetInterval、I/O、UI 渲染 Macrotask Queue

注意点

  • setTimeout(fn, 0) 不是 0ms 立即执行,只是尽快放入宏任务队列。
  • async/awaitawait 之后的代码等价于 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 累加三次得 12value2 为 1+2+3=9value36

易错点

  • 误以为 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(): 加载外部脚本
*/
相关推荐
Ws_5 分钟前
C#学习 Day2
开发语言·学习·c#
杰克尼25 分钟前
天机学堂复习总结(day03-day04)
java·开发语言·redis·elasticsearch·spring cloud
tedcloud12332 分钟前
RTK部署教程:构建稳定的AI Workflow环境
服务器·javascript·人工智能·typescript·ocr
x***r1511 小时前
jdk-11.0.16.1_windows使用步骤详解(附JDK 11环境变量配置与验证教程)
java·开发语言·windows
luck_bor2 小时前
File类&递归作业
java·开发语言
努力努力再努力wz5 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
skywalk81636 小时前
言知(Yanzhi)系统提升建议报告和完工报告 by AutoCoder
开发语言·编程
yunn_6 小时前
单例模式两种实现方法
开发语言·c++·单例模式
我材不敲代码6 小时前
Python基础:列表详解、增删改查及常用高阶操作
开发语言·windows·python
zithern_juejin6 小时前
new 运算符
javascript