一文搞懂 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');
car1 和 car2 都会「共享」prototype 里的 name、length 和 run 方法。你可以直接访问它们:
js
console.log(car1.name); // 'su7-Ultra'(来自原型)
console.log(car2.run()); // '车在跑~'(来自原型)

为什么要这么设计?
如果把 name、run 这些公共属性直接写在构造函数里,每次创建实例都会重复创建一遍,就像每个小区都自己造一套共享健身器材,纯属浪费内存。原型就像小区的「公共健身区」,大家共用一套,既省空间又好维护。
对象的「隐形连接」:__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 件事(堪称「幕后黑手」):
-
创建空对象 :
const obj = {}(先搭个空架子) -
绑定 this :让构造函数里的
this指向这个空对象(Car.call(obj)) -
执行构造函数 :给空对象加属性(比如
obj.color = color) -
连接原型 :
obj.__proto__ = Car.prototype将这个空对象的隐式原型(proto) 赋值成 构造函数的显示原型(prototype)(给空对象一张「地图」) -
返回对象:把这个打扮好的对象 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 的内置对象(比如 Array、Object)也有原型。我们可以给它们的原型加方法,让所有实例都能用。
比如给数组加一个 abc 方法:
js
// 给 Array 的原型加方法
Array.prototype.abc = function() {
return '我是数组的新方法~';
};
// 所有数组都能用
const arr = [];
console.log(arr.abc()); // 输出'我是数组的新方法~'
不过要注意:别随便修改内置对象的原型,可能会和其他代码冲突(比如别人也加了同名方法)。
总结:原型家族关系图
最后用一张「关系图」帮你理清:
js
构造函数(Car)
↓ 拥有
prototype(仓库)
↓ 被指向
实例对象(car1)的 __proto__
↓ 向上找
prototype 的 __proto__(比如 Object.prototype)
↓ 直到
null(原型链的终点)

记住这几点,原型就难不倒你了:
-
函数有
prototype(公共仓库); -
对象有
__proto__(指向构造函数的prototype); -
new会帮你绑定原型关系; -
属性查找顺着原型链往上走。
原型机制是 JavaScript 的核心,理解它能让你在写代码时更得心应手。下次再遇到原型相关的问题,不妨回头看看这篇文章,说不定会有新收获~