本文通过两个JavaScript代码示例,深入解析原型继承中“=”赋值与引用调用的区别。第一种情况通过push修改共享数组,所有实例共享变更;第二种情况使用赋值操作符覆盖属性,仅当前对象拥有新属性,父类实例仍访问原型链上的原始值。理解这一差异对正确设计JS继承至关重要。
JavaScript原型继承中赋值与引用的区别
首先,我们来看一段传统的JavaScript继承代码示例。
代码如下:
// 定义超类
function Father(){
this.name = "父亲";
}
Father.prototype.theSuperValue = ["NO1","NO2"];
// 定义子类
function Child(){
}
// 实现继承
Child.prototype = new Father();
// 修改共享数组
Child.prototype.theSuperValue.push("修改");
// 创建子类实例
var theChild = new Child();
console.log(theChild.theSuperValue); //["NO1","NO2","修改"]
// 创建父类实例
var theFather = new Father();
console.log(theFather.theSuperValue); //["NO1","NO2","修改"] 观察上述代码中关键部分:子类Child的原型对象被设置为父类Father的一个实例(即 new Father())。当我们调用 new Father() 对象中的 theSuperValue 属性时,由于该实例本身没有此属性(仅有 name 属性),JavaScript 引擎会沿着原型链向上查找,最终在 Father.prototype 中找到该属性。这是一个数组,属于引用类型。此时,我们向该数组中添加了一个字符串“修改”。
随后,我们创建了子类实例 theChild。当访问 theChild 的 theSuperValue 属性时,首先检查实例自身,未找到后继续查找其原型对象(即 new Father()),仍未找到,最终在 Father.prototype 中找到了该数组,其值已变为 ["NO1","NO2","修改"]。
接着,我们创建了父类实例 theFather。同理,在访问 theSuperValue 时,由于实例自身没有该属性,引擎沿原型链向上查找,最终在 Father.prototype 中找到了同一个引用类型的数组,其值同样为 ["NO1","NO2","修改"]。需要注意的是,数组在JavaScript中始终是引用类型。
上述过程可能对于熟悉原型链的朋友来说略显冗长,但为了清晰展示机制,我们继续看另一个相似的例子。
代码如下:
// 定义超类
function Father() {
this.name = "父亲";
}
Father.prototype.theSuperValue = ["NO1", "NO2"];
// 定义子类
function Child() {
}
// 实现继承
Child.prototype = new Father();
// 修改共享数组
Child.prototype.theSuperValue = ["我是覆盖代码"]
// 创建子类实例
var theChild = new Child();
console.log(theChild.theSuperValue);
// 创建父类实例
var theFather = new Father();
console.log(theFather.theSuperValue); 观察上述代码,我用特殊的紫色标注了此段代码与上一段代码的细微区别,但运行结果却发生了显著变化,具体见下面的截图:

这里之所以强调“巨大变化”,是因为操作从“重用公共属性”转变为“覆盖公共属性,建立自身特色属性”。虽然示例中使用数组演示,但这种模式常用于函数中,即用子类方法覆盖父类方法。关键在于第二段代码中紫色标注代码前的“=”赋值操作符。当我们对 Child.prototype 或 new Father() 对象执行赋值操作时,实际上是在该对象上“新建”了一个属性。因此,当后续 theChild 实例调用 theSuperValue 时,返回的是该实例原型上刚创建的新属性值 ["我是覆盖代码"]。然而,当我们创建父类实例 theFather 时,情况不同。theFather 是通过 Father() 构造函数直接创建的对象,它并不包含刚才在 Child.prototype 上新建的属性。因此,访问 theFather 的 theSuperValue 时,引擎会沿原型链向上查找,最终在 Father.prototype 中找到最初定义的那个数组。通过这两个例子可以看出,在使用JavaScript原型继承功能时,特别是涉及子对象操作原型方法或属性时,必须严格区分“=”赋值与引用调用(如 push),因为两者会导致截然不同的结果。