在JavaScript的原型链继承方式中,为啥子类在调用父类的构造函数时不能传参数?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在JavaScript的原型链继承方式中,为啥子类在调用父类的构造函数时不能传参数?相关的知识,希望对你有一定的参考价值。
教程上说子类的构造函数需要父类的实例作为自己的prototype,但在调用父类的构造函数时参数列表必须为空,请问这是为什么?我试了一下传参,没有出现问题,参数也能正确起作用。希望高人解答!
我认为,不是不能给父类构造函数传参数,而是传的参数最终不能起到作用。举个例子:function Parents(ln) this.lastName=ln; //定义父类构造函数
function Children(fn,ln) this.firstName=fn; //定义子类,lastName 继承自父类
//原型链继承,给父类构造函数传入参数,试图用 Children 类构造函数中传入的 ln 初始化 lastName:
Children.prototype=new Parents(this ln);
//尝试建立对象实例:
var child=new Children("Bill","Gates");
//输出结果。很明显,lastNmae 并没有得到想要的值:
alert(child.firstName);//Bill
alert(child.lastName);//Undefine
这说明给父类构造函数传递参数是无效的。原因就在于原型链方式中,调用父类构造函数的代码并不在子类构造函数中,建立对象实例时给的属性值(即子类构造函数的参数)并不能影响到子类调用的父类构造函数。
当然,在继承时可以这样写:
Children.prototype=new Parents("Gates");//调用父类构造函数时给固定值
但是,这个固定的属性值必定会影响所有子类的对象实例,相当于子类构造函数“擅作主张”给所有对象实例的属性提前“赋了值”。这样写是不太符合面向对象编程的规则的。 参考技术A 以前我在看书时也遇到过这样的问题,找了很多资料都没有明确的解释。
我觉得,并不是语法上不能实现对构造函数的参数传递,而是这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。
如果在子类定义时就将属性赋了值,对象实例就不能再更改自己的属性了。这样就变成了类拥有属性,而不是对象拥有属性了。
举个例子,子类 Children 继承父类 Parents,Parents 构造函数:
function Parents(name) this.name=name;
使用原型链并给父类构造函数传参数:
Children.prototype=new Parents("Hello");
那么此时,Children 类就拥有了 name=“Hello” 属性,而 Children 类的实例对象 c1、c2、c3 等等只能被迫接受这个 name 属性。Children 是 "Hello" 的拥有者而 c1、 c2、c3不是!
如此写完全失去了面向对象编程的意义,所以在原型链继承方式中规定不能对父类构造函数传递参数。也因为这个原因,原型链继承方式并不实用。本回答被提问者采纳 参考技术B
WANGERN 的答案很有启发, 我接着他回答, 我也是新手, 这只是我的理解, 希望牛人来指正:
并不是语法上不能实现对构造函数的参数传递,而是这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。如果在子类定义时就将属性赋了值,就变成了类拥有属性,而不是对象拥有属性了。 举个例子,
function Parent(name) this.name=name;function Child(age)this.age=age;
Child.prototype=new Parent("Hello");
var c1= new Child(1);
alert(c1.age+'...'+c1.name);
此时Child类是"Hello"的拥有者, 而Child类的实例对象c1不是. 如此写完全失去了面向对象编程的意义. 但是在"避免类拥有属性值"这一前提下, 还是可以这样修改的:
function Parent(name)this.name=name;function Child(age)this.age=age;
Child.prototype = new Parent();
var c1= new Child(1);
c1.name='Hello';
alert(c1.age+'...'+c1.name);
显然这样的代码也不够优雅, 因为需要初始化之后再设置name值, 代码被打散了. 于是自然想到将 Child 的实例化封装成函数.
function Parent(name)this.name=name;function Child(age)this.age=age;
function createChild(age, name)
Child.prototype = new Parent();
var c=new Child(age);
c.name=name;
return c;
var c1= createChild(1, 'Hello');
alert(c1.age+'...'+c1.name);
可是, 本来规范的 new Child(name,age) 书写变成了 createChild(age, name), 代码还是不优雅. 于是就有了 "对象冒充+原型链继承" 的 "组合模式":
function Parent(name) this.name = name;function Child(age,name)Parent.call(this,name);this.age=age;
Child.prototype = new Parent();
var c1= new Child(1, 'Hello');
alert(c1.age+'...'+c1.name);
代码变得优雅了一些. 但也不是绝对. 所以, 因为javascript语法的灵活(不伦不类), 事实上继承的实现方式有很多, 也并不存在绝对无法传参数的问题, 只不过组合模式代码相对优雅, 而广泛采用. 说到底, 对象冒充一开始也不是ECMAScript官方认定的继承方式. 此问题本身就是灰色地带.
JavaScript常见继承方式
与传统的面向对象语言不同,JavaScript的继承主要是通过原型链和借用构造函数的方式实现。今天我们就来学习下在JavaScript中常见的四种继承实现方式,分别是:原型链继承、借用构造函数继承、组合继承以及Class类继承。
原型链继承
原型链继承的核心思想是通过将子类的原型设置为父类实例的对象来实现对属性和方法的继承。
实际案例1:
//父类构造函数
function SuperType(){
this.color=[\'红\',\'橙\',\'黄\'];
this.name="大壮";
}
//子类构造函数
function SubType(){}
//将子类的原型指向父类的实例
SubType.prototype = new SuperType();
let instance1 = new SubType();
instance1.color.push("紫");
instance1.name ="大顺"
console.log(instance1.name); // 大顺
console.log(instance1.color);// [\'红\',\'橙\',\'黄\',"紫"]
var instance2 = new SubType();
console.log(instance2.color);// [\'红\',\'橙\',\'黄\',"紫"]
console.log(instance2.name);// 大壮
这里,我们可以清晰的看到,由于原型属性中引用的类型会被实例共享,而基本基本数据类型不会被实例共享,所以导致我们修改了原型上的name属性不会影响其他实例,而当修改了引用实例(color)却作用到了其他实例,这肯定不是我们想要的。
原型属性还有一个问题,就是我们在实例化属性的时候我们不能进行传参,比如我们在实例化属性的时候想要给name传一个值,但由于子类的参数无法共享给父类,所以是无法做到的。原型链继承主要问题总结如下:
- 原型属性上的引用类型数据修改后会污染其他的实例;
- 实例化对象时无法传参。
借用构造函数继承
借用构造函数继承的核心思想是通过call()、apply()函数在将来实例化的对象上执行构造函数。
实际案例2:
// 父类构造函数
function SuperType1(){
this.color = [\'red\',\'orange\',\'yellow\'];
this.name = "DaZhuang";
}
// 子类构造函数
function SubType1(){
SuperType1.call(this);
}
let instance3 = new SubType1();
instance3.color.push("purple");
instance3.name = "DaShun";
console.log(instance3.color); //["red", "orange", "yellow", "purple"]
console.log(instance3.name); // DaShun
let instance4 = new SubType1();
console.log(instance4.color); //[\'red\',\'orange\',\'yellow\']
console.log(instance4.name); // DaZhuang
我们可以清楚地看到,通过借用构造函数,我们可以在创建子类实例的时候执行SuperType()上写好的初始化代码,这样每个实例都有一个color的副本了,很好的处理了引用类型属性污染其他实例的问题。
但是这种借用构造函数方式的继承也存在问题,那就是属性全都在父类中定义因此无法进行函数的复用,而且在父类原型中定义的方法对子类也是不可见的,结果所有类型都只能使用构造函数模式。因此借用构造函数的方式也很少单独使用。
组合继承
组合继承是指将原型链继承和借用构造函数继承这两种方式结合起来,从而发挥二者各自的长处的一种继承模式。
其核心思路是通过原型链实现对原型属性和方法的继承,而借用构造函数实现对实例属性和方法的继承。
实际案例3:
//父类构造函数
function Super (name) {
this.name = name;
this.colors = ["金", "木", "水"];
this.sayBigName = function () {
console.log(\'父类的方法\');
}
}
//子类构造函数
function Sub (name) {
//实现父类属性和方法的继承
Super.call(this, name);
}
//实现实例属性和方法的继承
Sub.prototype = new Super();
//修复Sub构造函数原型构造函数的指向,从Super变更为Sub
Sub.prototype.constructor = Sub;
Sub.prototype.sayName = function () {
console.log(this.name);
}
var sub1 = new Sub("大壮");
sub1.colors.push("火");
console.log(sub1.colors); //["金", "木", "水", "火"]
sub1.sayName(); //大壮
sub1.sayBigName(); //父类的方法
var sub2 = new Sub("大顺");
console.log(sub2.colors); // ["金", "木", "水"]
sub2.sayName(); //大顺
这个案例比较复杂,读者需要细细品读。首先读者要明确哪些属性和方法是在父类中声明的(对应案例中Super里面的:name,colors,sayBigName()),这些属性和方法是怎么实例化的呢?是通过13行代码(借用构造函数)实例化的,通过call方法在Sub上下文执行了Super属性和方法的实例化,这样Sub就拿到了Super里面的属性和方法。但是做完这一步是完全不够的,因为我们在子类中也有一些方法需要子类继承,这些方法我们也希望它能在实例化的时候被实例对象共享,这时候就需要采用原型链继承模式(代码17行)。
我们通过将子类(Sub)的原型指向父类的实例,这样在我们在子类中定义的方法和属性就会被存放到这个实例化的父类对象里面。在后续实例化Sub子类创建实例过程中,实例对象就可以通过原型链继承的形式拿到子类中定义的方法(sub1._ proto_ 等于Sub.prototype)。这样我们就实现了实例对象对父类对象属性和方法的继承以及子类对象方法的继承。
细心的同学可能会发现,我们并没有通过原型链模式让实例对象继承子类的属性,其原因其实我们在原型链继承模式部分已经介绍,就是因为使用原型链继承引用类型数据的时候存在污染其他实例对象的问题,因此我们采用原型链模式仅继承子类的方法而不对子类的属性进行继承(属性可以放到父类构造函数中)。
ES6 Class继承
在es6中,引入了class函数,通过class语法我们可以很容易的实现继承
实际案例4:
//父类
class Super {
getValue () {
console.log(this.val)
}
}
//子类
class Sub extends Super {
constructor(value) {
super(value)
this.val = value
}
}
let sub = new Sub(1)
sub.getValue() // 1
sub instanceof Super // true
以上是关于在JavaScript的原型链继承方式中,为啥子类在调用父类的构造函数时不能传参数?的主要内容,如果未能解决你的问题,请参考以下文章