原型式继承: 将父级对象整个赋值给一个空函数的原型,然后new
这个函数,将返回值传出去。因此外面的实例可以通过[[proto]]
找到构造函数的prototype
,沿着原型链找到父级的属性。
基本实现:
function object(father) {
function Fun () {}
Fun.prototype = father
return new Fun()
}
let fa = {
name: 'father',
colors: ['red', 'blue', 'green']
}
let son = object(fa)
console.log(son.__proto__.name === fa.name) // true
console.log(son.__proto__.colors === fa.colors) // true
console.log(son.__proto__ === fa) // true
前提是new
的实现原理你已经了解过了。
寄生式继承: 寄生式继承算是原型式继承的改造,它的不同之处在于:创造一个原型为父类的子类,然后在子类上手动添加需要的方法和属性,问题自然就是方法不能重用。
以上两种方式都是比较偏向对象,而不是创建函数的情况。
寄生式组合继承: 该方法使用寄生式和组合式实现继承。首先用父类的原型对象创建一个原型副本,然后防止发生子类constructor
丢失的问题,手动将原型副本的constructor
指到子类,再将这个原型副本赋值给子类的原型,这样成功继承了:父类原型上的方法和属性。
然后我们试图继承父类的属性和内部定义方法(严谨一点吧。。。),就使用组合继承喽,在子类构造函数中调用父类的构造函数,记得this
指给实例。
function object(father) {
function Fun () {}
Fun.prototype = father
return new Fun()
}
function inheritPrototype(son, father) {
let prototype = object(father.prototype)
prototype.constructor = son
son.prototype = prototype
}
function Father (name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Father.prototype.sayName = function () {
console.log(this.name)
}
function Son (name, age) {
Father.call(this, name)
this.age = age
}
inheritPrototype(Son, Father)
// 一定放在这里 不要往上移 否则被覆盖
Son.prototype.sayAge = function () {
console.log(this.age)
}
let instance = new Son('Chenxingxing', 20)
instance.sayName() // Chenxingxing
instance.sayAge() // 20
这样只会在子类创建实例的时候执行一次父类的构造函数,没有在组合继承中那样创建一个父类实例赋值给子类的原型。
我们详细观察一下:object
方法中,一个函数的原型被赋值为父类的原型,然后创建了一个实例传了出去,这个实例没有prototype
属性,但是有__proto__
属性,指向父类的原型,然后这个实例赋值给一个所谓的原型副本,这个原型副本经过加工,覆盖了子类的原型。完成了继承。注意,子类原型方法的定义只能在继承父类之后执行,否则跟没写一样,会在继承的过程中被覆盖。
然后我们创建子类实例,请问子类的原型是谁,子类的原型的原型是谁。
答案1:子类的原型,答案2:父类的原型
也即:
instance.__proto__ === Son.prototype
instance.__proto__.__proto__ === Father.prototype
看这结构,这不就实现了继承吗。
在原型链上讲话要文明哈。