JavaScript原型继承中引用类型的陷阱与最佳实践
本文探讨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)
运行结果如下

结果显示,修改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进行了修改,结果如下

可以看到,此时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)JavaScript怎么准确判断数据类型:typeof、instanceof与Object.prototype.toString
2026-11-05 17:09:21
JS中将数组和对象混合组装为JSON字符串
2026-11-05 16:41:42
JS运算符优先级与结合性的区别是什么?
2026-11-05 13:22:02
JS Map怎么只遍历Key或Value
2026-11-05 13:00:36
D3.js 与 ECharts 选型对比:灵活性 vs 开发效率
2026-11-05 09:45:01
