【延伸学习】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())
    }
}

全部输出日志:

相关推荐
joan_8528 分钟前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
还是大剑师兰特1 小时前
什么是尾调用,使用尾调用有什么好处?
javascript·大剑师·尾调用
Watermelo6171 小时前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
一个处女座的程序猿O(∩_∩)O3 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
燃先生._.9 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖10 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
black^sugar11 小时前
纯前端实现更新检测
开发语言·前端·javascript
2401_8576009513 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009513 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js