JavaScript继承(一)——原型链中提出原型链继承的两个问题:一是原型的数据共享问题,二是创建子类型的实例时,不能向父类型的构造函数中传递参数。这两个问题的根源还是在于使用原型模式创建对象,可以参考JavaScript创建对象(三)——原型模式。本篇文章来讨论一下这两个问题的解决方案——借用构造函数继承。
借用构造函数也被称为伪造对象或经典继承。这种技术的基本思想相当简单,即在子类型构造函数的内部调用父类型构造函数。在Javascript中,从本质上来讲,我们声明的函数不过是Function
类型在特定执行环境中执行代码的实例,因此可以通过使用apply
方法在创建子类型的对象时,把子类型的执行环境传递给父类型。
首先我们来看下apply
的用法,如下代码所示:
var nums = {
n1: 10,
n2: 8
}
function sum(a, b) {
console.log(this);
console.log(arguments);
}
sum(2, 4);//this: window 。 arguments:[2, 4]
sum.apply(nums, [2, 4]);//this: nums 。 argument:[2, 4]
直接调用sum
时,函数sum
中的this
指代的是调用sum
的执行环境的变量对象,即全局变量对象window
,使用sum.apply
的形式调用,第一个参数传的nums
,第二个参数传的数组,那么sum
在执行时,this
指的就是nums
,arguments
指的就是传入的参数数组。
明白了apply
的用法,下面我们来看借用构造函数继承,如代码所示:
function Human(){
this.colors = ['yellow', 'white', 'black'];
}
function Person(){
Human.apply(this);
}
var p1 = new Person();
p1.colors.push('brone');
console.log(p1.colors);//["yellow", "white", "black", "brone"]
var p2 = new Person();
console.log(p2.colors);//["yellow", "white", "black"]
Human
中声明了一个数组colors
,Person
中使用apply
的方式调用Human
,并把this
传递给Human
,以实现Person
继承Human
,当执行new Person()
时,函数Person
中的this
指的就是新创建出的对象,接着就会传递到Human
,于是这个新对象上就有了colors
属性,实现了继承的效果,通过这种方式,每次创建对象时,子类型中都生成了父类型中定义的属性的一个副本,没有共享属性,也就不存在数据共享的问题了。
另外,对于在子类型构造函数中向超类型构造函数传递参数的问题来看以下示例:
function Human(){
this.name = arguments[0];
}
function Person(name){
Human.apply(this, arguments);
//重新定义子类型的属性,防止被父类型覆盖
this.age = 18;
}
var p1 = new Person('Bob');
console.log(p1.name);//Bob
console.log(p1.age);//18
创建Person
的实例时传递了一个参数Bob
,在构造函数中通过apply
的方式又将参数传递给了Human
,在Human
中进行赋值操作实际上就是给新创建的实例设置name
属性。为了防止子类型的属性被重写,可以在调用超类型的构造函数之后再添加应该在子类型中定义的特有的属性。
如果仅仅是借用构造函数,那么也将无法避免构造函数模式存在的问题——方法都在构造函数中定义,因此函数复用就无从谈起了。而且,在超类型的原型中定义的方法,对子类型而言也是不可见的,因为整个过程中并没有创建超类型的对象,根据原型链的特征,自然也就无法使用超类型的原型,结果继承链上的所有类型都只能使用构造函数模式创建对象。考虑到这些问题,借用构造函数继承的技术也是很少使用的。
读者也许已经想到可以组合使用构造函数和原型链继承,就像组合使用构造函数模式和原型模式创建对象一样,那么下一篇文章就来谈谈组合继承。