JavaScript原型继承中引用类型的陷阱与最佳实践

作者:木棉

时间:2026-10-27

浏览:212

本文探讨JavaScript原型继承中常见的引用类型共享陷阱。通过对比基本数据类型与对象、数组在原型链上的表现,揭示修改子类实例如何意外影响父类。文章建议数据属性通过this赋值,方法通过原型继承,并介绍了Object.create及自定义类工厂函数的最佳实践,以避免隐蔽的Bug。

文章目录

JavaScript原型继承基础

JavaScript默认采用原型继承机制。尽管语言本身没有传统的类(class)概念,但函数(function)可以作为构造器(constructor)使用。结合this关键字和new操作符,可以构建出类似Java等语言的类式结构。因此,JavaScript通过扩展自身能力,能够模拟类式(class-based)继承模式。

与大多数面向对象语言一样,JavaScript中的对象类型采用引用传递方式。持有对象的变量实际上存储的是内存地址,而基本类型数据则是值传递。当在原型上存储对象引用时,可能会引发一些不易察觉的陷阱。

首先通过一个简单示例进行说明。

相关代码如下:

var create = function() {
function Fn() {}
return function(parent) {
Fn.prototype = parent
return new Fn
    } 
}() 
var parent = {
name: 'jack',
age: 30,
isMarried: false
} 
var child = create(parent)
console.log(child)

上述create工具函数实现了一个基础的原型继承逻辑。每次调用create时,都会基于parent对象创建一个新的子对象,该子对象的所有属性均源自parent。在此示例中,parent包含三个基本数据类型属性:字符串、数字和布尔值。

接下来,尝试修改child对象,观察是否会影响parent对象。

相关代码如下:

child.name = 'lily'
child.age = 20,
child.isMarried = true
console.log(child)
console.log(parent)

运行结果如下

JavaScript原型继承基础

结果显示,修改child对象的属性并不会影响parent对象。这是因为基本数据类型的赋值是值拷贝,child拥有自己独立的属性副本。

引用类型共享陷阱

再看另一个涉及引用类型的例子。

相关代码如下:

var create = function() {
function Fn() {}
return function(parent) {
Fn.prototype = parent
return new Fn
    } 
}() 
var parent = {
data: {
name: 'jack',
age: 30,
isMarried: false
    }, 
language: ['Java']
} 
var child = create(parent)
child.data.name = 'lily'
child.data.age = 20
child.data.isMarried = true
child.language.push('javascript')
console.dir(child)
console.dir(parent)

注意,此例中parent的两个属性data和language均为引用类型,分别是一个对象和一个数组。child依然继承自parent,随后对child进行了修改,结果如下

JavaScript原型继承基础

可以看到,此时parent对象也被修改了,其name、age等属性值与child完全一致。这是使用原型继承时需要特别注意的陷阱:当原型上存储的是引用类型时,所有通过该原型创建的子类实例将共享同一份数据。

最佳实践与工具函数

为了规避此类问题,使用继承时建议采用以下最佳实践:

1. 数据属性应采用类式继承方式,即挂在this上,这样在new实例时也可以通过参数进行配置。

2. 方法应采用原型继承,这样既能节省内存,又允许子类重写方法而不影响父类。

下面是一个满足上述两点要求的类编写工具函数示例。

相关代码如下:

/** 
* @param {String} className
* @param {String/Function} superCls
* @param {Function} factory
 */
function $class(name, superClass, factory) {
if (superClass === '') superClass = Object
function clazz() {
if (typeof this.init === 'function') {
this.init.apply(this, arguments)
        } 
    } 
var p = clazz.prototype = new superCls
clazz.prototype.constructor = clazz
clazz.prototype.className = className
var supr = superCls.prototype
window[className] = clazz
factory.call(p, supr)
}

当对象类型被放置在父类原型上时,务必小心子类对其修改。否则,继承于该父类的所有子类实例都将受到相同影响,导致难以排查的Bug。

ES5 Object.create替代方案

ES5标准中引入了一个新的API来实现原型继承:Object.create。可以使用它替代上述自定义的create函数,示例如下

相关代码如下:

var parent = {
name: 'jack',
age: 30,
isMarried: false
} 
var child = Object.create(parent)
console.log(child)

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章