1、原型链继承
关键:将父类的一个实例对象作为子类的原型(child.prototype=new Parent)
function Parent () {
this.name = 'yyqx',
this.sex = '男',
this.list = [1, 2, 3]
}
Parent.prototype.hobby = function () {
console.log('打篮球');
}
function Child (age) {
this.age = age
}
Child.prototype = new Parent(); // 关键
Child.prototype.constructor = Child; // 让子类的原型中的constructor指向子类函数的本身
Child.prototype.sleep = function () {
console.log('睡觉时间到了');
}
var child1 = new Child(20);
var child2 = new Child(30);
child1.hobby(); // 打印“打篮球”,hobby方法继承来自父类型的原型,通过child1.__proto__.__protp__查找到
console.log(child1.list); // [1,2,3]
console.log(child2.list); // [1,2,3]
child1.list.push(4);
console.log(child1.list); // [1,2,3,4]
console.log(child2.list); // [1,2,3,4]
缺点:
1)因为Child的原型对象都是New Parent,所以实例化出来的对象中的属性都是一样的,且对于引用类型的属性复制的是其地址,(即所有实例对象指向同一个地址),因此只要一个实例对象修改了,其他也会跟着修改。
2)无法向父类构造函数传参
2、借助构造函数继承(call)
步骤:
1)定义父类构造函数
2)定义子类构造函数
3)在子类构造函数中调用call或者apply把Parent的this指向改变为是Child的this指向。
关键:在子类型构造函数中通过call调用父类型构造函数(Parent.call(this))
function Parent (name, age) {
// this指向Parent实例对象
this.name = name,
this.age = age
}
Parent.prototype.hobby = function () {
console.log('打篮球');
}
function Child (name, age, gender) {
// call后:Parent中的this指向子类型构造函数实例对象
Parent.call(this, name, age);
this.gender = gender
}
let child = new Child('yyqx', 21, '男');
console.log(child.name, child.age); // 打印“yyqx”,21
child.hobby(); // Uncaught TypeError:child.hobby is not a function
优点:可以向父类传递参数,而且解决了原型链继承中父类属性使用this声明的引用类型属性会在所有实例共享的问题。
缺点:只能继承父类型上的属性和方法,无法继承父类原型上的属性和方法
3、组合式继承(原型链+构造函数)
组合上述两种方法,用原型链实现对原型上的属性和方法的继承,用构造函数技术来实现实例属性的继承。
function Parent (name) {
this.name = name
}
Parent.prototype.hobby = function () {
console.log('打篮球');
}
function Child (name, age) {
Parent.call(this, name);// 实现Parent中“name”属性的继承
this.age = age;
}
Child.prototype = new Parent(); // 实现子继承父的所有属性和方法包括原型上的
Child.prototype.constructor = Child;
const c = new Child('yyqx', 21);
console.log(c.name); // 打印“yyqx”
c.hobby(); // 打印“打篮球” 通过c.__proto__.__proto__找到该方法
优点:
1)可以继承父类原型上的属性,可以传参,可复用
2)每个新子类对象实例引入的构造函数属性是私有的(即不会相互影响)
缺点:
1)两次调用父类构造函数(new Parent()和Parent.call(this)),造成一定的性能损耗
2)在使用子类创建实例对象时,其原型中存在两份相同属性/方法的问题。
4、原型式继承(Object.create())
这是组合式继承得优化
var Parent = {
name: 'yyqx',
age: 21,
courses: ['前端']
}
var child = Object.create(Parent);
var child2 = Object.create(Parent);
child.courses.push('后端')
child2.courses.push('全栈')
console.log(child2.courses); // 打印:“[前端,后端,全栈]”
优点:解决了组合继承中,父类得构造函数被调用了两次,会产生两组相同属性,(一组在实例上,一组在原型上)。
缺点:与原型链继承一样,多个实例共享被继承对象得属性,会相互影响
加上构造函数的另一种写法
仅仅就是把组合式中的new Parent()替换为原型式Object.create(),这样就解决了上面那些写法的缺点(实例相互影响)
function Parent (name, courses) {
this.name = name,
this.courses = courses
}
Parent.prototype.hobby = function () {
console.log('打篮球');
}
function Child (name, age, courses) {
Parent.call(this, name, courses)
this.age = age
}
// 用Obj.create复制Parent原型到Child原型上
Child.prototype = Object.create(new Parent);
Child.prototype.constructor = Child
let child = new Child('yyqx', 21, [1])
let child2 = new Child('sxh', 24, [1])
console.log(child.courses); //[1]
console.log(child2.courses); //[1]
child.courses.push(2);
console.log(child.courses); //[1,2]
console.log(child2.courses); //[1]
5、寄生式继承
仅仅在原型式继承的基础上,创建一个封装继承过程的函数(创建、增强、返回)
使用场景:专门为对象来做某种固定方式的增强。
function createAnother (o) {
// 通过调用 object中的函数创建一个新对象
var clone = Object.create(o);
// 给新对象增加新的方法和属性(以某种方式来增强对象)
clone.say = function () {
console.log('say');
}
return clone;
}
var Parent = {
name: 'yyqx',
age: 21,
courses: ['前端']
}
var child = createAnother(Parent);
var child2 = createAnother(Parent);
child.courses.push('后端');
console.log(child2.courses); // ['前端','后端']
child2.say(); // say
优点:没有创建自定义构造函数类型,因为只是套了个壳子增加特定属性/方法返回对象,以达到增强对象的目的
缺点:同原型链继承,多个实例的引用类型属性指向同一地址,会相互影响,也无法传递参数。
6、寄生-组合式继承
借用寄生式实现封装,组合式实现继承。
function Parent (name) {
this.name = name
}
Parent.prototype.hobby = function () {
console.log("打篮球");
}
function Child (name, age) {
// 借助构造函数继承:继承父类通过‘this’声明的属性和方法
Parent.call(this, name)
this.age = age
}
// 寄生式继承:封装child.prototype对象原型继承parent.prototype的过程,并且增强了传入的对象
// 继承父类原型上的属性和方法
function inheritPrototype (child, parent) {
var clone = Object.create(parent.prototype);
clone.constructor = child; // 曾强对象,弥补child因重写原型而失去的默认的constructor属性
child.prototype = clone; // 将创建的拷贝对象赋值给子类的原型
}
inheritPrototype(Child, Parent);
Child.prototype.eat = function () {
console.log('eat');
}
var c = new Child('yyqx', 21)
c.hobby(); // '打篮球'
console.log(c.name); // yyqx
c.eat(); // eat
// c.__prot__ === clone
// clone.__prot__ === Parent.prototype
优点:
1)只调用一次父类Parent构造函数(Parent.call()),间接的使用了原型式继承(Parent.prototype)
2)避免了子类prototype上创建多余的属性
3)寄生组合式继承是最成熟的继承方法,也是现在最常见的继承方法
还可以这样子写:
function Parent (value) {
this.value = value
}
Parent.prototype.getValue = function () {
console.log(this.value);
}
function Child (value) {
Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
});
const child = new Child(1);
child.getValue(); //1
child instanceof Parent //true
7、ES6 extends继承(最优方式)
class Father {
constructor(name, age) {
this.name = name
this.age = age
}
skill () {
console.log('父类的方法');
}
}
// extends:表示继承
class Son extends Father {
constructor(name, age, job) {
super(name, age) // 调用父类的constructor,只用调用super之后,才能使用this关键字
this.job = job
}
getInfo () {
console.log(this.name, this.age, this.job);
}
}
let son = new Son('yyqx', 21, '演员')
son.skill(); // 父类的方法
son.getInfo(); // yyqx 21 演员
说明:子类必须在constructor方法中调用super方法,否则新建实例时会报错。因为子类没有自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其进行加工,加上子类自己的实例属性和方法。如果不能调用super方法,子类就得不到this对象,新建对象就会报错
总结
ES5继承
1.借助原型链实现继承:Child.prototype = new Parent()
2.借助构造函数实现继承:Parent.call(this)
3.组合式继承:综合上面两种方式,即Child.prototype = new Parent()
和Parent.call(this)
4.原型式继承:优化版组合式,将new Parent()
改成Object.create(Parent)
5.寄生式继承:在原型式外面套了个函数壳子(创建,增强、返回)
6.寄生-组合式:寄生式 + 构造函数的call + 原型式的Object.create()
ES5继承与ES6继承的区别
1.ES5的继承实质上是先创建子类的实例对象,再将父类的方法添加到this上(Parent.call(this)) 2.ES6的继承是先创建父类的实例对象this,再用子类的构造函数修改this 3.因为子类自己没有自己的this对象,所以必须先的调用父类的super()方法