JavaScript 是一门基于原型(Prototype-based)的面向对象编程语言,不同于 Java、C++ 等基于类的面向对象语言,JS 没有真正的类与实例的静态编译关系,其对象复用、属性查找、代码复用、层级复用完全依托原型与原型链机制实现。
原型、prototype、__proto__、原型链、构造函数、继承体系、Function 与 Object 底层关系,是 JS 面向对象体系的底层核心,也是前端进阶、源码阅读、框架原理、面试必考的核心知识体系。本文将从底层原理出发,系统性拆解原型机制、六种经典继承方案、顶层构造关系,并配套高频面试真题,实现理论与实战一体化掌握。
一、JavaScript原型核心基础体系
1.1 原型的核心定义
在 JavaScript 中,每个对象都拥有一个内置原型对象。对象可以自动访问原型上的属性与方法,这种机制称为原型委托。原型的核心设计目的是:实现属性与方法的共享复用,减少内存冗余,实现对象的层级继承。
JS 中所有引用类型(对象、数组、函数、正则、日期)均基于原型体系构建,所有内置方法(toString、hasOwnProperty、push 等)全部挂载在原型对象上,而非实例自身。
1.2 prototype 与 __proto__ 核心区分
原型体系存在两个极易混淆的核心属性,二者是原型链构建的核心,职责完全不同。
1.2.1 prototype(显式原型)
prototype 是构造函数专属属性,仅函数拥有。
1、作用:存储实例共享的属性与方法,供所有实例对象委托访问;
2、属性归属:只有函数具备 prototype,普通对象没有;
3、核心价值:实现实例之间的方法共享,节省内存。
1.2.2 __proto__(隐式原型)
__proto__ 是所有对象专属属性(包含函数对象、数组对象、普通对象)。
1、作用:指向创建当前对象的构造函数的 prototype,用于构建原型链查找关系;
2、属性归属:所有引用类型对象都拥有;
3、规范说明:__proto__ 是非标准废弃访问器属性,现代开发推荐使用 Object.getPrototypeOf() / Object.setPrototypeOf() 替代。
1.3 核心对应关系公式(底层核心)
// 核心恒等式
实例对象.__proto__ === 构造函数.prototype
// 示例
function Person(){}
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
1.4 constructor 构造器属性
每个构造函数的 prototype 上默认自带 constructor 属性,指向当前构造函数本身,用于标记实例的构造来源。
console.log(Person.prototype.constructor === Person); // true
console.log(p.constructor === Person); // true
在继承过程中,constructor 属性极易丢失,是继承方案优化的关键细节。
二、原型链运行机制
2.1 原型链定义
当对象的 __proto__ 逐级向上指向原型对象,形成的层级查找链条,即为原型链。原型链是 JS 实现属性查找、继承复用的唯一底层机制。
2.2 原型链查找规则
1、访问对象属性/方法时,优先查找对象自身;
2、自身不存在则通过 __proto__ 向上查找原型;
3、逐级向上查找,直至 Object.prototype;
4、Object.prototype.__proto__ === null,为原型链的顶层终点;
5、查找终点仍未找到属性,返回 undefined。
2.3 原型链顶层结构
所有普通对象最终都继承自 Object.prototype,所有函数最终继承自 Function.prototype,构成 JS 完整的原型顶层闭环。
三、Function 与 Object 终极底层关系(重难点)
Function 和 Object 的互成闭环关系,是 JS 原型体系的终极考点,也是绝大多数开发者的知识盲区。
3.1 基础关系梳理
1、Object 是所有对象的祖宗:所有引用类型最终都继承自 Object.prototype;
2、Function 是所有函数的祖宗:所有函数(包含内置函数、自定义函数)都继承自 Function.prototype;
3、函数本身也是对象,因此函数既属于 Function 实例,也属于 Object 体系。
3.2 核心闭环公式
// 1. 所有函数 __proto__ 指向 Function.prototype
console.log(Object.__proto__ === Function.prototype); // true
console.log(Function.__proto__ === Function.prototype); // true
// 2. 所有原型对象都是普通对象,继承自 Object.prototype
console.log(Function.prototype.__proto__ === Object.prototype); // true
// 3. 顶层终点
console.log(Object.prototype.__proto__ === null); // true
3.3 关系总结
1、Function 构造了所有函数,包括 Object、Function 自身;
2、Object 构造了所有对象,包括所有原型对象;
3、Function 源于自身,Function.prototype 源于 Object,形成完美闭环。
四、JavaScript 六种经典继承方式(完整演进)
JS 没有原生类继承,所有继承均基于原型链模拟实现。从 ES5 到 ES6 共演进出六种标准继承方案,各有优劣、适配不同场景,是面试与工程化高频考点。
4.1 原型链继承(最基础)
原理
将子类原型直接指向父类实例,实现原型链挂靠。
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
}
Parent.prototype.say = function(){ console.log(this.name); }
function Child(){}
// 核心继承代码
Child.prototype = new Parent();
const c1 = new Child();
console.log(c1.name);
缺陷
1、父类引用类型属性被所有实例共享,一个实例修改数组,所有实例同步变更;
2、子类无法向父类传参;
3、丢失 constructor 指向。
4.2 构造函数继承(借用父构造)
原理
在子类构造函数中通过 call/apply 借用父类构造函数,复制父类实例属性。
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
}
function Child(name) {
Parent.call(this, name); // 核心
}
const c1 = new Child("张三");
const c2 = new Child("李四");
优点
解决引用类型共享问题、支持子类传参。
缺陷
只能继承实例属性,无法继承原型方法,造成方法无法复用。
4.3 组合继承(原型链+构造函数,ES5经典)
原理
结合前两种方式:构造函数继承实例属性,原型链继承原型方法。
function Parent(name) {
this.name = name;
this.arr = [1,2,3];
}
Parent.prototype.say = function(){console.log(this.name)}
function Child(name) {
Parent.call(this, name); // 继承属性
}
Child.prototype = new Parent(); // 继承方法
Child.prototype.constructor = Child; // 修复构造器指向
缺陷
父类构造函数执行两次,造成原型上存在一份冗余属性。
4.4 原型式继承(Object.create)
原理
依托 Object.create() 创建一个空对象,挂靠指定原型。适用于单纯对象继承。
const parent = {name:"父类"};
const child = Object.create(parent);
缺陷
依然存在引用属性共享问题,无法实现严格的类继承。
4.5 寄生式继承
原理
基于原型式继承,封装一层增强函数,为新对象扩展方法。
缺陷:无法复用函数,每次继承都会创建新方法,内存冗余。
4.6 寄生组合继承(ES5 最优继承)
原理
通过 Object.create 直接继承父类原型,不执行父类构造函数,彻底解决两次执行问题,是ES5 最完美继承方案。
function Parent(name){
this.name = name;
}
Parent.prototype.say = function(){}
function Child(name){
Parent.call(this, name);
}
// 核心最优继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
4.7 ES6 Class extends 继承(语法糖)
ES6 class 继承是寄生组合继承的语法糖,底层完全基于原型机制实现,通过 super 实现父构造调用,是现代开发标准继承方案。
class Parent {
constructor(name){
this.name = name;
}
say(){}
}
class Child extends Parent {
constructor(name){
super(name);
}
}
五、原型体系扩展特性与易错点
5.1 原型属性遮蔽
当实例自身属性与原型属性重名时,自身属性优先遮蔽原型属性,原型属性不会被修改。
5.2 hasOwnProperty 属性判断
hasOwnProperty() 仅判断自身属性,不遍历原型链,可用于区分自身属性与继承属性。
5.3 原型动态性
原型对象的修改会实时同步给所有实例,原型链查找是动态实时的。
六、高频面试真题解析(原型与继承专属)
真题一:prototype 与 __proto__ 区别(口述必考)
标准答案:
1、prototype 是构造函数独有的显式原型,用于存放实例共享方法;
2、__proto__ 是所有对象独有的隐式原型,用于指向构造函数的 prototype,构建原型链;
3、核心等式:实例.__proto__ === 构造函数.prototype;
4、__proto__ 非标准,推荐使用 Object.getPrototypeOf。
真题二:原型链顶层关系判断题
题目:判断以下代码输出
console.log(Function.__proto__ === Function.prototype);
console.log(Function.prototype.__proto__ === Object.prototype);
console.log(Object.prototype.__proto__);
答案:true、true、null
解析:Function 自构造,所有函数原型继承 Object,Object 原型为顶层终点。
真题三:原型链继承引用类型共享坑
题目:写出代码输出
function Parent(){
this.arr = [1,2,3];
}
Parent.prototype.name = "父类";
function Child(){}
Child.prototype = new Parent();
const c1 = new Child();
const c2 = new Child();
c1.arr.push(4);
console.log(c2.arr);
答案:[1,2,3,4]
解析:原型链继承中,所有实例共享父类引用属性,一个修改全部生效,是原型链继承的致命缺陷。
真题四:六种继承方式优缺点总结(背诵题)
标准面试总结:
1、原型链继承:共享引用属性、无法传参;
2、构造函数继承:可传参、隔离引用,无法继承原型方法;
3、组合继承:功能齐全,父类执行两次存在冗余;
4、原型式/寄生式继承:适合对象继承,不适合类继承;
5、寄生组合继承:ES5 最优,无冗余、无共享缺陷;
6、ES6 extends:语法简洁、底层寄生组合、工程化首选。
真题五:属性遮蔽与原型查找
题目:分析输出结果
function Person(){}
Person.prototype.a = 10;
const p = new Person();
p.a = 20;
console.log(p.a);
答案:20
解析:自身属性优先级高于原型属性,发生属性遮蔽,不会修改原型值。
真题六:instanceof 底层原理
面试题:简述 instanceof 原理
答案:遍历实例的原型链,判断构造函数的 prototype 是否出现在实例的原型链上,存在返回 true,否则 false。
七、全文核心终极总结(可直接背诵)
1、原型核心:prototype 供函数存共享方法,__proto__ 构建原型链;
2、原型链机制:由内向外逐级查找,终点为 Object.prototype.__proto__ = null;
3、Function与Object关系:Function 构造所有函数,Object 构造所有对象,形成顶层闭环;
4、继承演进核心:从共享缺陷 → 无法继承方法 → 冗余执行 → 寄生组合最优;
5、现代规范:ES6 extends 是寄生组合继承语法糖,为工程化首选;
6、高频坑点:原型引用属性共享、constructor 丢失、原型链动态查找、属性遮蔽。