在学习 JavaScript 的过程中,Promise、构造函数和原型链是三个经常被混淆、也最容易被遗忘的概念。原因并不在于它们本身多么复杂,而在于它们分别对应了 JavaScript 中三类不同的底层机制:

  • Promise 处理异步任务的结果流转;
  • 构造函数负责对象的批量创建;
  • 原型链决定对象属性和方法的查找规则。

如果只把它们当成孤立的定义去记忆,很快就会忘。更有效的方式,是从它们各自解决的问题出发:为什么需要它们,它们解决了什么问题,以及在代码执行时它们具体做了什么。

本文将围绕这三个问题,重新梳理 Promise、构造函数和原型链之间的关系。

我按重要程度讲:


1. Promise:解决“异步结果未来才回来”的问题

第一步:先理解同步和异步

同步代码是这样:

const a = 1;
const b = 2;
console.log(a + b);

代码从上往下执行,结果马上有。

但读取文件、请求接口、连接数据库这些操作不是马上完成的:

fs.readFile('./a.txt', 'utf-8', (err, data) => {
  console.log(data);
});

这里的问题是:

console.log('开始');

fs.readFile('./a.txt', 'utf-8', (err, data) => {
  console.log('文件内容', data);
});

console.log('结束');

输出顺序不是:

开始
文件内容
结束

而是:

开始
结束
文件内容

因为 readFile 是异步的。


第二步:Promise 本质是什么?

Promise 本质上是一个承诺对象

现在没有结果,但未来会给你一个结果。
要么成功,要么失败。

它有三种状态:

pending   等待中
fulfilled 成功
rejected  失败

一旦从 pending 变成成功或失败,就不会再变。


第三步:最原始的 Promise 写法

const p = new Promise((resolve, reject) => {
  const success = true;

  if (success) {
    resolve('成功结果');
  } else {
    reject('失败原因');
  }
});

p.then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

这里你要抓住三个点:

new Promise(...)

创建一个 Promise 对象。

resolve()

表示成功。

reject()

表示失败。


第四步:Promise 主要是为了解决回调地狱

传统回调写法:

fs.readFile('./a.txt', 'utf-8', (err, data1) => {
  fs.readFile('./b.txt', 'utf-8', (err, data2) => {
    fs.readFile('./c.txt', 'utf-8', (err, data3) => {
      console.log(data1, data2, data3);
    });
  });
});

这玩意嵌套一多就很恶心。

Promise 写法:

readA()
  .then(data1 => readB())
  .then(data2 => readC())
  .then(data3 => {
    console.log(data3);
  })
  .catch(err => {
    console.log(err);
  });

但现代 JS 更推荐:

async function main() {
  try {
    const data1 = await readA();
    const data2 = await readB();
    const data3 = await readC();

    console.log(data1, data2, data3);
  } catch (err) {
    console.log(err);
  }
}

第五步:你现在最该记住的 Promise 结论

你不需要死背 Promise 内部细节,先记住:

Promise = 用对象包装一个未来才会完成的异步结果。
await = 等这个 Promise 完成,然后拿到结果。
async = 声明这个函数里面可以用 await,并且这个函数本身会返回 Promise。

比如:

import fs from 'node:fs/promises';

async function main() {
  const data = await fs.readFile('./a.txt', 'utf-8');
  console.log(data);
}

main();

这段代码里面:

fs.readFile('./a.txt', 'utf-8')

返回的是 Promise。

await fs.readFile(...)

意思是:等文件读完,把结果给我。


2. 构造函数:用来批量创建同类对象

第一步:为什么需要构造函数?

比如你要创建很多用户:

const user1 = {
  name: '张三',
  age: 18,
  sayHi() {
    console.log('你好,我是' + this.name);
  }
};

const user2 = {
  name: '李四',
  age: 20,
  sayHi() {
    console.log('你好,我是' + this.name);
  }
};

问题是:重复代码太多。

所以 JS 需要一种“对象工厂”。


第二步:构造函数就是专门用来创建对象的函数

function User(name, age) {
  this.name = name;
  this.age = age;
}

const user1 = new User('张三', 18);
const user2 = new User('李四', 20);

console.log(user1);
console.log(user2);

输出类似:

User { name: '张三', age: 18 }
User { name: '李四', age: 20 }

第三步:new 到底干了什么?

这一句:

const user1 = new User('张三', 18);

底层大概做了四件事:

1. 创建一个空对象 {}
2. 把 this 指向这个新对象
3. 执行 User 函数,把 name 和 age 挂到 this 上
4. 返回这个新对象

所以:

function User(name, age) {
  this.name = name;
  this.age = age;
}

等价理解为:

const 新对象 = {};
新对象.name = name;
新对象.age = age;
return 新对象;

第四步:构造函数的关键特征

构造函数通常首字母大写:

function User() {}
function Person() {}
function Product() {}

调用时必须配合 new

const u = new User();

如果不用 new

const u = User('张三', 18);

this 就可能指向错误,甚至是 undefined。现代严格模式下会直接出问题。


第五步:现在更常见的是 class,但底层还是构造函数

现代写法:

class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

const user1 = new User('张三', 18);

这只是语法更舒服。

本质上 class 仍然是基于 JS 的原型机制。


3. 原型链:解决“方法复用”和“属性查找”的问题

这个最容易忘,因为它抽象。

先给你一句硬定义:

原型链 = JS 对象查找属性和方法时,沿着 __proto__ 一层一层往上找的机制。

但你先别背,直接看问题。


第一步:构造函数里直接写方法有什么问题?

function User(name, age) {
  this.name = name;
  this.age = age;

  this.sayHi = function () {
    console.log('你好,我是' + this.name);
  };
}

const user1 = new User('张三', 18);
const user2 = new User('李四', 20);

表面没问题。

但实际上:

console.log(user1.sayHi === user2.sayHi);

结果是:

false

为什么?

因为每 new 一次,都会重新创建一个新的 sayHi 函数。

这很浪费。


第二步:所以方法应该放到 prototype 上

function User(name, age) {
  this.name = name;
  this.age = age;
}

User.prototype.sayHi = function () {
  console.log('你好,我是' + this.name);
};

const user1 = new User('张三', 18);
const user2 = new User('李四', 20);

console.log(user1.sayHi === user2.sayHi);

结果是:

true

因为 sayHi 不是分别存在 user1user2 身上,而是存在它们共同的原型对象上。


第三步:prototype 和 proto 的关系

这两个最容易混。

记住:

函数有 prototype
对象有 __proto__
对象的 __proto__ 指向构造函数的 prototype

看代码:

function User(name) {
  this.name = name;
}

const user1 = new User('张三');

console.log(User.prototype);
console.log(user1.__proto__);
console.log(user1.__proto__ === User.prototype);

结果:

true

也就是说:

user1.__proto__ === User.prototype

第四步:对象查找属性的过程

function User(name) {
  this.name = name;
}

User.prototype.sayHi = function () {
  console.log('你好');
};

const user1 = new User('张三');

user1.sayHi();

执行 user1.sayHi() 时,JS 会这样找:

1. user1 自己身上有没有 sayHi?
2. 没有,就去 user1.__proto__ 上找
3. user1.__proto__ 指向 User.prototype
4. User.prototype 上有 sayHi
5. 执行它

这就是原型链。


第五步:原型链继续往上是什么?

user1.__proto__ === User.prototype
User.prototype.__proto__ === Object.prototype
Object.prototype.__proto__ === null

可以理解为:

user1
  ↓
User.prototype
  ↓
Object.prototype
  ↓
null

这条查找路径就是原型链。


4. 三者放在一起怎么理解?

这三个东西不是一个层级。理解时应从问题场景出发,而非孤立记忆定义,这样才能真正掌握其设计原理和应用场景。

Promise 是异步机制

解决:

结果现在没有,未来才有,怎么优雅处理?

比如:

const data = await fs.readFile('./a.txt', 'utf-8');

构造函数是对象创建机制

解决:

怎么批量创建结构相同的对象?

比如:

function User(name) {
  this.name = name;
}

const u = new User('张三');

原型链是属性查找和方法复用机制

解决:

对象自己没有某个方法时,去哪里找?
多个对象如何共享方法?

比如:

User.prototype.sayHi = function () {
  console.log(this.name);
};

更多推荐