在前端开发中,Promise 是处理异步操作的核心工具,而 JavaScript 的 原型链机制 则是其面向对象特性的基石。本文将结合一段典型代码,深入剖析 Promise 的执行流程、状态变化,并同步讲解对象的原型继承关系,帮助你打通异步编程与面向对象两大核心概念。
一、Promise 执行流程详解
先看这段代码:
html
预览
<script>
const p = new Promise((resolve, reject) => {
console.log(111);
setTimeout(() => {
console.log(333);
reject('结果2');
}, 1000);
});
console.log(222);
console.log(p, '////'); // 此时 p 仍为 pending
p.then(data => console.log(data))
.catch(err => console.log(err))
.finally(() => console.log('finally'));
</script>
控制台输出顺序:
text
编辑
111
222
Promise {<pending>} ////
333
结果2
finally
关键点解析:
- executor(执行器)是同步执行的
new Promise(...)中传入的函数会立即执行,因此先输出111。 - Promise 状态初始为
pending
在setTimeout触发前,p的状态仍是pending,所以console.log(p)显示<pending>。 .then()和.catch()是异步注册的回调
即使reject在 1 秒后才调用,.catch()也能捕获错误,因为 Promise 内部会“记住”状态变化,并在状态确定后触发对应的回调。.finally()总是执行
无论成功或失败,finally都会运行,常用于清理资源(如关闭 loading 动画)。
✅ 注意:
resolve/reject只能改变一次状态,后续调用无效。
二、JavaScript 原型链:实例、原型与 constructor
再看这段面向对象的代码:
javascript
编辑
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.special = '人类';
let zhen = new Person('zhen', 18);
console.log(zhen.special); // '人类'
const kong = {
name: '孔子',
hobbies: ['books', 'music'],
};
// 强制修改原型
zhen.__proto__ = kong;
console.log(zhen.hobbies, zhen.special); // ['books', 'music'] undefined
输出解释:
- 初始时,
zhen.__proto__ === Person.prototype,所以能访问special。 - 手动赋值
zhen.__proto__ = kong后,原型链被切断,指向kong对象。 - 因此
zhen.hobbies可读(来自kong),但zhen.special变为undefined(kong没有该属性)。
核心概念梳理:
| 概念 | 说明 |
|---|---|
实例对象(如 zhen) | 通过 new 创建的对象,拥有自身属性(name, age) |
原型对象(如 Person.prototype) | 构造函数的 prototype 属性,所有实例共享其方法/属性 |
__proto__ | 实例的内部指针,指向其构造函数的 prototype(非标准但广泛支持) |
constructor | 原型对象上的属性,指向构造函数本身(如 Person.prototype.constructor === Person) |
重要规则:
-
原型链查找:访问属性时,先查自身,再沿
__proto__向上查找。 -
修改原型对象会影响所有实例(若未重写
__proto__):js 编辑 Person.prototype.sayHi = function() { console.log('Hi'); }; zhen.sayHi(); // 可调用 -
直接替换整个
prototype会丢失constructor:js 编辑 Person.prototype = { greet: 'hello' }; // ❌ constructor 指向 Object // 应手动修复: Person.prototype.constructor = Person; // ✅
⚠️ 不推荐直接修改
__proto__
虽然可行,但性能差且破坏封装性。应使用Object.setPrototypeOf()(仍不推荐)或设计良好的继承结构。
三、Promise.all:批量处理异步任务
虽然你的代码未使用 Promise.all,但作为 Promise 的重要静态方法,值得简要介绍。
js
编辑
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 500));
const p3 = fetch('/api/data'); // 假设返回 Promise
Promise.all([p1, p2, p3])
.then(results => console.log(results)) // [1, 2, response]
.catch(err => console.error('任一失败:', err));
特点:
- 全部成功才成功:任一 Promise
reject,整体立即reject。 - 保持顺序:结果数组顺序与输入数组一致。
- 并发执行:所有 Promise 同时开始,非串行。
替代方案对比:
| 方法 | 行为 | 适用场景 |
|---|---|---|
Promise.all | 全成功才成功 | 表单提交需所有字段验证通过 |
Promise.allSettled | 等待所有完成(无论成败) | 批量请求,需知道每个结果 |
Promise.race | 返回第一个完成的 | 超时控制(如 race(fetch(), timeoutPromise)) |
四、总结要点
✅ Promise 关键点:
- executor 同步执行,状态一旦变更不可逆。
.then/.catch是微任务,会在当前宏任务结束后、下一轮事件循环前执行。.finally不接收参数,不影响 Promise 链的值传递。
✅ 原型链关键点:
- 实例通过
__proto__链接到原型对象。 - 原型对象的
constructor应指向构造函数(避免意外覆盖)。 - “面向对象”在 JS 中本质是“委托行为” ,不是类继承。
✅ 最佳实践:
- 避免直接操作
__proto__,优先使用class或Object.create()。 - 使用
Promise.allSettled处理可容忍部分失败的批量请求。 - 始终在
.catch中处理错误,防止未捕获的 rejection。
五、拓展思考
-
如果
reject后没有.catch会怎样?
浏览器会抛出 “Uncaught (in promise)” 警告,但不会中断脚本(与 throw 不同)。 -
能否多次调用
resolve或reject?
可以,但只有第一次有效,后续调用被忽略。 -
zhen.special为什么变成undefined而不是报错?
JS 属性访问不存在时返回undefined,这是语言设计特性,需用hasOwnProperty或in判断存在性。 -
现代替代方案:
- 用
async/await替代.then链,提升可读性。 - 用
class语法替代构造函数 + prototype,更接近传统 OOP。
- 用