JavaScript 原型链解密:原来 proto 和 prototype 这么好懂

一文搞懂 JavaScript 原型:从「蒙圈」到「通透」

作为前端开发者,JavaScript 的原型机制可能是你入门时的第一个「小坎」。明明看着简单,可一到实际应用就容易晕头转向 ------ 原型原型链prototype__proto__ 这些概念总像绕口令一样让人迷糊。今天咱们就用最接地气的方式,把原型这点事儿彻底说清楚。

先从一个「反常识」的问题说起

你有没有想过:为什么数组能直接调用 push() 方法?

js 复制代码
const arr = [];

arr.push(1); // 这玩意儿从哪来的?

我们明明没给数组 arr 定义 push 方法,它却能直接用。这就像你去朋友家串门,没提前打招呼却能直接推门而入 ------ 因为朋友家的「钥匙」早就放在了一个大家都知道的地方。在 JavaScript 里,这个「放钥匙的地方」就是 原型

原型是什么?先认识 prototype(显示原型)

函数天生带「仓库」

在 JavaScript 里,所有函数天生就有一个 prototype 属性 ,它就像一个「公共仓库」。这个仓库里的东西(属性或方法),能被函数创建的所有实例共享。

举个例子,我们定义一个 Car 构造函数:

js 复制代码
// 构造函数

function Car(color) {

 this.color = color; // 每个车的颜色可能不同,放在实例里

}

// 给构造函数的 prototype 加东西(公共属性)

Car.prototype.name = 'su7-Ultra';

Car.prototype.length = 4800; // 假设所有车长度相同

Car.prototype.run = function() {

 console.log('车在跑~');

};

这里的 Car.prototype 就是「公共仓库」。当我们用 new 创建实例时:

js 复制代码
const car1 = new Car('pink');

const car2 = new Car('blue');

car1car2 都会「共享」prototype 里的 namelengthrun 方法。你可以直接访问它们:

js 复制代码
console.log(car1.name); // 'su7-Ultra'(来自原型)

console.log(car2.run()); // '车在跑~'(来自原型)

为什么要这么设计?

如果把 namerun 这些公共属性直接写在构造函数里,每次创建实例都会重复创建一遍,就像每个小区都自己造一套共享健身器材,纯属浪费内存。原型就像小区的「公共健身区」,大家共用一套,既省空间又好维护。

对象的「隐形连接」:__proto__(隐式原型)

每个对象(包括函数创建的实例)都有一个 __proto__ 属性,它指向创建这个对象的构造函数的 prototype。简单说就是:

js 复制代码
实例对象.__proto__ === 构造函数.prototype

还是用上面的 Car 举例:

js 复制代码
console.log(car1.__proto__ === Car.prototype); // true

这就像每个小区居民都知道「公共健身区」在哪 ------__proto__ 就是实例对象找到「公共仓库」的地图。

js 复制代码
function Car() {
  this.name = 'su7'
}
const car = new Car()// {name: 'su7'}
console.log(car.constructor); // 输出 Car函数,从原型上继承来的
// 让每一个实例对象都能知道自己是由谁创建出来的

new 关键字到底干了啥?

当你用 new 创建实例时,JavaScript 悄悄做了 5 件事(堪称「幕后黑手」):

  1. 创建空对象const obj = {}(先搭个空架子)

  2. 绑定 this :让构造函数里的 this 指向这个空对象(Car.call(obj)

  3. 执行构造函数 :给空对象加属性(比如 obj.color = color

  4. 连接原型obj.__proto__ = Car.prototype 将这个空对象的隐式原型(proto) 赋值成 构造函数的显示原型(prototype)(给空对象一张「地图」)

  5. 返回对象:把这个打扮好的对象 return 出去

所以 const car1 = new Car('pink') 其实是 JavaScript 帮你完成了一系列操作,最终得到一个带属性、连原型的对象。

js 复制代码
Car.prototype.run = function() {
  console.log('running');
}

function Car() {   // new Function()
  // const obj = {}  //// 步骤1:创建空对象
  // Car.call(obj)  // 步骤2   call 方法将 Car 函数中的 this = obj
  this.name = 'su7'  // 步骤3
  // obj.__proto__ = Car.prototype  // 步骤4:连接原型
  // return obj  //步骤5:返回对象
}
const car = new Car() // {name: 'su7'}.__proto__  == Car.prototype

car.run()

实例与原型的爱恨情仇

实例可以访问原型上的属性,但想修改原型上的属性可没那么容易:

js 复制代码
Person.prototype.say = function() {
  console.log('想吃漂亮饭');
}

function Person() {
  this.name = '小橘'
}
const p = new Person()// p 里面显示拥有一个属性 name 隐式拥有一个属性 say
p.say = 'hello' // 这只是给p加了个say属性,不是修改原型上的

console.log(p); // {name: '小橘', say: 'hello'}

这就像你可以用公司的打印机(原型上的方法),但你自己买了台新打印机(实例上的属性),并不会影响公司那台。

原型链:对象的「向上查找」神功

v8 在访问对象中的属性时,会先访问该对象中的显示属性,如果找不到,就去对象的隐式原型__proto__ 上找,如果还找不到,就去__proto__.proto 上找,层层往上,直到找到null为止。这种查找关系被称为 原型链.

举个生活例子:

你想吃西瓜(访问 xigua 属性):

  • 先翻自己的冰箱(对象本身),没有;

  • 去客厅的公共冰箱(__proto__ 指向的原型),还没有;

  • 去小区便利店(__proto__.__proto__),找到了!

代码演示:

js 复制代码
Grand.prototype.house = function() {
  console.log('四合院');
}
function Grand() {
  this.card = 10000
}
Parent.prototype = new Grand()  // {card: 10000}.__proto__ =Grand.prototype= Grand.prototype.__proto__ = Object.prototype.__proto__ = null
function Parent() {
  this.lastName = '张'
}
Child.prototype = new Parent()  // {lastName: '张'}.__proto__ = Parent.prototype
function Child() {
  this.age = 18
}
const c = new Child()  // {age: 18}.__proto__ = Child.prototype
console.log(c.card);
c.house()
console.log(c.toString());

现在访问 child.house()

  • child 自身没有 house 方法;

  • child.__proto__(爸爸的实例),没有;

  • child.__proto__.__proto__(爷爷的实例),没有;

  • child.__proto__.__proto__.__proto__(爷爷的 prototype),找到了!执行 house() 方法。

这就是原型链的查找过程,是不是像「刨根问底」一样?

实战技巧:给内置对象加方法

JavaScript 的内置对象(比如 ArrayObject)也有原型。我们可以给它们的原型加方法,让所有实例都能用。

比如给数组加一个 abc 方法:

js 复制代码
// 给 Array 的原型加方法

Array.prototype.abc = function() {

 return '我是数组的新方法~';

};

// 所有数组都能用

const arr = [];

console.log(arr.abc()); // 输出'我是数组的新方法~'

不过要注意:别随便修改内置对象的原型,可能会和其他代码冲突(比如别人也加了同名方法)。

总结:原型家族关系图

最后用一张「关系图」帮你理清:

js 复制代码
构造函数(Car)

 ↓ 拥有

prototype(仓库)

 ↓ 被指向

实例对象(car1)的 __proto__

 ↓ 向上找

prototype 的 __proto__(比如 Object.prototype)

 ↓ 直到

null(原型链的终点)

记住这几点,原型就难不倒你了:

  1. 函数有 prototype(公共仓库);

  2. 对象有 __proto__(指向构造函数的 prototype);

  3. new 会帮你绑定原型关系;

  4. 属性查找顺着原型链往上走。

原型机制是 JavaScript 的核心,理解它能让你在写代码时更得心应手。下次再遇到原型相关的问题,不妨回头看看这篇文章,说不定会有新收获~

相关推荐
ohyeah1 小时前
使用 LocalStorage 实现本地待办事项(To-Do)列表
前端·javascript
Jing_Rainbow1 小时前
【前端三剑客-6/Lesson11(2025-10-28)构建现代响应式网页:从 HTML 到 CSS 弹性布局再到 JavaScript 交互的完整指南 🌈
前端·javascript
6***37941 小时前
JavaScript虚拟现实开发
开发语言·javascript·vr
非专业程序员1 小时前
精读 GitHub - servo 浏览器(一)
前端·ios·rust
Yanni4Night1 小时前
掌握 JS 中迭代器的未来用法
前端·javascript
Irene19911 小时前
Element UI 及其 Vue 3 版本 Element Plus 发展现状
前端·vue.js·ui
Account_Ray1 小时前
vue3 的专属二维码组件 vue3-next-qrcode 迎来 4.0.0 版本
前端·vue.js·nuxt.js
BBB努力学习程序设计1 小时前
Web App开发入门:页面分析与环境准备全攻略
前端·html
BBB努力学习程序设计1 小时前
超好用的轮播图神器:Swiper插件入门指南
前端·html