一、继承的原理
复制父类的方法和属性来重写子类的原型对象。
二、原型链机制
JavaScript将原型链作为实现继承的主要方法,其基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(propto)。
其实每一个Function都是Object基类的一个实例,所以每一个Function上都有一个_propto_指向了Object.prototype。当查找一个实例的属性时,会先从这个实例的自定义属性上找,如果没有的话通过_propto_去实例所属类的原型上去找,如果还没有的话再通过原型(原型也是对象,只要是对象就有_propto_属性)的_propto_到Object的原型上去找,一级一级的找,如果没有就undefined。
可以说引用类型之间的继承就是通过原型链机制实现的。
三、继承方式
1、原型链
// 父类构造函数
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
console.log('sayName:',this.name);
}
//子类构造函数
function Student(score) {
this.score = score
}
Student.prototype = new Person('xiaoming',20)
var student = new Student(100)
console.log(student.name) //xiaoming
console.log(student.age) //20
console.log(student.score) //100
student.sayName() //sayName: xiaoming
//当然也可以在改变原型指向的时候不初始化父类的属性值,通过访问对象实例的属性时,再初始化值
Student.prototype = new Person()
var student = new Student(99)
student.name = 'xiaohong'
student.age = 24
console.log(student.name) //xiaohong
console.log(student.age) //24
console.log(student.score) //99
student.sayName() //sayName: xiaohong
通过改变子类原型指向的方法,可以使子类继承父类的属性和方法。
注:我们需要在子类中添加新方法或者重写父类的方法时,切记一定要放到替换原型的语句之后。
优点:
简单易操作。
缺点:
1、在通过原型来实现继承时,原型实际上会变成另一个类型的实例。
2、父类使用this声明的属性被所有实例共享,在多个实例之间对引用类型数据操作会相互影响。
3、在创建子类型的实例时,不能向父类型的构造函数中传递参数。
2、借用构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function () {
console.log('sayName:',this.name);
}
function Student(name, age, score) {
Person.call(this, name, age) // 相当于: this.Person(name, age)
this.score = score
}
var student = new Student('xiaoming', 20, 100)
console.log(student.name) //xiaoming
console.log(student.age) //20
console.log(student.score) //100
console.log(student.sayName())//报错,Uncaught TypeError: student.sayName is not a function
在子类型构造函数中通用call()调用父类型构造函数。
优点:
1、可以向父类传递参数。
2、解决父类this声明的属性会被实例共享的问题。
3、可以实现多继承(call多个父类对象)。
缺点:
1、只能继承父类通过this声明的属性和方法,不能继承父类prototype上的属性和方法。
2、父类方法无法复用,每次实例化子类,都要执行父类函数。
3、组合继承
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.sayName = function() {
console.log('sayName:',this.name);
}
function Student(score, name, age) {
//借用构造函数
Person.call(this, name, age);
this.score = score;
}
Student.prototype = new Person() //改变原型指向
var student = new Student(100,'xiaoming',22)
console.log(student.name) //xiaoming
console.log(student.age) //22
console.log(student.score) //100
student.sayName() //sayName: xiaoming
通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用。
优点:
1、解决原型链继承父类this声明的属性或者方法被共享的问题。
2、可以同时继承父类的属性和方法。
3、可以传参,函数可复用。
缺点:
1、调用了两次父类构造函数,生成了两份实例,造成一定的性能问题。
2、因调用两次父类,导致父类通过this声明的属性和方法被生成两份的问题。
4、原型式继承
// Person()对传入其中的对象执行了一次浅拷贝,将构造函数F的原型直接指向传入的对象。
function Person(o) {
function F() {}
F.prototype = o; // 将传进来obj对象作为空函数的prototype
return new F(); // 此对象的原型为被继承的对象, 通过原型链查找可以拿到被继承对象的属性
}
var options = {
age: 2,
favorFood: ['meet', 'chicken']
};
// var student1 = Object.create(options); // 效果一样
var student1 = Person(options);
student1.name = 'xiaoming';
student1.favorFood.push('fish');
var student2 = Person(options);
student2.name = 'xiaohong';
student2.favorFood.push('tofu');
console.log(options.favorFood); //meet chicken fish tofu
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。
优点:
从已有对象衍生新对象,不需要创建自定义类型。
缺点:
与原型链继承一样,多个实例共享被继承对象的属性,存在篡改的可能,也无法传参。
5、寄生式继承
function Person(o) { // 传入一个对象
function F() {};
F.prototype = o;
return new F();
}
function createAnother(original) {
var clone = Person(original);
clone.sayHi = function () {
console.log('hi');
};
return clone;
}
var person = {
name: 'xiaoming',
favorFood: ['meet', 'chicken']
}
var student = createAnother(person);
student.sayHi(); // 'hi'
在原型式继承的基础上,创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象(增加了一些新的方法和属性),最后返回对象。
Person()方法可以用任何能够返回新对象的函数替代,即作用为返回一个对象的副本。
优点:
兼容性好,最简单的对象继承。
缺点:
与原型链继承,原型式继承一样,多个实例共享被继承对象的属性,存在篡改的可能,也无法传参。
5、寄生组合式继承
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function(){
console.log('父类的方法,hi')
}
function Student(name, age, score){
Person.call(this,name,age); // 借用构造继承: 继承父类通过this声明属性和方法至子类实例的属性上
this.score = score;
}
// 寄生式继承:封装了son.prototype对象原型式继承father.prototype的过程,并且增强了传入的对象。
function inheritPrototype(son,father){
var clone = Object.create(father.prototype); // 原型式继承:浅拷贝father.prototype对象
clone.constructor = son; // 增强对象,弥补因重写原型而失去的默认的constructor 属性
son.prototype = clone; // 指定对象,将新创建的对象赋值给子类的原型
}
inheritPrototype(Student,Person); // 将父类原型指向子类
// 新增子类原型属性
Student.prototype.sayHello = function(){
console.log('子类的方法,hello')
}
var student = new Student('xiaoming',22,100);
console.log(student.name); //xiaoming
console.log(student.age); //22
console.log(student.score); //100
结合借用构造函数传递参数和寄生模式实现继承。
优点:
1、只调用了一个父类的构造函数,避免了在父类的prototype上创建多余的属性,节约了性能。
2、原型链同时保持不变,子类的prototype只有子类通过prototype声明的属性和方法,父类的prototype只有父类通过prototype声明的属性和方法。
寄生组合式继承是最成熟的继承方法, 也是现在最常用的继承方法,众多JS库采用的继承方案也是它。