1.经典继承
为了解决原型包含引用值导致的继承问题,一种叫作“盗用构造函数”(constructor stealing)的技术在开发社区流行起来(这种技术有时也称作“对象伪装”或“经典继承”)。基本思路很简单:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文执行构造函数。
function Animal() {
this.categorys = ["cat", "rabbit"];
}
function Dog() {
// 继承 Animal
Animal.call(this);
}
在var d1 = new Dog()时,是d1调用Dog构造函数,所以其内部this的值指向的是d1,所以Animal.call(this)就相当于Animal.call(d1),就相当于d1.Animal()。最后,d1去调用Animal方法时,Animal内部的this指向就指向了d1。那么Animal内部this上的所有属性和方法,都被拷贝到了d1上。所以,每个实例都具有自己的categorys属性副本。他们互不影响。
var d1 = new Dog();
d1.categorys.push("dog");
console.log(d1.categorys); // [ 'cat', 'rabbit', 'dog' ]
var d2 = new Dog();
console.log(d2.categorys); // [ 'cat', 'rabbit' ]
我们在Dog的构造函数中展示了经典继承函数的调用。通过使用 call()(或 apply())方法,Animal构造函数在为 Dog 的实例创建的新对象的上下文中执行了。这相当于新的 Dog 对象上运行了Animal()函数中的所有初始化代码。结果就是每个实例都会有自己的 categorys 属性。
2.1.传递参数
相比于使用原型链,经典继承函数的一个优点就是可以在子类构造函数中向父类构造函数传参。
function Animal(name) {
this.name = name;
}
function Dog() {
// 继承 Animal 并传参
Animal.call(this, "zhangsan");
// 实例属性
this.age = 29;
}
var d = new Dog();
console.log(d.name); // zhangsan
console.log(d.age); // 29
在这个例子中,Animal 构造函数接收一个参数 name,然后将它赋值给一个属性。在 Dog构造函数中调用 Animal 构造函数时传入这个参数,实际上会在 Dog 的实例上定义 name 属性。为确保 Animal 构造函数不会覆盖 Dog 定义的属性,可以在调用父类构造函数之后再给子类实例添加额外的属性。
2.2.经典继承函数的问题
经典继承函数的主要缺点,也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。由于存在这些问题,经典继承函数基本上也不能单独使用。
总结:
1.创建的实例并不是父类的实例,只是子类的实例。
2.没有拼接原型链,不能使用instanceof。因为子类的实例只继承了父类的实例属性/方法,没有继承父类的构造函数的原型对象中的属性/方法。
3.每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用。