阅读 36

【JavaScript】继承总结

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()方法

文章分类
前端
文章标签