本文分析JavaScript原型链继承机制中存在的两个主要问题。首先,原型中包含的引用类型值会被所有实例共享,修改一个实例会污染其他实例。其次,在创建子类实例时无法向超类构造函数传递参数,导致无法灵活初始化父类属性。
原型链继承的基本关系与引用共享缺陷
利用原型链实现继承是一种常见且有效的方式。我们可以参考以下原型链继承的基本示例来理解其工作原理。
function A () {
this.abc = 44;
}
A.prototype.getAbc = function (){
return this.abc;
};
function B() {
}
B.prototype = new A(); // B通过A的实例完成了继承,形成了原型链(B的原型就是A的实例)
var b = new B();
b.getAbc();
其对象关系链表现为:实例 b 指向 B.prototype,而 B.prototype 是 new A() 的实例,接着指向 A.prototype,最终指向 Object.prototype。
然而,这种看似优雅的继承方式实际上隐藏着一些缺陷。
首要问题在于原型中包含引用类型值。由于原型被所有实例共享,这会导致数据冲突。以下代码展示了这一现象。
function Person () {
}
Person.prototype = {
friends = ["a","b"];
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("c");
console.log(person1.friends); // "a","b","c"
console.log(person2.friends); //"a","b","c"
当通过某个实例修改原型中引用的数组或对象时,该修改会反映在所有其他实例上。这正是为什么引用类型数据应当定义在构造函数内部,而非原型中的原因。
在原型链继承的场景下,同样会遭遇此类共享引用的问题。
function A () {
this.numbers = [1,2,3];
}
function B() {
}
B.prototype = new A();
var b = new B();
var a = new A();
b.numbers.push(4);
b.numbers; //1234
var b2 = new B();
b2.numbers; //1234
a.numbers; //123
如代码所示,由于原型链继承本质上是让子类的原型成为父类的一个实例,因此父类实例的属性便自然成为了子类的原型属性,从而引发共享问题。
值得注意的是,父类 A 的实例 a 访问 numbers 属性时仍返回初始值 123。这表明在 B 继承 A 时,系统复制了 A 实例的所有属性(包括 prototype 指针)以构建原型链,而非直接引用 A 的实例。这里存在一个疑问:是否因为继承的是 A 的特定实例,所以未影响 A 创建的其他实例?
子类实例化时无法向超类传递参数
第二个关键问题在于,创建子类实例时,无法在不影响所有对象实例的前提下向超类传递参数。
function A (light) {
this.light1 = light;
};
function B (light) {
this.light = light;
};
//给B赋值的同时,想给A赋值,无法实现
B.prototype = new A();
var C = new B(123);
console.log(C.light);
console.log(C.light1);
若要解决此问题,必须手动调用 A 的构造函数,但这会干扰其他实例的正常行为。
function A (light) {
this.light1 = light;
};
function B (light) {
this.light = light;
A.call(this,light);//手动调用A的构造方法
};
//给B赋值的同时,给A赋值
B.prototype = new A();
var C = new B(123);
console.log(C.light);
console.log(C.light1);
以上分析了 JavaScript 原型链继承中存在的局限性,希望能为读者提供有益参考。如有任何疑问,欢迎留言交流,我们将及时回复。感谢大家对本站的支持!