在JavaScript的原型链继承方式中,为什么子类在调用父类的构造函数时不能传参数?
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");//调用父类构造函数时给固定值
但是,这个固定的属性值必定会影响所有子类的对象实例,相当于子类构造函数“擅作主张”给所有对象实例的属性提前“赋了值”。这样写是不太符合面向对象编程的规则的。
我觉得,并不是语法上不能实现对构造函数的参数传递,而是这样做不符合面向对象编程的规则:对象(实例)才是属性的拥有者。
如果在子类定义时就将属性赋了值,对象实例就不能再更改自己的属性了。这样就变成了类拥有属性,而不是对象拥有属性了。
举个例子,子类 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不是!
如此写完全失去了面向对象编程的意义,所以在原型链继承方式中规定不能对父类构造函数传递参数。也因为这个原因,原型链继承方式并不实用。
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官方认定的继承方式. 此问题本身就是灰色地带.