谈谈我对js继承的几种实现方法

1、原型链继承

=》SubFun.prototype = new SuperFun()

首先谈谈原型、构造函数和实例以及三者之间的关系

原型:所有对象都有_prop_属性

构造函数:其实就是函数,其特点就是函数名首字母大写和得使用new实例对象调用;

三者关系:创建构造函数同时会自动创建一个property原型属性,每一个property属性都有constructor属性,这个constructor属性指向得就是构造函数

搞清楚上面三者关系,回归正文,原型链继承方法

//构造函数实现通用属性和方法,new实例方法继承构造函数的属性和方法
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
    console.log('都有'+this.has);
    console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
let huahua = new SuperFun('花花',23)
huahua.getSuperValue()
huahua.has.push('耳朵')
console.log(...huahua.has); //头发 眼睛 鼻子 耳朵
let ming = new SuperFun('明明',21)
ming.getSuperValue()
ming.has.push('嘴巴')
console.log(...ming.has); //头发 眼睛 鼻子 嘴巴

// 原型链继承,其实就是通过prototype原型属性的方式继承父类
function SubFun (name,age) {
     // 公共的属性
     this.name = name
     this.age = age
}
SubFun.prototype = new SuperFun()  //原型链继承
// 实例对象
let zhangsan = new SubFun('张三',11)
zhangsan.getSuperValue()  //都有头发,眼睛,鼻子
zhangsan.has.push('大长腿')
console.log(...zhangsan.has); //头发 眼睛 鼻子 大长腿

let lisi = new SubFun('李四',10)
lisi.getSuperValue()  //都有头发,眼睛,鼻子,大长腿
lisi.has.push('乌黑头发')
console.log(...lisi.has); //头发 眼睛 鼻子 大长腿 乌黑头发
// 原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。

// 解决方案:使用组合继承,原型链继承结合构造函数继承
// 在子构造函数中使用  通过拷贝父类属性
    // SuperFun.call(this,name,age)

原型链方案存在的缺点:多个实例对引用类型的操作会被篡改。

2、构造函数继承

=》 SuperFun.call(this,arguments)

借用父类的构造实例来增强子类实例,其实相当于复制一份父类构造函数中属性和方法

function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
    console.log('都有'+this.has);
    console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
function SubFun (name,age) {
   //  通过拷贝父类
   SuperFun.call(this,name,age)
   // 所有子类都有父类实例函数的副本
}

// 实例对象
let zhangsan = new SubFun('张三',11)
//zhangsan.getSuperValue()  // Uncaught TypeError: zhangsan.getSuperValue is not a function
zhangsan.has.push('大长腿')
console.log(...zhangsan.has); //头发 眼睛 鼻子 大长腿

let lisi = new SubFun('李四',10)
//lisi.getSuperValue()  // Uncaught TypeError: zhangsan.getSuperValue is not a function
lisi.has.push('乌黑头发')
console.log(...lisi.has); //头发 眼睛 鼻子 乌黑头发

/*
缺点:
只能继承父类的实例属性和方法,无法继承原型上的属性和方法
无法实现复用,所有子类都有父类实例函数的副本,影响性能
*/ 

缺点:
只能继承父类的实例属性和方法,无法继承原型上的属性和方法
无法实现复用,所有子类都有父类实例函数的副本,影响性能

3、组合继承

=》结合原型链和构造函数两者方法

//构造函数实现通用属性和方法,new实例方法继承构造函数的属性和方法
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
    console.log('都有'+this.has);
    console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}

// 原型链继承,其实就是通过prototype原型属性的方式继承父类
function SubFun (name,age,address) {
    //  通过拷贝父类属性
    SuperFun.call(this,name,age)
    this.address = address
}
SubFun.prototype = new SuperFun()  //原型链继承
// console.log(SubFun.prototype.constructor); //指向SuperFun
// 重写SubFun.prototype的constructor属性,指向自己的构造函数SubFun
SubFun.prototype.constructor = SubFun
// console.log(SubFun.prototype.constructor); //指向SuperFun
// 实例对象
let zhangsan = new SubFun('张三',11,'江西省上饶市')
zhangsan.getSuperValue()  //都有头发,眼睛,鼻子
zhangsan.has.push('大长腿')
console.log(zhangsan.has); //头发 眼睛 鼻子 大长腿
console.log(zhangsan.address);
let lisi = new SubFun('李四',10,'上海市浦东新区')
lisi.getSuperValue()  //都有头发,眼睛,鼻子
lisi.has.push('乌黑头发')
console.log(lisi.has); //头发 眼睛 鼻子 乌黑头发
console.log(lisi.address);

缺点就是在使用子类创建实例对象时,其原型中会存在两份相同的属性/方法。

4、原型式继承

/* 
原型式继承:
利用一个空对象作为中介,将某个对象直接赋值给空对象构造函数的原型
*/
function object (obj) {
    function F(){}
    F.prototype = obj
    return new F()
}
// object()对传入对象进行一次浅复制,将构造函数F的原型直接指向传入的对象,这样子类实例将共享该对象的所有属性和方法
let person = {
    name:'person',
    friends:['a','b']
}

let firstP = object(person)
firstP.name = 'xiaoxiao'
firstP.friends.push('cc')
console.log(firstP);

let secondP = object(person)
secondP.name = 'minghua'
secondP.friends.push('dd')
console.log(secondP);
console.log(person);

缺点:原型链继承多个实例的引用类型属性指向相同,存在篡改可能;无法传递参数。

ES5存在object.create()方法,能够替代上面的object方法

5、寄生式继承

// 寄生式继承:在原型式继承基础上,增加对象,返回构造函数
function commonP (original) {
    let clone = object(original) // 通过调用 object() 函数创建一个新对象
    clone.sayFun = function(){   // 以某种方式来增强对象
        console.log('hi hello world');
    }
    return clone  // 返回这个对象
}
let children = {
    name:'children',
    friends:['q1','q2']
}
let anotherP = commonP(children)
anotherP.name = 'xx'
anotherP.friends.push('p1') 
console.log(anotherP);
console.log(anotherP.friends);
anotherP.sayFun()
let otherP = commonP(children)
otherP.name = 'gg'
otherP.friends.push('mm')
console.log(otherP);
console.log(otherP.friends);
otherP.sayFun()
console.log(children);
/*
friends: (4) ["q1", "q2", "p1", "mm"]
name: "children" 
*/

缺点:(同原型式继承)

原型链继承多个实例的引用属性存在相同,存在篡改可能;无法传递参数

6、寄生组合式继承

/* 
寄生组合式继承:结合借用构造函数传递参数和寄生模式实现继承
*/
// 寄生式
function inheritPrototype (subType,superType) {
    // 创建对象
    let __prototype = Object.create(superType.prototype)  //创建父类原型的一个副本
    // 指定对象
    subType.prototype = __prototype    //将新创建的对象赋值给子类的原型
    // 增强对象
    __prototype.constructor = subType  //防止因重写原型而失去默认的constructor属性
}
// 父类初始化实例属性和原型属性
function SuperFun (name,age) {
    // 公共的属性
    this.name = name
    this.age = age
    this.has = ['头发','眼睛','鼻子']
}
// 公共方法
SuperFun.prototype.getSuperValue = function () {
    console.log('都有'+this.has);
    console.log(this.age+'岁'+this.name+'在晨跑锻炼');
}
//  借用构造函数传递增强子类实例属性(支持传参和避免篡改)
function SubFun (name,age,sex) {
   //  通过拷贝父类
   SuperFun.call(this,name,age) // 所有子类都有父类实例函数的副本
   this.sex = sex
}
// 将父类原型指向子类
inheritPrototype(SubFun,SuperFun)
// 新增子类原型属性
SubFun.prototype.saySex = function () {
    console.log(this.sex);
}
let instance1 = new SubFun('ss',12,'女')
instance1.has.push('aa')
let instance2 = new SubFun('gg',11,'男')
instance2.has.push('bb')
console.log(instance1);
console.log(instance2);

 这个例子的高效率体现在它只调用了一次SuperType 构造函数,并且因此避免了在SubType.prototype 上创建不必要的、多余的属性。于此同时,原型链还能保持不变;因此,还能够正常使用instanceof 和isPrototypeOf()

推荐使用这种继承方法,这也是最成熟的方法,也是库实现的方法

7、混入方式继承多个对象

function SuperCls (name) {
    this.name = name
    this.friends = ['a']
}
SuperCls.prototype.superMethod = function () {
    console.log('SuperCls');
}
function OtherSuperCls (sex) {
    this.sex = sex
}
OtherSuperCls.prototype.otherSuperMethod = function () {
    console.log('otherSuperMethod');
}
// 拷贝父构造函数实例所有属性
function MyCls (name,sex) {
    SuperCls.call(this,name)
    OtherSuperCls.call(this,sex)
}
// 继承一个类
MyCls.prototype = Object.create(SuperCls.prototype)
// 重新指定constructor属性
MyCls.prototype.constructor = MyCls
// 混入其他
Object.assign(MyCls.prototype,OtherSuperCls.prototype)
MyCls.prototype.myMethod = function () {
    // do something
    console.log('do something');
}
let instance1 = new MyCls('ss','女')
instance1.friends.push('aa')
let instance2 = new MyCls('gg','男')
instance2.friends.push('bb')
console.log(instance1);
console.log(instance2);

Object.assign会把 OtherSuperCls原型上的函数拷贝到 MyCls原型上,使 MyCls的所有实例都可用 OtherSuperCls的方法。 

8、ES6类继承extends

extends关键字主要用于类声明或者类表达式中,以创建一个类,该类是另一个类的子类。其中constructor表示构造函数,一个类中只能有一个构造函数,有多个会报出SyntaxError错误,如果没有显式指定构造方法,则会添加默认的 constructor方法,子类继承父类属性使用super;

一个构造函数可以使用 super 关键字来调用一个父类的构造函数。

class Rectangle {
    constructor(w,h){
        this.height = h
        this.width = w
    }
    get area(){
        return this.width*this.height
    }
}
const reactangle = new Rectangle(10,20)
console.log(reactangle.area); //200
console.log(reactangle);
// ------------------------继承--------------------------------
class Square extends Rectangle {
    constructor(length){
        super(length,length)
       // 如果子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
        this.name='square'
    }
    get area(){
        return this.width*this.height
    }
}
const square = new Square(10)
console.log(square.area); //100
console.log(square);

extends继承的核心代码如下,其实现和上述的寄生组合式继承方式一样

function _inherits(subType, superType) {
  
    // 创建对象,创建父类原型的一个副本
    // 增强对象,弥补因重写原型而失去的默认的constructor 属性
    // 指定对象,将新创建的对象赋值给子类的原型
    subType.prototype = Object.create(superType && superType.prototype, {
        constructor: {
            value: subType,
            enumerable: false,
            writable: true,
            configurable: true
        }
    });
    
    if (superType) {
        Object.setPrototypeOf 
            ? Object.setPrototypeOf(subType, superType) 
            : subType.__proto__ = superType;
    }
}

总结

1、函数声明和类声明的区别

函数声明会提升,类声明不会。首先需要声明你的类,然后访问它

2、ES5和ES6继承区别


ES5没有类概念,ES5继承实质上是先创建子类的实例对象,再将父类的属性和方法添加到this上(SuperFun.call(this))
ES6继承实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this;
因为子类没有自己的this对象,所以必须先调用父类super()方法,否则新建实例报错

/* 
总结
1、函数声明会提升,类声明不会;首先需要声明后才能访问,否则会报错
*/
let instace1 = new Rectangle()
class Rectangle{}
// Uncaught ReferenceError: Cannot access 'Rectangle' before initialization

/*2、ES5继承和ES6继承的区别
ES5没有类概念,ES5继承实质上是先创建子类的实例对象,再将父类的属性和方法添加到this上(SuperFun.call(this))
ES6继承实质上是先创建父类的实例对象this,然后再用子类的构造函数修改this;
因为子类没有自己的this对象,所以必须先调用父类super()方法,否则新建实例报错
*/ 

《javascript高级程序设计》笔记:继承

MDN之Object.create()

MDN之Class

有什么不对的地方可以给出您宝贵的评论或意见

参考来源:https://juejin.im/post/5bcb2e295188255c55472db0

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值