一文看懂JS中的继承

前言

看懂这篇文章需要你至少了解原型链的原理。

直接举例子,先定义一个Person,再定义一个Man:

function Person(name){
  this.name = name; 
  this.species='person'
  this.have = [1,2,3,4]
}
Person.prototype.hello = () => {
  console.log('person')
}
function Man(name) {
  this.name = name; 
  this.gender='man'
}
Man.prototype.hello = () => {
  console.log('man')
}

原型继承

我们首先创建a,b两个实例:

let a = new Person('a')
Person {name: "a", species: "person"}
  name: "a"
  species: "person"
  have: (4) [1, 2, 3, 4]
  __proto__:
    hello: () => {console.log('person')}
    constructor: ƒ Person(name)
    __proto__: Object
let b = new Man('b')
Man {name: "b", gender: "man"}
  name: "b" 
  gender: "man"
  __proto__:
    hello: () => {console.log('man')}
    constructor: ƒ Man(name)
    __proto__: Object

这时我们按照原型继承的方式:

Man.prototype=a
Man.prototype.constructor=Man

然后我们再创建一个c实例,为c的have方法推一个新值:

let c = new Man('c')
c.have.push(5)
Man {name: "c", gender: "man"}
name: "c"
gender: "man"
__proto__: Person
  name: "a"
  species: "person"
  have: (5) [1, 2, 3, 4,5]
  constructor: ƒ Man(name)
  __proto__: Object
    hello: () => { console.log('person') }
    constructor: ƒ Person(name)
    __proto__: Object

总结一下原型继承的缺点:

  1. 子类原型上的方法被覆盖。
  2. 父类引用类型的属性会被子类公用。
  3. 子类型无法给父类型传递参数

类式继承

针对上面的原型继承的问题,我们可以采用类式继承解决:

function Women(name){
  Person.call(this, name)
  this.gender='women'
}
let e = new Women('e')
name: "e"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "women"
__proto__: Object

这样就很好的解决了子类型向父类构造函数中传参的问题,引用属性也不会被公用。

但是这样一来,其实问题又更多了,典型的就是没办法复用函数。同时Person原型定义的方法,再e中根本无法使用。

组合式继承

很简单,融合一下呗。

  1. 使用原型链对原型属性和方法的继承
  2. 构造函数对于实例属性的继承
function Child(name) {
  Person.call(this, name) 
  this.gender='child'
}
Child.prototype.hello = () => {
  console.log('child')
}
Child.prototype=new Person()
let f = new Child('f')
Child {name: "f", species: "person", have: Array(4), gender: "child"}
name: "f"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "child"
__proto__: Person
  name: undefined                  //不需要
  species: "person"                //不需要
  have: (4) [1, 2, 3, 4]           //不需要
  __proto__:
    hello: () => { console.log('person') }
    constructor: ƒ Person(name)
    __proto__: Object

同时我们再次push值给f.have的话:

f.have.push(5)
Child {name: "f", species: "person", have: Array(5), gender: "child"}
name: "f"
species: "person"
have: (5) [1, 2, 3, 4, 5]
gender: "child"
__proto__: Person
  name: undefined
  species: "person"
  have: (4) [1, 2, 3, 4]
  __proto__:
    hello: () => { console.log('person') }
    constructor: ƒ Person(name)
    __proto__: Object

可以看到,haved的push出现在了f上,而它的原型上仍然是四个值的数组。

但是这种做法也是有点缺点的,典型的就是组合继承使用过程中会父类构造函数被调用两次。

而显然,Child.prototype=new Person() 这一次是不需要的,因为我们获取实例属性并不需要通过原型继承。

这时你可能会想到:

Child.prototype = Person.prototype

的方法,这样当然也是不对的,虽然可以避免去读父类实例属性,但是会把父类和字类的原型合并为一个。

所以有了下面的方法。

组合寄生

这里我们用了一部转化:

function create(proto) {
  function F(){}; 
  F.prototype=proto; 
  return new F()
}
function Nobody(name) {
  Person.call(this, name) 
  this.gender='nobody'
}
Nobody.prototype=create(Person.prototype)
Nobody.prototype.hello=()=>{console.log('nobody')}
Nobody.prototype.constructor=Nobody

和原来的组合继承相比仅仅改了一处:

Nobody.prototype=create(Person.prototype)
Child.prototype =new Person()

得到的结果就相差极大:

let c = new Nobody('c')
Nobody {name: "c", species: "person", have: Array(4), gender: "nobody"}
name: "c"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "nobody"
__proto__: Person
  hello: ()=>{console.log('nobody')}
  constructor: ƒ Nobody(name)
  __proto__:
    hello: () => { console.log('person') }
    constructor: ƒ Person(name)
    __proto__: Object

能做到这样主要原因就是我们写的这个create函数:

function create(proto) {
  function F(){}; 
  F.prototype=proto; 
  return new F()
}

这个函数中我们创建了一个空的构造函数,把他的prototype属性指向传入的参数,最后返回一个该构造函数的实例。这样可以保证原型链的完整性,同时又可以保证实例的原型上没有多余属性。

最后ES5中对这个create其实封装好了,我们使用时如下即可:

function Nobody(name) {
  Person.call(this, name) 
  this.gender='nobody'
}
Nobody.prototype=Object.create(Person.prototype)
Nobody.prototype.hello=()=>{console.log('nobody')}
Nobody.prototype.constructor=Nobody

创建个实例即:

let h = new Nobody('h')
Nobody {name: "h", species: "person", have: Array(4), gender: "nobody"}
name: "h"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "nobody"
__proto__: Person
  hello: ()=>{console.log('nobody')}
  constructor: ƒ Nobody(name)
  __proto__:
	hello: () => { console.log('person') }
	constructor: ƒ Person(name)
	__proto__: Object

ES6的class语法糖

这就不讲了,这还讲个啥

发布了371 篇原创文章 · 获赞 391 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/weixin_43870742/article/details/104276259