对象继承的六种方式

53 阅读4分钟

原型链继承

  • 利用原型让一个引用类型继承另一个引用类型的属性和方法
  • 核心:原型链对象变成父类实例,子类就可以调用父类方法和属性(子类.prototype = new 父类())
function Parent() {
    this.likeFood = ['水果', '鸡', '烤肉']
}
Parent.prototype.age = 18
Parent.prototype.getName = function () {
    return this.name
}

function Child(name) {
    this.name = name
}
Child.prototype = new Parent()
var child = new Child('leo')
// 这样子类就可以调用父类的属性和方法
console.log(child.getName())    // leo
console.log(child.age)          // 18
Child.prototype.constructor = Child//修改constructor的指向

var chongqiChild = new Child('重庆孩子')
var guangdongChild = new Child('广东孩子')

// 重庆孩子还喜欢吃花椒,但是原型链继承会变成所有孩子喜欢的食物都增加了花椒
chongqiChild.likeFood.push('花椒')
console.log(chongqiChild.likeFood)      // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
  • 缺点:在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱;在创建子类型的时候不能向超类型传递参数

借用构造函数实现继承

  • 子类构造函数内部调用父类构造函数,并传入this指针
// 2. 借用构造函数
function Parent(name) {
    this.name = name
    this.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name) {
    Parent.call(this, name)
}
Parent.prototype.getName = function() {
    return this.name
}
var chongqingChild = new Child('重庆孩子')
var guangdongChild = new Child('广东孩子')
chongqingChild.likeFood.push('花椒')

console.log(chongqingChild.likeFood)    //  ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    //  ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        //  "重庆孩子"
console.log(chongqingChild.getName())   //  Uncaught TypeError: chongqingChild.getName is
  • 优点:解决了不能向超类型传递参数的缺点
  • 缺点:无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到

组合继承(JS经典继承方式)

  • 结合原型链继承和借助构造函数方式,通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承
function Parent(name){
   this.name = name
   this.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name,age){
   Parent.call(this,name)//借助构造函数实现继承
   this.age = age
}
Parent.prototype.getName = function() {
   return this.name
}
Child.prototype = new Parent() //原型链继承
Child.prototype.constructor = Child
Child.prototype.getAge = function(){
   return this.age
}
var chongqingChild = new Child('重庆孩子', 18)
var guangdongChild = new Child('广东孩子', 19)
chongqingChild.likeFood.push('花椒')

console.log(chongqingChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        // "重庆孩子"
console.log(chongqingChild.getName())   // "重庆孩子"
console.log(chongqingChild.getAge())    // 18
  • 缺点:由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性

原型式继承

  • 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
  • 向函数中传入一个对象,然后返回一个以这个对象为原型的对象
  • 多用于对象与对象之间

/*
    @function 实现继承 函数
    @param parent 充当父类的对象
*/
function realizeInheritance(parent) {
    // 临时函数
    function tempFunc() {}
    tempFunc.prototype = parent
    return new tempFunc()
}
// 这个就是已有的对象
var baba = {
    name: "爸爸",
    likeFoods: ["水果", "鸡", "烤肉"]
}
/*
    var newChild = {} <==> baba  这两个对象建立关系就是这种继承的核心了。
*/
var child1 = realizeInheritance(baba)
var child2 = realizeInheritance(baba)
child1.likeFoods.push('花椒')
console.log(child1.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
console.log(child2.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
  • ES5 新增了个 Object.create(parentObject) 函数来更加便捷的实现上述继承
var baba = {
    name: "爸爸",
    likeFoods: ["水果", "鸡", "烤肉"]
}
var child1 = Object.create(baba)
var child2 = Object.create(baba)
child1.likeFoods.push('花椒')
console.log(child1.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]
console.log(child2.likeFoods) //    ["水果", "鸡", "烤肉", "花椒"]

寄生式继承

  • 创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象
function realizeInheritance(parent) {
    // 临时函数
    function tempFunc() {}
    tempFunc.prototype = parent
    return new tempFunc()
}
// Parasitic: 寄生的    inheritance: 继承    一个最简单的工厂函数。
function parasiticInheritance(object) {
    var clone = realizeInheritance(object)  
  // 这是用了原型式继承,但是只要是任何可以返回对象的方法都可以
    clone.sayName = function() {
        console.log('我是'+this.name)
    }
    return clone
}
var baba = {
    name: "爸爸",
    likeFoods: ["水果", "鸡", "烤肉"]
}
var child = parasiticInheritance(baba)
child.name = '儿子'
child.sayName() // 我是儿子
  • 优点就是对一个简单对象实现继承,如果这个对象不是自定义类型时
  • 缺点是没有办法实现函数的复用

寄生组合式继承

function Parent(name) {
    this.name = name
    this.likeFood = ["水果", "鸡", "烤肉"]
}
function Child(name, age) {
    Parent.call(this, name)
    this.age = age
}
Parent.prototype.getName = function() {
    return this.name
}

// Child.prototype = new Parent()  使用新方法解决
// Child.prototype.constructor = Child
inheritPrototype(Child, Parent)
function inheritPrototype(childFunc, parentFunc) {
    var prototype = realizeInheritance(parentFunc.prototype)   //创建对象,我们继续使用原型式继承的创建
    prototype.constructor = childFunc              //增强对象
    childFunc.prototype = prototype                //指定对象
}
function realizeInheritance(parent) {
    // 临时函数
    function tempFunc() {}
    tempFunc.prototype = parent
    return new tempFunc()
}

Child.prototype.getAge = function() {
    return this.age
}

var chongqingChild = new Child('重庆孩子', 18)
var guangdongChild = new Child('广东孩子', 19)
chongqingChild.likeFood.push('花椒')

console.log(chongqingChild.likeFood)    // ["水果", "鸡", "烤肉", "花椒"]
console.log(guangdongChild.likeFood)    // ["水果", "鸡", "烤肉"]
console.log(chongqingChild.name)        // "重庆孩子"
console.log(chongqingChild.getName())   // "重庆孩子"
console.log(chongqingChild.getAge())    // 18
  • 优点:组合继承+寄生式继承
  • 缺点在于调用两次父类构造函数,子类原型有冗余属性,寄生式继承的特性规避了这类情况,集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式

---------------------------------------------------------------------------2024.5.18每日一题