JavaScript 原型与原型链核心机制深度解析
本文深入解析 JavaScript 原型与原型链机制,详细阐述 prototype 与 __proto__ 的区别与联系。通过图解与代码示例,说明实例、构造函数与原型对象之间的关系,并详解 new、instanceof 的实现原理及组合继承、寄生组合式继承等常见继承模式。
前言
在前端开发领域,原型与原型链是每位前端开发者必须掌握的核心概念。无论是在技术面试还是技术博客中,这一话题出现的频率极高,足见其重要性。尽管其原理本身并不复杂,但许多初学者在面对 prototype 和 __proto__ 时往往感到困惑,导致思维混乱。这完全是正常现象,学习任何技能都需要循序渐进,就像先学会走路才能跑步一样。接下来,我们将一起攻克这一难点。
通过阅读本文,你将掌握以下核心知识点:
- 理解
__proto_; - 理解
prototype; - 理解
javascript中对象的概念; - 理解原型和原型链;
- 理解
javascript中类的概念; - 理解
new的实现; - 理解
instanceof的实现; - 理解
javascript的继承; - 加深对
javascript这门语言的理解。
上述内容构成了本文的写作逻辑与思路。
对象
我们从对象这一概念入手。虽然“JavaScript 中万物皆是对象”这一说法广为流传,但它并不准确。例如,在 javasript 中,简单基本类型(string、boolean、number、null、undefined、symbol)本身并非对象。实际上,javasript 中的对象主要分为函数对象和普通对象两类。
具体分类如下:
StringNumberBooleanObjectFunctionArrayDateRegExpError
上述列出的均为函数对象,它们也被统称为内置对象。函数对象本质上是一个纯函数,而 javascript 利用它们来模拟类。
普通对象则更为直观,即我们日常开发中常见的对象形式:
const obj = {
name: "juefei",
desc: "cool",
};如果此时你仍对函数对象和普通对象的区别感到模糊,可以通过以下代码进行直观对比:
const obj1 = {};
const obj2 = new Object();
function func1() {}
const obj3 = new func1();
const func2 = new (function () {})();
const func3 = new Function();接下来,我们分别打印上述对象以观察结果:
console.log(obj1); // object
console.log(obj2); // object
console.log(obj3); // object
console.log(func1); // function
console.log(func2); // function
console.log(func3); // function从输出结果可见,obj1、obj2以及obj3均属于普通对象,它们都是Object的实例;而func1、func2和func3则是Function的实例,被称为函数对象。
我们再观察另一种情况:
console.log(typeof Object); //f unction
console.log(typeof Function); // function结果可能令人惊讶,原来Object和Function实际上都是 Function的实例。
由此我们可以得出一个关键结论:
- 只要是
Function的实例,它就属于函数对象,其余情况则归为普通对象。
同样可以看出,不仅 Object是函数对象,就连 Function本身也是函数对象,因为通过 console.log(typeof Function); 验证得知 Function是 Function的实例。这可能会让人感到有些绕口,但请牢记这一结论:
- 只要是
Function的实例,那就是函数对象,其余则为普通对象。
既然对象通常通过构造函数使用 new 关键字创建,这便与原型和原型链密切相关。那么,原型和原型链的具体作用是什么呢?
原型
在探讨这两个概念之前,我们需要先引入两个关键变量:__proto__和prototype。这两个属性在 javascript中无处不在,我们先通过一张表格来直观展示它们的分布:
| 对象类型 | __proto__ | prototype |
|---|---|---|
| 普通对象 | ✅ | ❌ |
| 函数对象 | ✅ | ✅ |
请牢记以下结论:
- 只有
函数对象拥有prototype属性,普通对象不具备此属性。 函数对象和普通对象均拥有__proto__属性。prototype和__proto__是在创建函数或对象时自动生成的属性。
接下来我们通过代码进行验证:
function func() {
//func称为构造函数
}
console.log(typeof func.prototype); // object
console.log(typeof func.__proto__); // functionconst obj = {};
console.log(typeof obj.__proto__); //object
console.log(typeof obj.prototype); //undefined (看见了吧,普通对象真的没有 prototype 属性)这证实了我们之前的结论:
- 只有
函数对象具备prototype属性,普通对象没有该属性。 函数对象和普通对象均拥有__proto__属性。prototype和__proto__是在创建函数或对象时自动生成的属性。
此处重复强调并非为了凑字数,而是为了加深记忆,这对后续内容的理解至关重要。
接着观察以下代码:
console.log(obj.__proto__ === Object.prototype); // true
console.log(func.__proto__ === Function.prototype); // true由此我们得出如下结论:
- 实例的
__proto__属性主动指向构造函数的prototype; prototype属性被__proto__属性所指向。
这就是prototype属性和__proto__属性的区别与联系。如果觉得复杂,请多阅读本节并背诵结论。那么问题来了,既然func是一个函数对象,而函数对象拥有 prototype 属性,那么func.prototype.__proto__等于什么?为了解决这个问题,我们首先看看func.prototype是什么:
console.log(typeof func.prototype); //object现在我们知道,func.prototype是一个对象。既然是对象,那么 func.prototype不就是 Object的实例吗?这意味着,func.prototype.__proto__属性肯定是指向Object.prototype的。我们来验证一下:
console.log(func.prototype.__proto__ === Object.prototype); //true结果正如预期。至此,你应该已经明白,当创建一个构造函数时,JavaScript 是如何自动为我们生成__proto__和prototype属性的。具体过程如下:
//我们手动创建func函数
function func() {}
//javascript悄悄咪咪执行以下代码:
func._proto = Function.prototype; //实例的 __proto__ 属性主动指向构造的 prototype
func.prototype = {
constructor: func,
__proto: Object.prototype, //我们刚刚才在上面验证的,你别又忘记了
};我还专门为你绘制了示意图(希望能帮到你):

因此,prototype被称为显式原型对象,而__proto__被称为隐式原型对象。
看到这里,你是否感觉豁然开朗?如果仍有疑惑,请重新阅读上述章节,建议配合纸笔绘图并在电脑上运行示例代码。整理好思路后,我们接下来探讨原型链。
原型链
在介绍这一概念之前,我们先看如下代码:
function Person = function(name,desc){
this.name = name;
this.desc = desc;
} //***1****//
Person.prototype.getName = function(){
return this.name;
}//***2****//
Person.prototype.getDesc = function(){
return this.desc;
}//***3****//
const obj = new Person('juefei','cool');//***4****//
console.log(obj);//***5****//
console.log(obj.getName);//***6****//接下来我们逐步解析:
- 创建了一个构造函数
Person,此时,Person.portotype自动创建,其中包含constructor和__proto__两个属性; - 给对象
Person.prototype新增了一个方法getName; - 给对象
Person.prototype新增了一个方法getDesc; - 根据构造函数
Person新建一个实例:obj(在创建实例时,构造函数会自动执行); - 打印实例
obj:
{
name: 'juefei',
desc: 'cool'
}根据上一节的结论,我们得出:
obj.__proto__ = Person.prototype;- 执行到第 6 步时,由于在实例
obj上找不到getName()这个方法,它就会自动通过自身的__proto__继续向上查找,结果找到了Person.prototype。接着它发现,Person.prototype上恰好有getName()方法,于是找到了该方法并停止寻找。这种环环相扣的关系形成了链条,即原型链。
我们得出如下结论:
在访问一个对象(假设名为 obj)的属性/方法时,若在当前对象上找不到,则会尝试通过obj.__proto__去寻找。而 obj.__proto__ 又指向其构造函数(假设名为objCreated)的 prototype,因此它会自动去 objCreated.prototype 的属性/方法中查找。若仍未找到,则访问 objCreated.prototype.__proto__继续向上寻找,直到找到为止。若最终未能找到,则返回 undefined。一直沿原型链寻找,直到找到 Object.prototype.__proto__,它指向 null,于是返回 undefined。
这样理解是否自然?我再次为你绘制了示意图:

接下来补充一些额外概念:
- 任何
内置函数对象本身的__proto__属性都指向Function的原型对象,即:Function.prototype; - 除了
Object.prototype.__proto__指向null外,所有的内置函数对象的原型对象的__proto__属性 (内置函数对象.prototype.__proto__),都指向Object。
我们得出如下终极原型链的图:

针对此图,最终给出我们常看到的原型链示意图:

请仔细对比,拿出纸笔绘制,结合上述讲解,相信你很容易就能理解。
javascript中的类
刚才我们终于明白了 原型和 原型链。下面我们根据上述概念来讲解javascript中的类。我们知道,在面向对象语言中,类可以被实例化多次,这里的实例化是指我们可以根据构造函数独立复制多个独立的实例,这些实例之间相互独立。但在 javascript中并非如此,因为它不具备这种复制机制。我们不能创建一个类的多个实例,只能创建这个类的多个对象,因为它们都通过原型和原型链关联到同一个对象。因此在 javascript中,类都是通过原型和原型链来实现的,它其实是一种委托方式。
new的实现
了解了上述javascript中类的概念,理解new的过程应该很容易,其核心无非是执行原型链的链接:
function myNew(Cons, ...args) {
let obj = {};
obj.__proto__ = Cons.prototype; //执行原型链接
let res = Cons.call(obj, args);
return typeof res === "object" ? res : obj;
}instanceof的实现
学习了原型和原型链后,instanceof的实现也就很简单了,它也是通过原型和原型链来实现的:
function myInstanceof(left, right) {
let rightProto = right.prototype;
let leftValue = left.__proto__;
while (true) {
if (leftValue === null) {
return false;
}
if (leftValue === rightProto) {
return true;
}
leftValue = leftValue.__proto__;
}
}此处不再赘述过程,相信你能够看懂。
javascript的继承
我们知道继承也是通过原型和原型链来实现的,在此介绍两种常见的继承方式:
组合继承:
//组合式继承
//通过call继承Parent的属性,并传入参数
//将Child的原型对象指向Parent的实例,从而继承Parent的函数
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function () {
console.log(this.val);
};
function Child(value) {
Parent.call(this, value); //继承Parentd的属性
}
Child.prototype = new Parent();寄生组合式继承:
//寄生组合式继承
//通过call继承Parent的属性,并传入参数
//通过Object.create()继承Parent的函数
function Parent(value) {
this.val = value;
}
Parent.prototype.getValue = function () {
console.log(this.val);
};
function Child(value) {
//继承Parentd的属性
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
writable: true,
configurable: true,
enumerable: false,
},
});总结
- 若 A 通过 new 创建了 B,则
B.__proto__ = A.prototype; - 执行
B.a,若在 B 中找不到 a,则会在B.__proto__中,也就是A.prototype中查找。若A.prototype中仍然没有,则会继续向上查找,最终一定会找到Object.prototype。倘若还找不到,因为Object.prototype.__proto__指向null,因此会返回undefined; - 原型链的顶端,一定有
Object.prototype.__proto__——> null。
由此可见,原型和原型链机制如此强大。
到此这篇关于Javascript 原型与原型链深入详解的文章就介绍到这了,更多相关Javascript 原型链内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
JavaScript新手常犯的编码错误与最佳实践指南
2026-11-04 22:12:38
