ES5实现继承
2020-04-08 本文已影响0人
书虫和泰迪熊
原型链继承
- 原理:将子类的原型指向父类的实例
function Animal(name) {
this.name = name;
this.color = ['red', 'bule'];
}
Animal.prototype.showName = function() {
console.log(this.name);
}
function Dog(age) {
this.age = age;
}
Dog.prototype = new Animal(); // Dog 继承 Animal
Dog.prototype.showAge = function() { // 注意1
console.log(this.age)
}
// 测试
let dog1 = new Dog(12);
dog1.color.push('black');
console.log(dog1.color); // ["red", "bule", "black"]
dog1.showName(); // undefined
dog1.showAge(); // 12
let dog2 = new Dog(33);
console.log(dog2.color); // ["red", "bule", "black"]
dog2.showName(); // undefined
dog2.showAge(); // 33
- 缺点
- 父类包含引用类型的原型,他会被所有实例共享;因为子类的prototype指向了父类的实例,父类实例中的 color 指针一直保持不变;
- 因为子类直接指定了原型,所以不能实现多继承;
- 创造子类实例时,无法向父类构造函数中传递参数;
- 优点
- 子类可以通过原型链共享父类原型中的方法;
- 注意
- 子类原型添加方法的代码必须放在子类替换原型的语句之后,不能使用对象字面量方式添加原型方法,这样会重写原型链。 而添加的方法实质是添加到了子类默认的原型中。
- 默认的原型:所有的类型都继承自Object,所有函数的原型对象都是Object 的实例,因此默认原型都要一个指针,指向Object.prototype。
借用构造函数继承
- 原理:在子构造函数内部调用父类构造函数,使用call() 或 apply() 方法将 this 指向子类
function Animal(name) {
this.name = name;
this.color = ['red', 'bule'];
}
Animal.prototype.showName = function() {
console.log(this.name);
}
function Dog(age, name) {
Animal.call(this, name);
this.age = age;
}
Dog.prototype.showAge = function() {
console.log(this.age)
}
// 测试
let dog = new Dog(11, 'tom');
dog.color.push('pink');
console.log(dog.name); // tom
console.log(dog.color); // ["red", "bule", "pink"]
dog.showName(); // Uncaught TypeError: dog.showName is not a function
dog.showAge(); // 11
let dog2 = new Dog(22, 'jack');
console.log(dog2.name); // jack
console.log(dog2.color); // ["red", "bule"]
dog2.showName(); // Uncaught TypeError: dog.showName is not a function
dog2.showAge(); // 22
-缺点
- 子类的原型无法通过原型链到父类原型,所以子类实例无法访问父类的方法。
- 优点
- 引用类型在每个子类实例中都有一个单独的副本,不会相互影响;某种形式上可以实现多继承
- 创建子类实例时可以向父类传参数
组合继承(是原型链继承和构造函数继承的合体)
- 原理:将原型链继承和构造函数结合在一起;使用原型链继承实现了对原型属性和方法的继承,使用构造函数实现了对父类实例和属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性
function Animal(name) {
this.name = name;
this.color = ['red', 'bule'];
}
Animal.prototype.showName = function() {
console.log(this.name);
}
function Dog(age, name) {
Animal.call(this, age);
this.age = age;
}
Dog.prototype = new Animal();
Dog.prototype.showAge = function() {
console.log(this.age);
}
// 测试
let dog = new Dog(11, 'tom');
dog.color.push('pink');
console.log(dog.name); // tom
console.log(dog.color); // ["red", "bule", "pink"]
dog.showName(); // tom
dog.showAge(); // 11
let dog2 = new Dog(22, 'jack');
console.log(dog2.name); // jack
console.log(dog2.color); // ["red", "bule"]
dog2.showName(); // jack
dog2.showAge(); // 22
- 缺点
- 会调用两次超类型构造函数,一次是在创建子类型原型的时候,一次是在子类型构造函数的内部,占用内存
- 由于调用了两次父构造函数在子类实例的原型对象上会有冗余的属性存在
- 优点
- 既可以继承父类原型中的方法和属性,也可以为父构造函数传递参数,保证每个子实例都有自己的属性(副本)
寄生组合继承 (业内比较的方法)
- 原理:通过调用构造函数来继承属性,通过原型链的混成形式来继承方法;不必为了指定子类型的原型而调用超类型的构造函数。本质:就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型
function obj(o) {
function F(){}
F.prototype = o;
return new F();
}
function inheritPrototype(sonType,farType){
let prototype = obj(farType); // 创建对象
prototype.constructor = sonType; // 增强对象
sonType.prototype = prototype ; // 指定对象
}
function Animal(name) {
this.name = name;
this.color = ["red","green","blue"];
}
Animal.prototype.showName = function(){
console.log(this.name);
}
function Dog(name, age) {
Animal.call(this, name);
}
inheritPrototype(Dog, Animal.prototype);
Dog.prototype.sayAge = function() {
console.log(this.age);
}
// 测试
let dog = new Dog('tom', 12);
dog.color.push('pink')
console.log(dog.color) // ["red", "green", "blue", "pink"]
dog.showName() // tom
dog.showAge() // 12
let dog2 = new Dog('jack', 33);
dog2.color.push('pink')
console.log(dog2.color) // ["red", "green", "blue", "pink"]
dog2.showName() // jack
dog2.showAge() // 33