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

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

一. 基(父)类

TypeScript 复制代码
//----------------------------- 狗 -----------------------------//
// "狗"类的构造函数
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

TypeScript 复制代码
//----------------------------- 狗-比熊 -----------------------------//
// 比熊的构造函数
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

TypeScript 复制代码
//----------------------------- 狗-萨摩 -----------------------------//
// 萨摩的构造函数
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上,从而实现了继承父类成员函数的功能。

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

TypeScript 复制代码
//----------------------------- 狗-拉布拉多 -----------------------------//
// 拉布拉多构造函数
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:上文我写了两种成员变量,两种原型函数的继承法防,都可以选出来组合着使用的。

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

TypeScript 复制代码
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也被修改了。

核心结论:

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

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

TypeScript 复制代码
//----------------------------- 狗-金毛 -----------------------------//
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

TypeScript 复制代码
//----------------------------- 狗-贵宾 -----------------------------//
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来做了一个类的继承测试......

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

TypeScript 复制代码
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())
    }
}

全部输出日志:

相关推荐
乐悠小码12 分钟前
Java设计模式精讲---04原型模式
java·设计模式·原型模式
摸着石头过河的石头21 分钟前
Service Worker 深度解析:让你的 Web 应用离线也能飞
前端·javascript·性能优化
不爱吃糖的程序媛1 小时前
Electron 如何判断运行平台是鸿蒙系统(OpenHarmony)
javascript·electron·harmonyos
Hilaku1 小时前
我用AI重构了一段500行的屎山代码,这是我的Prompt和思考过程
前端·javascript·架构
Cxiaomu1 小时前
React Native App 自动检测版本更新完整实现指南
javascript·react native·react.js
掘金安东尼2 小时前
前端周刊第439期(2025年11月3日–11月9日)
前端·javascript·vue.js
起这个名字3 小时前
微前端应用通信使用和原理
前端·javascript·vue.js
鹏多多3 小时前
Web使用natapp进行内网穿透和预览本地页面
前端·javascript
钱端工程师3 小时前
uniapp封装uni.request请求,实现重复接口请求中断上次请求(防抖)
前端·javascript·uni-app
茶憶3 小时前
uni-app app移动端实现纵向滑块功能,并伴随自动播放
javascript·vue.js·uni-app·html·scss