1.原型链继承
function SuperType () {
this.property = true
}
SuperType.prototype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
// 让子类的原型等于父类的实例
SubType.prototype = new SuperType()
SubType.prototype.getSubValue = function () {
return this.property
}
var instance = new SubType()
console.log(instance.getSuperValue()) //true
console.log(SubType.prototype.constructor === SuperType); //true
基本思想:让子类的原型等于父类的实例。
优点:子类的实例可继承的属性——子类的实例的构造函数的属性、父类构造函数属性、父类原型的属性,但不会继承父类实例的属性
缺点:
- 在创建子类的实例时,不能向父类的构造函数中传递参数。严格来说,是没有办法在不影响所有对象实例的情况下,给父类的构造函数传递参数
- 所有子类的实例都会共享父类的属性:包含引用类型值的原型属性会被所有实例共享。
- 继承方式单一。
2.构造函数继承
function SuperType (age) {
this.name = ['Mark','Jane','John']
this.age = age
}
function SubType (age) {
// 通过call()方法继承了SuperType,同时传递了参数
SuperType.call(this, age)
}
let ins1 = new SubType(18)
ins1.name.pop()
console.log(ins1.name) // ['Mark','Jane']
let ins2 = new SubType(20)
console.log(ins2.name) // ['Mark','Jane','John']
console.log(ins1.age, ins2.age); // 18 20
基本思想:通过call()或apply() 方法在子类构造函数的内部调用父类构造函数
优点:
- 子类实例可以向父类构造函数传递参数
- 解决了包含引用类型值的原型属性会被所有实例共享的问题
- 使用call()或apply()可以同时继承多个构造函数的属性,解决了继承方式单一的问题
缺点:
- 只能继承父类构造函数的属性。
- 每次使用都要重新调用,无法实现构造函数的复用。
- 每个子类实例都有父类构造函数的副本。
3.组合继承
function SuperType (age) {
this.name = ['Mark','Jane','John']
this.age = age
}
SuperType.prototype.callAge = function () {
alert(this.age)
}
function SubType (age) {
// 第二次调用SuperType()
SuperType.call(this, age)
}
// 第一次调用SuperType()
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
let ins1 = new SubType(18)
ins1.name.pop()
ins1.callAge() // 18
console.log(ins1.name) // ['Mark','Jane']
let ins2 = new SubType(20)
ins2.callAge() // 20
console.log(ins2.name) // ['Mark','Jane','John']
console.log(ins1.age, ins2.age); // 18 20
基本思想:使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承。
优点:
- 可以复用、也可以传参、还可以继承父类原型上的属性
- 每个子类的实例引入的构造函数属性都是私有的。
缺点:调用了两次父类构造函数,而且子类的构造函数会代替原型上的父类构造函数
4.原型式继承
function object (obj) {
function fn () {}
fn.prototype = obj
return new fn()
}
let person = {
name: 'Mark',
hobbies: ['football', 'basketball', 'swimmming']
}
let p1 = object(person)
p1.name = 'John'
p1.hobbies.pop()
let p2 = object(person)
p2.name = 'Jane'
p2.hobbies.push('volleyball')
console.log(person.hobbies) // ["football", "basketball", "volleyball"]
基本思想:在object()函数内部,先创建了一个临时性的构造函数,然后将传入的对象作为这个构造函数的原型,最后返回了这个临时类型的一个新实例。你可以根据具体需求对得到的对象加以修改。object.create() 方法规范化了原型式继承。
优点:本质上,object()对传入其中的对象执行了一次浅复制。
缺点:类似于原型链继承,包含引用类型值的属性始终都会共享相应的值;而且子类实例属性都是后面才添加的,无法实现复用。
5.寄生式继承
function object (obj) {
function fn () {}
fn.prototype = obj
return new fn()
}
function createObj (obj) {
// 通过调用函数创建一个新对象
var clone = object(obj)
// 以某种方式增强这个对象
clone.callHello = function () {
alert('Hello')
}
// 返回这个对象
return clone
}
let person = {
name: 'Mark',
hobbies: ['football', 'basketball', 'swimmming']
}
let p1 = createObj(person)
p1.callHello() // Hello
p1.name = 'John'
p1.hobbies.pop()
let p2 = createObj(person)
p2.name = 'Jane'
p2.hobbies.push('volleyball')
p2.callHello() // Hello
console.log(person.hobbies) // ["football", "basketball", "volleyball"]
基本思想:与寄生构造函数和工厂函数类似,即创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后再像真的是它做了所有工作一样返回对象。可以理解为在原型式继承的基础上包了一层特殊的壳。
优点:没有自定义类型,适合使用在主要考虑对象而不是自定义类型和构造函数的情况。
缺点:类似于构造函数继承,没有使用原型,不能做到函数复用而降低效率
6.寄生组合式继承
function object (obj) {
function fn () {}
fn.prototype = obj
return new fn()
}
// 寄生组合式继承的基本模式
function inheritProtoType (subType, superType) {
// 创建对象
var prototype = object(superType.prototype)
// 增强对象
prototype.constructor = subType
// 指定对象
subType.prototype = prototype
}
function SuperType (name) {
this.name = name
this.hobbies = ['football', 'basketball', 'swimming']
}
SuperType.prototype.callName = function () {
alert(this.name)
}
function SubType (name, age) {
SuperType.call(this, name)
this.age = age
}
inheritProtoType(SubType, SuperType)
SubType.prototype.callAge = function () {
alert(this.age)
}
let sub1 = new SubType('Mark', 20)
sub1.callName() // 'Mark'
sub1.callAge() // 20
console.log(sub1.hobbies); // ['football', 'basketball', 'swimming']
基本思想:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。我们需要的是父类原型的一个副本,不必为了指定子类的原型而调用父类的构造函数。本质上,其实就是使用寄生式继承来继承父类的原型,然后再将结果指定给子类的原型。
优点:只调用了一次父类构造函数,大大提高了效率。集寄生式继承和组合继承的优点于一身,是引用类型最理想的继承范式。
7.class继承
class Phone{
// 构造方法
constructor(brand , price){
this.brand = brand
this.price = price
}
// 父类的成员属性
call(){
console.log('可以打电话')
}
}
class SmartPhone extends Phone{
// 构造方法
constructor(brand , price , color , size){
// 调用父类的方法
super(brand , price)
this.color = color
this.size = size
}
photo(){
console.log('可以拍照')
}
playgame(){
console.log('可以玩游戏')
}
// 重写
call(){
console.log('可以进行视频通话')
}
}
const iPhone = new SmartPhone('苹果',2999,'black','4.7inch')
console.log(iPhone) // SmartPhone {brand: "苹果", price: 2999, color: "black", size: "4.7inch"}
iPhone.call() // 可以进行视频通话
iPhone.photo() // 可以拍照
iPhone.playgame() // 可以玩游戏
ES6中的class可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰,更加面向对象编程的语法而已。
详细请参见: 阮一峰的ES6入门 class的继承.