【延伸学习】TS(JS)类的继承(prototype、call、apply,extends)

文章详细介绍了JavaScript中类的继承机制,包括通过原型链、原型方法的继承、call和apply实现成员变量和方法的继承,以及ES6中的extends关键字。作者还对比了不同方式的优缺点,强调了组合继承和寄生式继承的使用和原理。
摘要由CSDN通过智能技术生成

PS:文末附上完整的代码(是在CocosCreator下运行的)

一. 基(父)类
 

//----------------------------- 狗 -----------------------------//
// “狗”类的构造函数
function Dog(name, age){
    this.name = name
    this.age = age
    this.luckyNumber = [1,2,3]
}

// 类方法,有点像静态方法,不用创建对象,用类名直接调用的方法
Dog.eat = function(){
    console.log("dog eat")
}

// 通过原型设置狗的两个原型方法
Dog.prototype.showName = function(){
    console.log("dog - name = ", this.name)
}

Dog.prototype.showAge = function(){
    console.log("dog - age = ", this.age)
}

console.log("--------------------- 狗-测试 ---------------------")
var dog = new Dog("lucky", 18)
Dog.eat()
dog.showName()
dog.showAge()
console.log("dog info = ", dog.name, dog.age)

基类包含三个成员变量(名字、年龄)还有一个后面用于测试的数组,两个原型方法(输出名字,输出年龄),还有一个类似静态函数的方法。

基类唯一测试的,就是用类名可以直接调用eat这个静态方法。

二. 继承原型方法-通过遍历prototype

//----------------------------- 狗-比熊 -----------------------------//
// 比熊的构造函数
function Dog_BX(){
    this.new_name = "bixiong"
}

// 使用原型来继承方法【写法1】
Dog_BX.prototype = {}
for(var i in Dog.prototype){
    Dog_BX.prototype[i] = Dog.prototype[i]
}

Dog_BX.prototype.showName = function(){
    console.log("dog_bx name = ", this.new_name)
}


console.log("--------------------- 比熊-测试 ---------------------")
var bx_Dog = new Dog_BX()
bx_Dog.showName()
bx_Dog.showAge()
console.log("bx_dog info = ", bx_Dog.name, bx_Dog.age, bx_Dog.new_name)

代码:
1.通过遍历基类的prototype方法,来把基类的原型方法赋值给比熊类,从而实现了方法的继承。
2.通过重写showName的原方法,实现了函数的重载。

输出:
1.showName方法的输出,证实里调用的是子类的showName,重载成功。
2.showAge也调用成功了,调用的是父类的showAge,但由于子类没继承父类的成员变量(更没有赋值了),所以输出的age是未定义。
3.输出结果也同样显示,并没有继承父类的成员变量。

核心结论:
通过遍历prototype再赋值给子类,来实现原型方法上的继承。

三.继承成员变量-通过call

//----------------------------- 狗-萨摩 -----------------------------//
// 萨摩的构造函数
function Dog_SM(age){
    // 使用call继承成员变量【写法1】
    Dog.call(this, "sm", age)
}

console.log("--------------------- 萨摩-测试 ---------------------")
var sm_Dog = new Dog_SM(10)
console.log("is function exist = ", sm_Dog.showName, sm_Dog.showAge)
console.log("sm_Dog info = ", sm_Dog.name, sm_Dog.age)

代码:
1.Dog.call,使用call来实现继承父类成员函数的功能。

输出:
1.可以看到父类的原型方法并没有被继承到。
2.父类的成员变量被继承了。

核心结论:
使用call来实现继承父类成员函数的功能。其实大致原理是通过改变this的绑定,调用父类构造函数进行赋值时,把赋值的内容赋到子类的this上,从而实现了继承父类成员函数的功能。

四.通过组合实现完美继承

//----------------------------- 狗-拉布拉多 -----------------------------//
// 拉布拉多构造函数
function Dog_LBLD(age){
    // 使用apply继承成员变量【写法2】
    Dog.apply(this, ["lbld", age])
}

// 使用原型来继承方法【写法2】
var obj = function() {}
obj.prototype = Dog.prototype
Dog_LBLD.prototype = new obj()




console.log("--------------------- 拉布拉多-测试 ---------------------")
var lbld_Dog = new Dog_LBLD(5)
lbld_Dog.showName()
lbld_Dog.showAge()
console.log("lbld_Dog info = ", lbld_Dog.name, lbld_Dog.age)

代码:
1.上面使用了call,这里使用apply,实现了成员变量的继承。
PS:我还没太分得清call和apply的区别,也就一个传可变长参数,一个传数组作为参数……
2.通过把Dog.prototype赋值给obj,再new一个obj给Dog_LBLD实现了原型方法的继承。
PS:为什么要写得这么绕?大致就和深拷贝浅拷贝的原理有关,下文【5】再详述吧。

输出:
简单概括,就是成员变量和原型函数都继承下来了,实现了完美的继承。

核心结论:
分别通过不同的方式组合起来实现了成员变量和原型函数的继承。
虽然标题的“完美”二字只是代表成员变量和原型函数都同时继承下来的意思,但当前这种继承方式应该是避开了浅拷贝的问题了,算是比较好的继承方法了。

PS:上文我写了两种成员变量,两种原型函数的继承法防,都可以选出来组合着使用的。

五.原型链直接继承(不建议这样做)

function Dog_BM(){

}

Dog_BM.prototype = new Dog("BoMei", 9)




console.log("--------------------- 博美-测试 ---------------------")
var bm_Dog = new Dog_BM()
var bm_Dog2 = new Dog_BM()
bm_Dog.age = 10
bm_Dog.luckyNumber.push(4)
bm_Dog.showName()
bm_Dog.showAge()
console.log("bm_dog info = ", bm_Dog.name, bm_Dog.age, bm_Dog.luckyNumber)
console.log("bm_dog2 info = ", bm_Dog2.name, bm_Dog2.age, bm_Dog2.luckyNumber)

代码:
1.非常简单的,直接new Dog赋值给prototype

输出:
1.成功调用了父类的showName以及showAge方法,并能显示出name和age两个成员函数来。
2.根据【bm_dog info】显示,也顺利的对age和luckyNumber数组进行修改。
3.这种写法方便时方便,但有个致命的缺点,他对引用的处理是浅拷贝的,可以看到,我们只修改了bm_Dog的luckyNumber数组,添加了4进去,可bm_dog2的luckyNumber数组居然也有4. 所以强烈不建议使用这种方式来做继承。 此处也回应了上文【4】中对prototype的处理为什么要这么绕了。

PS:在【4】中对prototype的处理中,我也尝试过用更简单的处理方法:
Dog_LBLD.prototype = Dog.prototype
这样写更加大错特错,因为这样二者的原型函数就会指向同一个内存,当我在Dog_LBLD里重载showName时,最终发现,连父类Dog的ShowName也被修改了。

核心结论:
写法很简单,缺点很明显,就是通过浅拷贝来做继承,要是成员变量存在引用,修改成员变量时,可能会影响到所有对象的成员变量。

六.寄生式组合继承(最优解)

//----------------------------- 狗-金毛 -----------------------------//
function Dog_JM(age){
    // 使用apply继承成员变量【写法2】
    Dog.apply(this, ["jinmao", age])
}

// 使用原型来继承方法【写法3】
Dog_JM.prototype = Object.create(Dog.prototype)




console.log("--------------------- 金毛-测试 ---------------------")
var jm_Dog = new Dog_JM(5)
jm_Dog.showName()
jm_Dog.showAge()
console.log("jm_Dog info = ", jm_Dog.name, jm_Dog.age, jm_Dog.luckyNumber)

容许我说一段废话文学吧……有点气死我了……

文章写到此处,其实前5个例子都是我手打,我都能理解的……最后这第六个例子,我看别的文章里说的,说是做继承的最优解。因为别的文章中有提到一种“组合继承”的方式,需要调用多次父类构造函数,效率不是最优(我以为他说的组合继承就是我【4】里面写得那种……)。用这种寄生式继承能完美解决,而这种方式一个我不理解的地方就是Object.create,我不知道他的实现源码是什么。 所以我就当做他是原型继承我想到的第三种方法……

后来我觉得不能不求甚解,就跑去找Object.create的实现源码,结果发现,Object.create这个方法的内部逻辑就是文章【4】里面原型继承的【写法2】………………  当时我写【4】的时候就觉得很好了,还用“完美继承”来形容……

核心结论:
所以这个方法【6】其实和方案【4】是一模一样的………………

七.extends

//----------------------------- 狗-贵宾 -----------------------------//
class Dog2{
    protected name

    constructor(name){
        this.name = name
    }

    showName = function(){
        console.log("Dog2 name = ", this.name)
    }
}

class Dog_GB extends Dog2{
    private age
    constructor(name, age){
        // 初始化父类构造函数
        super(name)
        this.name = name
        this.age = age
    }

    getName(){
        return this.name
    }

    getAge(){
        return this.age
    }
}

代码:
1.父类一个成员变量,一个成员函数,一个构造函数。
2.子类构造函数一定要调用super对父类进行初始化,然后子类新增二个成员函数和一个成员变量。

输出:
1.成功使用了父类的方法。
2.成功使用了子类的方法来获取父类的成员变量,证明成员变量继承成功。

核心结论:
就是用extends来做了一个类的继承测试……

以上就是对继承的全部测试了,下面贴上完全的全部代码:

const {ccclass, property} = cc._decorator;

//----------------------------- 狗 -----------------------------//
// “狗”类的构造函数
function Dog(name, age){
    this.name = name
    this.age = age
    this.luckyNumber = [1,2,3]
}

// 类方法,有点像静态方法,不用创建对象,用类名直接调用的方法
Dog.eat = function(){
    console.log("dog eat")
}

// 通过原型设置狗的两个原型方法
Dog.prototype.showName = function(){
    console.log("dog - name = ", this.name)
}

Dog.prototype.showAge = function(){
    console.log("dog - age = ", this.age)
}

//----------------------------- 狗-比熊 -----------------------------//
// 比熊的构造函数
function Dog_BX(){
    this.new_name = "bixiong"
}

// 使用原型来继承方法【写法1】
Dog_BX.prototype = {}
for(var i in Dog.prototype){
    Dog_BX.prototype[i] = Dog.prototype[i]
}

Dog_BX.prototype.showName = function(){
    console.log("dog_bx name = ", this.new_name)
}

//----------------------------- 狗-萨摩 -----------------------------//
// 萨摩的构造函数
function Dog_SM(age){
    // 使用call继承成员变量【写法1】
    Dog.call(this, "sm", age)
}

//----------------------------- 狗-拉布拉多 -----------------------------//
// 拉布拉多构造函数
function Dog_LBLD(age){
    // 使用apply继承成员变量【写法2】
    Dog.apply(this, ["lbld", age])
}

// 使用原型来继承方法【写法2】
var obj = function() {}
obj.prototype = Dog.prototype
Dog_LBLD.prototype = new obj()

//----------------------------- 狗-博美 -----------------------------//
function Dog_BM(){

}

Dog_BM.prototype = new Dog("BoMei", 9)

//----------------------------- 狗-金毛 -----------------------------//
function Dog_JM(age){
    // 使用apply继承成员变量【写法2】
    Dog.apply(this, ["jinmao", age])
}

// 使用原型来继承方法【写法3】
Dog_JM.prototype = Object.create(Dog.prototype)

//----------------------------- 狗-贵宾 -----------------------------//
class Dog2{
    protected name

    constructor(name){
        this.name = name
    }

    showName = function(){
        console.log("Dog2 name = ", this.name)
    }
}

class Dog_GB extends Dog2{
    private age
    constructor(name, age){
        // 初始化父类构造函数
        super(name)
        this.name = name
        this.age = age
    }

    getName(){
        return this.name
    }

    getAge(){
        return this.age
    }
}

@ccclass
export default class NewClass extends cc.Component {

    onLoad(){
        console.log("--------------------- 狗-测试 ---------------------")
        var dog = new Dog("lucky", 18)
        Dog.eat()
        dog.showName()
        dog.showAge()
        console.log("dog info = ", dog.name, dog.age, dog.luckyNumber)

        console.log("--------------------- 比熊-测试 ---------------------")
        var bx_Dog = new Dog_BX()
        bx_Dog.showName()
        bx_Dog.showAge()
        console.log("bx_dog info = ", bx_Dog.name, bx_Dog.age, bx_Dog.new_name)

        console.log("--------------------- 萨摩-测试 ---------------------")
        var sm_Dog = new Dog_SM(10)
        console.log("is function exist = ", sm_Dog.showName, sm_Dog.showAge)
        console.log("sm_Dog info = ", sm_Dog.name, sm_Dog.age)

        console.log("--------------------- 拉布拉多-测试 ---------------------")
        var lbld_Dog = new Dog_LBLD(5)
        lbld_Dog.showName()
        lbld_Dog.showAge()
        console.log("lbld_Dog info = ", lbld_Dog.name, lbld_Dog.age, lbld_Dog.luckyNumber)

        console.log("--------------------- 博美-测试 ---------------------")
        var bm_Dog = new Dog_BM()
        var bm_Dog2 = new Dog_BM()
        bm_Dog.age = 10
        bm_Dog.luckyNumber.push(4)
        bm_Dog.showName()
        bm_Dog.showAge()
        console.log("bm_dog info = ", bm_Dog.name, bm_Dog.age, bm_Dog.luckyNumber)
        console.log("bm_dog2 info = ", bm_Dog2.name, bm_Dog2.age, bm_Dog2.luckyNumber)

        console.log("--------------------- 金毛-测试 ---------------------")
        var jm_Dog = new Dog_JM(5)
        jm_Dog.showName()
        jm_Dog.showAge()
        console.log("jm_Dog info = ", jm_Dog.name, jm_Dog.age, jm_Dog.luckyNumber)

        console.log("--------------------- 贵宾-测试 ---------------------")
        var gb_Dob = new Dog_GB("guibin", 13)
        gb_Dob.showName()
        console.log("gb_Dog info = ", gb_Dob.getName(), gb_Dob.getAge())
    }
}

全部输出日志:

  • 18
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值