前言
看懂这篇文章需要你至少了解原型链的原理。
直接举例子,先定义一个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
总结一下原型继承的缺点:
- 子类原型上的方法被覆盖。
- 父类引用类型的属性会被子类公用。
- 子类型无法给父类型传递参数
类式继承
针对上面的原型继承的问题,我们可以采用类式继承解决:
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中根本无法使用。
组合式继承
很简单,融合一下呗。
- 使用原型链对原型属性和方法的继承
- 构造函数对于实例属性的继承
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语法糖
这就不讲了,这还讲个啥