吃透 Promise 与原型链:JS 底层基础实战解析

48 阅读6分钟

JavaScript 的单线程特性,看似限制了执行效率,却通过事件循环、Promise 等机制构建了独特的异步编程模型,让复杂的并发操作变得可控且清晰。

从线程的基础概念到 JS 中宏任务与微任务的优先级调度,核心逻辑始终围绕 “如何在有限的执行资源中,高效处理多任务”。

理解这些原理,不仅能帮助我们写出更符合运行机制的代码,更能深入领悟编程语言在设计时对性能与逻辑清晰性的平衡智慧。无论是多线程环境的资源共享,还是单线程下的异步协作,本质上都是对 “并发” 这一核心问题的不同解答。

本文将结合实例,先回顾同步与异步的核心概念,通过 Promise 代码解析异步执行流程,再深入原型链的工作机制,帮你夯实 JS 底层基础。

一、同步和异步的概念回顾

核心定义

  • 同步:代码按顺序逐行执行,前一行未执行完毕,后一行无法开始,主线程会被阻塞直到当前任务完成。
  • 异步:对于网络请求、定时器、文件读取等耗时任务,不会阻塞主线程执行。这些任务会被移交到浏览器对应模块(如定时器模块、网络模块)在后台处理,完成后通过回调通知主线程处理结果。
  • 异步核心思想:不等待耗时操作完成,先继续执行后续代码,最大化利用主线程资源

实例解析:Promise 异步执行流程

以下通过完整代码示例,拆解异步任务的执行逻辑:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS 异步执行示例</title>
</head>
<body>
<script>
  // 创建 Promise 实例
  const p = new Promise((resolve, reject) => {
    console.log(111); // Promise 构造函数同步执行
    // 异步任务:定时器(宏任务)
    setTimeout(() => {
      console.log(333);
      reject('数据加载失败'); // 改变 Promise 状态为 rejected
    }, 1000);
  });

  console.log(222);
  console.log(p, '//////'); // 打印当前 Promise 实例状态
  console.log(p.__proto__ === Promise.prototype); // 验证原型关系
  console.log(p.__proto__); // 打印实例的原型对象

  // 注册状态变更回调
  p.then((data) => {
    console.log(data); // 状态 resolved 时执行
  }).catch(err => {
    console.log(err); // 状态 rejected 时执行
  }).finally(() => {
    console.log('异步任务处理完毕'); // 无论状态如何都会执行
  });
</script>
</body>
</html>

执行步骤拆解(结合 JS 运行机制)

  1. 同步执行 Promise 构造函数new Promise(executor) 会立即调用传入的 executor 函数(同步执行),因此先输出 111
  2. 异步任务移交后台:遇到 setTimeout(宏任务),主线程不会等待,而是将其移交浏览器定时器模块处理,继续向下执行同步代码。
  3. 执行后续同步代码:依次输出 222;此时 setTimeout 尚未完成,Promise 状态仍为 pending,因此打印 Promise {<pending>} //////;后续两行验证原型关系的代码同步执行,输出 true 及 Promise.prototype 对象。
  4. 注册回调函数(不执行).then().catch().finally() 仅注册回调,不会立即执行,这些回调会被存储,等待 Promise 状态变更后触发。
  5. 异步任务完成,触发回调:1 秒后,定时器任务完成,其回调被推入宏任务队列;主线程空闲时执行该回调,输出 333,并调用 reject('数据加载失败') 将 Promise 状态改为 rejected
  6. 执行对应回调:状态变更后,触发 .catch() 回调,输出 数据加载失败;最后执行 .finally() 回调,输出 异步任务处理完毕

最终输出顺序

111
222
Promise {<pending>} //////
true
Promise.prototype 对象(包含 then、catch、finally 等方法)
333
数据加载失败
异步任务处理完毕

image.png

6caae61ae07974a1b7516e99f4f401a6.png

二、原型链的核心机制:JS 继承的底层逻辑

原型链是 JS 实现继承的核心机制,其本质是:对象在访问属性 / 方法时,若自身不存在,则会沿着 __proto__ 指向的原型对象逐层查找,直到找到目标或到达原型链终点(null

以下通过两段代码,拆解原型链的工作原理:

示例代码

// 1. 构造函数与原型继承
function Student(name, grade) {
  this.name = name;
  this.grade = grade;
}
Student.prototype.category = '学生'; // 给构造函数的原型添加属性
let lin = new Student('林同学', 8);
console.log(lin.category); // 输出:'学生'

// 2. 手动修改 __proto__ 改变原型链
const teacher = {
  name: '王老师',
  hobbies: ['阅读', '备课', '教研']
};
lin.__proto__ = teacher; // 重新指向原型对象
console.log(lin.hobbies); // 输出:['阅读', '备课', '教研']

第一部分:构造函数与原型的默认继承

核心逻辑

  • Student 是构造函数,通过 new 关键字创建实例 lin 时,lin 的默认原型(lin.__proto__)会指向 Student.prototype(构造函数的原型对象)。

  • 当访问 lin.category 时:

    1. 先查找 lin 自身属性,未找到 category
    2. 沿着 __proto__ 查找 Student.prototype,找到 category: '学生'
    3. 返回该属性值,因此输出 '学生'

第二部分:手动修改 __proto__ 重构原型链

核心逻辑

  • lin.__proto__ = teacher 手动修改了实例的原型指向,此时 lin 的原型不再是 Student.prototype,而是普通对象 teacher

  • 当访问 lin.hobbies 时:

    1. 查找 lin 自身属性,未找到 hobbies
    2. 沿着 __proto__ 查找 teacher 对象,找到 hobbies: ['阅读', '备课', '教研']
    3. 返回该属性值,因此输出 ['阅读', '备课', '教研']

关键注意事项

  1. __proto__ 的特性__proto__ 是实例对象访问原型的内置属性,属于非标准规范但被主流浏览器广泛支持,其作用是搭建实例与原型对象之间的连接,构成原型链。

  2. 不推荐生产环境修改 __proto__ :手动修改 __proto__ 会破坏原型链的默认结构,导致继承关系混乱,同时会影响 JS 引擎的优化(如属性查找缓存),降低代码性能,还可能引发难以调试的问题。

  3. 更安全的继承方案:若需自定义继承关系,推荐使用 Object.create()(基于指定原型创建新对象)或 ES6 class + extends 语法(标准化继承方案),例如:

    // Object.create 方式
    const studentWithTeacherProto = Object.create(teacher);
    studentWithTeacherProto.name = '林同学';
    studentWithTeacherProto.grade = 8;
    
    // class extends 方式
    class Teacher {
      constructor(name) {
        this.name = name;
        this.hobbies = ['阅读', '备课', '教研'];
      }
    }
    class Student extends Teacher {
      constructor(name, grade) {
        super(name);
        this.grade = grade;
      }
    }
    

总结

  • 异步核心:JS 单线程通过 “同步执行主线程代码 + 异步任务后台处理 + 状态变更触发回调” 实现高效并发,Promise 则规范了异步任务的状态管理与回调注册逻辑。
  • 原型链本质:对象属性查找的 “链式机制”,默认通过构造函数与 prototype 关联,手动修改 __proto__ 会改变这一关联关系,但需谨慎使用。
  • 理解这两大底层机制,能帮助我们更精准地预判代码执行结果,规避常见坑点,同时为后续学习闭包、模块化、框架源码等内容打下坚实基础。