在js中,我们可以使用继承来实现子类拥有父类的属性和方法
原型链继承
通过子类的原型对象指向父类的实例,父实例内部又有一个[[prototype]]指向父类的原型对象(书上说子类原型直接指向父类原型了,我觉着我这样更容易理解一点)。这样,子类实例通过找自身,没有,去子类原型去找,没有,去父实例上找,没有,去父原型上找。这样就形成了一个原型连。
function Foo(name) {
this.name = name
}
function Son() {}
// 子类原型拥有了一个指向父类实例的指针
Son.prototype = new Foo('len')
let son = new Son()
console.log(son.name) // len
这个name是在父类实例上的,我们再看
----------------------------
function Foo() {
}
function Son() {}
Foo.prototype.name = 'len'
Son.prototype = new Foo()
let son = new Son()
console.log(son.name) // len, 这里就是父类原型上的name属性了。
这就是一个最简单的原型链继承。子类Son拥有了父类Foo的name属性。
缺点: 所有的属性和方法都是共享的,且子类不能向父类传递参数
借用构造函数
通过在子类的构造函数里面来调用父类的构造函数
function Foo() {
this.name = 'len'
}
function Son() {
Foo.call(this)
}
let son = new Son()
console.log(son.name) // len
-------给父类传参--------
function Foo(name) {
this.name = name
}
function Son() {
Foo.apply(this, arguments)
}
let son1 = new Son('len')
let son2 = new Son('lance')
console.log(son1.name) // len
console.log(son2.name) // lance
这样即做到了参数私有化,而且还能给父类传参
缺点: 所有的属性和方法都是写在构造函数里面的,这样做不到复用
借用构造函数和原型链(俗称: 组合继承,也是目前最常用的继承方式)
也就是将构造和原型链结合起来
function Foo(name) {
this.name = name
}
function Son() {
Foo.apply(this, arguments)
}
Son.prototype = new Foo()
Foo.prototype.sayName = function() {
console.log('hello: ' + this.name)
}
let s1 = new Son('len')
let s2 = new Son('lance')
s1.sayName() // hello: len
s2.sayName() // hello: lance
这样就做到了sayName的复用,如果想私有化,只需要将方法放在构造函数内即可
这是目前最完美的解决方案了
原型式继承
function clone(o) {
function F() {}
F.prototype = o
return new F()
}
let person = {
name: 'len',
friends: ['a', 'b']
}
let p1 = clone(person) // len
let p2 = clone(person) // ['a', 'b']
console.log(p1.name) // len
console.log(p1.friends) // ['a', 'b']
console.log(p2.name)
console.log(p2.friends)
p1.friends.push('c')
console.log(p1.friends) // ['a', 'b', 'c']
console.log(p1.friends) // ['a', 'b', 'c']
这种模式的前提是你必须有一个对象可以作为另一个对象的基础。所暴露出来的问题和原型链继承一样的,所有的属性和方法都是共享的。所以,在没有必要兴师动众地创建构造函数,而只想让一个对象与另一个对象保持类似的情况下,原型链继承是完全可以胜任的。
上面的clone函数,在es5中,完全可以用Object.create()来替代。
寄生式继承
思路和原型式继承是紧密相关的
function create(o) {
// 这里也可以用上面的clone函数
var obj = Object.create(o)
obj.sayHi = function() {
console.log('hi')
}
return obj
}
let person = {
name: 'len',
friends: ['a', 'b']
}
let p1 = create(person)
p1.sayHi() // hi
寄生组合式继承
前面说过,组合继承是JavaScript最常用的继承模式;不过,它也有自己的不足。组合继承最大的问题就是无论什么情况下,都会调用两次父类构造函数。第一次是在new Foo(),第二次是在子类的构造函数中Foo.call(this)。
所谓寄生组合式继承,即通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。原理就是:不必为了指定子类型的原型而调用符类型的构造函数,我们所需要做的无非就是父类型原型的一个副本而已。
function inheritProperty(son, foo) {
// 父类原型副本
var prototype = Object.create(foo.prototype)
// 父类原型副本指向子类的构造函数
prototype.constructor = son
// 子类构造函数的prototype指向了父类(副本)的原型
son.prototype = prototype
}
function Foo(name) {
this.name = name
this.friends = ['a', 'b', 'c']
}
Foo.prototype.sayName = function() {
console.log(this.name)
}
function Son(name, age) {
Foo.call(this, name)
this.age = age
}
// 之前这里是 Son.prototype = new Foo(), 你可以自行比较一下
inheritProperty(Son, Foo)
Son.prototype.sayAge = function() {
console.log(this.age)
}
let s = new Son('len', 23)
console.log(s.friends) // ['a', 'b', 'c']
console.log(s.name) // len
s.sayName() // len
console.log(s.age) // 23
s.sayAge() // 23
说白了就是创键一个父类原型副本,将这个副本的constructor指向子类的构造函数,子类构造函数的prototype指向了父类原型副本。你可以这么理解,就是相当于foo === son了
(每一个原型都有一个constructor属性,每一个构造函数都有一个prototype属性)