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())
}
}
全部输出日志: