JavaScript 原型与原型链深入解析及继承实现方案
本文深入解析JavaScript中一切皆对象的特性,详细阐述原型与原型链的工作机制。通过构造函数与实例对象的关系,说明prototype与__proto__的作用。重点探讨两种继承模式:构造函数间的原型链继承及利用Object.create实现的对象继承,并分析直接赋值带来的副作用与解决方案。
原型与原型链基础概念
本文通过具体实例,详细解析JavaScript中原型与原型链的核心概念及其实际应用,旨在帮助读者深入理解并掌握相关技术。
在JavaScript语言中,所有事物本质上都是对象。由于缺乏传统面向对象语言中Class类的概念,直接表达对象间的层级与关联关系变得较为复杂。
以对象A和对象B为例,若二者相互独立,修改A的属性不会影响B的状态,这种独立性在逻辑上是合理的。
然而,若对象A与对象B都拥有完全相同的run()方法,且各自保存一份完整的代码副本,将导致不必要的内存资源消耗。
当程序中创建大量对象时,这种重复存储带来的资源开销将变得显著。因此,是否存在一种机制,使多个对象共享公共属性和方法,并建立它们之间的联系?
我们可以设想存在一个名为common的公共对象,其中存储了共有的属性和方法。此时,对象A和对象B内部均包含一个prototype属性,该属性指向这个common对象。
当访问对象A或B的属性和方法时,若在当前对象中未找到,解释器会沿着prototype属性指向的目标对象继续查找。
此外,common对象自身也拥有prototype属性,指向更高层级的公共对象。查找过程持续向上进行,直到遇到null为止,此时停止查找。
这种自下而上的查找路径构成了所谓的“原型链”,而被指向的公共对象则被称为“原型对象”。
接下来,我们通过一个具体的构造函数示例来进一步说明。
构造函数与原型对象关系
function Base(name) {
this.name = name;
}
let A = new Base('A');
let B = new Base('B');
//每一个函数都有一个prototype属性,指向该函数的原型对象
console.log(Base.prototype);
//当然原型对象也是一个对象,它也有一个constructor,指向构造函数
console.log(Base.prototype.constructor === Base);
//每一个实例对象的constructor都指向创建它们的构造函数
console.log(A.constructor === Base);
console.log(B.constructor === Base);
//每一个实例对象都有一个__proto__属性,该属性指向构造函数的原型对象
console.log(A.__proto__ === Base.prototype);
console.log(B.__proto__ === Base.prototype);
首先,每个函数都具备一个prototype属性,该属性指向该函数对应的原型对象。
其次,原型对象本身也是对象,因此它拥有自己的constructor属性,指向构造函数Base()。这意味着原型对象实际上是构造函数Base()的一个特殊实例,专门用于存放公共属性和方法。
第三,通过构造函数Base()创建的每个实例对象,都拥有constructor属性,该属性指向创建它的构造函数。
第四,每个对象都包含一个__proto__属性,该属性指向构造函数Base()的原型对象。__proto__是将各个原型串联成链的关键,若缺失此属性,对象A与B将无法访问原型对象上的公共属性和方法。
function Base(name) {
this.name = name;
}
//我们在原型对象上添加公共属性
Base.prototype.status = '开始';
//我们在原型对象上添加公共方法
Base.prototype.run = function() {
console.log(this.name + ' run ...');
};
let A = new Base('A');
let B = new Base('B');
A.run();
B.run();
console.log(A.status);
console.log(B.status);
//修改原型上的属性,则实例对象也会跟着改变
Base.prototype.status = '停止';
console.log(A.status);
console.log(B.status);
借助原型与原型链机制,对象之间建立了明确的关联关系。
那么,如何利用原型与原型链实现构造函数之间的继承?
假设我们需要让构造函数Child继承自构造函数Base,直观的想法是让Child的prototype指向Base的原型对象。
构造函数间的原型链继承
function Base(name) {
}
Base.prototype.name = 'Base';
Base.prototype.run = function () {
console.log(this.name + ' run ...');
};
function Child() {
}
Child.prototype = Base.prototype;
//注意这个时候,Child.prototype对象的constructor属性指向了Base
//这就导致通过构造函数Child创建的实例对象,对象的constructor属性会指向Base,而不是Child,这会导致混乱。
//所以我们重新设置Child.prototype.constructor指向Child
Child.prototype.constructor = Child;
let c = new Child();
console.log(c.name);
c.run();
但这种做法存在缺陷:Child.prototype与Base.prototype将指向同一个原型对象,对Child.prototype的任何修改都会直接反映到Base.prototype上。
此时,Base.prototype.constructor将错误地指向Child,这显然不符合预期。
为了解决这一问题,我们需要引入一个中间的空构造函数来完成原型的正确指向。
function Base(name) {
}
Base.prototype.name = 'Base';
Base.prototype.run = function () {
console.log(this.name + ' run ...');
};
function Child() {
}
//创建一个中间的空构造函数
function Mid() {
}
//让该空构造函数的prototype指向Base的原型对象
Mid.prototype = Base.prototype;
//再让Child的prototype指向该空构造函数的一个实例
Child.prototype = new Mid();
//这样,当修改Child.prototype.constructor时,Base.prototype就不会受影响了
Child.prototype.constructor = Child;
let c = new Child();
console.log(c.name);
c.run();
//Base.prototype的constructor仍然指向Base,没有受到影响
console.log(Base.prototype.constructor);
接下来探讨如何通过原型与原型链实现对象对另一个对象的继承。
例如,若要让对象B继承对象A,本质目标是获取对象A的属性和方法。直观做法是将对象B的__proto__指向对象A。
对象间的继承与Object.create应用
let A = {
name: 'A',
run() {
console.log(this.name + ' run ...');
}
};
console.log(A.name);
A.run();
let B = {};
//让对象B的__proto__指向对象A
B.__proto__ = A;
//当对象B调用run()方法时会在自身上找,如果没找到,则通过__proto__向上找
//由于__proto__指向对象A,所以最终会在对象A中找到run()方法
B.run();
B.__proto__.name = 'B';
console.log(A.name);
console.log(B.name);
然而,这种方法存在问题:当执行B.__proto__.name = 'B'时,对象A的name属性也会随之改变,导致数据污染。
为了解决上述问题,可以利用ES5提供的Object.create()方法。该方法允许通过指定的原型对象创建新对象,从而避免直接修改原型链带来的副作用。
let A = {
name: 'A',
run() {
console.log(this.name + ' run ...');
}
};
console.log(A.name);
A.run();
let B = {};
//通过Object.create()创建一个以对象A为原型对象的新对象
//让对象B的__proto__指向该新对象
//这样再操作B.__proto__中的属性就与对象A无关了。
B.__proto__ = Object.create(A);
B.run();
B.__proto__.name = 'B';
console.log(A.name);
console.log(B.name);
感兴趣的用户可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码的运行效果。
更多关于JavaScript相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript查找算法技巧总结》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》
希望本文所述内容能对读者的JavaScript程序设计有所帮助。
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28
