浅谈javaScript(ES5)继承

   JavaScript是一门基于原型的语言(不同于类),但并不意味着它不能写面向对象的代码,这篇文章将讲述JavaScript面向对象继承的内容

如何使用JavaScript写一个类呢,ES5中没有class关键字,如果要实现一个类,需要使用function来模拟

比如

function Person(name,age,sex) {
   this.name = name;
   this.age = age;
   this.sex = sex;
}
Person.prototype = {
    constructor : Person,
    getName: function() {
       return this.name;
    },
    getAge:function() {
       return this.age
    },
    
    getSex:function() {
       return this.sex
    },
    id:20
}
Person.id = "abcd";
var p = new Person("dpf",12,"man");
console.log(p.getName());

这是一种比较好的实现类的方法(属性放函数体的this里,方法放原型里),当然也还有其他方式,那么如何来理解这个呢,使用面向对象语言的说法,函数体Person里的内容可以看作是构造函数,Person.prototype指向的对象可以认为是一个类里面的方法或属性,而Person.id就是类属性或静态属性,如果是方法的话,就是类方法或静态方法。

那么如何去继承一个类呢,首先继承的本质是代码复用,也就是所只要把一个类的属性和方法都给另一个类那么就实现了继承,JavaScript中就是这个思路,所以来看第一种继承方式:

1.原型继承


//原型继承
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

//类B
function ClassB(a,b,c){
    this.a = a;
    this.b = b;
    this.c = c;
}
//继承的关键
ClassB.prototype = new ClassA();

console.log(ClassB.prototype.constructor === ClassA);//这里会输出true
//所以要修正构造函数

ClassB.prototype.constructor = ClassB;

ClassB.prototype.getC = function() {
    return this.c
} 

var a = new ClassA(2,3);
console.log(a.getA(),a.getB());

var b = new ClassB(3);
console.log(b.getA(),b.getB(),b.getC())

原型继承的缺点也是很明显的,比如过多的继承了没有用的属性等,这里new ClassA()出来的对象可能会包含很多没有用的属性,比如ClassA里面的this.a,this.b这两个(因为无法通过上面的方式继承到,如果要继承到ClassA函数体里面的属性,貌似就得这么写ClassB.prototype  = new ClassA(1,2)  这样的话问题就非常明显了,ClassB里面的a,b属性始终是1,2),甚至更多

2.借用构造函数

//借用构造函数
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

//类B
function ClassB(a,b,c) {
    ClassA.call(this,a,b); //构造函数借用,通过这种方式,把ClassA里的属性给挂到ClassB的this里
    this.c = c;
}
ClassB.prototype = {
    constructor: ClassB,
    getC:function (){
        return this.c;
    }
}
var b = new ClassB(1,2,2);
console.log(b.a,b.b,b.c);//这里ok
console.log(b.getC());
//这下面的代码会报错,原因是没有把ClassA的getA,getB方法给继承下来
console.log(b.getA());
console.log(b.getB());

上面继承方式的缺点不用我说相信大家也明白。

3.混合模式(原型加构造)

//混合模式
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

//类B
function ClassB(a,b,c) {
    ClassA.call(this,a,b); //通过这种方式,把ClassA里的属性给挂到ClassB的this里
    this.c = c;
}
ClassB.prototype = new ClassA();//把方法给绑到ClassB的原型链里,相当于继承

ClassB.prototype.constructor = ClassB;

ClassB.prototype.getC = function () {
    return this.c;
}

var b = new ClassB(1,2,3);
//这样就没问题了
console.log(b.getA(),b.getB(),b.getC());

这貌似跟原型继承没啥两样,而且还是借用用了构造函数,这一类的继承都有缺点,每次构造函数都要多走一个函数或不能继承借用的构造函数原型,而且还有原型继承的缺点

4.优化混合模式(主要解决了原型继承的问题)


//优化混合模式
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

//类B
function ClassB(a,b,c) {
    ClassA.call(this,a,b); //通过这种方式,把ClassA里的属性给挂到ClassB的this里
    this.c = c;
}
//引入中间函数F
function F() {}
//F的原型指向ClassA的原型对象,从而剔除ClassA函数体里定义的属性,原型里面只要继承方法就好了
F.prototype = ClassA.prototype;

ClassB.prototype = new F();//new F()是一个只要ClassA的方法的对象

ClassB.prototype.constructor = ClassB;

ClassB.prototype.getC = function () {
    return this.c;
}

var b = new ClassB(1,2,3);
//一样能用
console.log(b.getA(),b.getB(),b.getC());

而这个的问题也是有的,为了实现继承,然后还要多写一个中间函数,怎么感觉都有点费劲

其实还有一种不用中间函数的实现,通过使用Object.create方法

//另一种优化混合模式
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

//类B
function ClassB(a,b,c) {
    ClassA.call(this,a,b); //通过这种方式,把ClassA里的属性给挂到ClassB的this里
    this.c = c;
}


ClassB.prototype = Object.create(ClassA.prototype,{
 constructor:{
     value:ClassB
 },
 getC:{
     value:function() {
         return this.c
     }
 }   
});
/*
或者这样也行
ClassB.prototype = Object.create(ClassA.prototype);
ClassB.prototype.constructor = ClassB;
ClassB.prototype.getC = function() {
    return this.c
}
*/

var b = new ClassB(1,2,3);
//一样能用
console.log(b.getA(),b.getB(),b.getC());

现在大概js的继承有了一定的认识,现在的方法就是,类的方法使用原型来实现继承,而类里面this上的属性使用构造函数的方式来继承

所以在继承方法上我们其实可以直接将继承类的prototype.__proto__指向被继承类的prototype,这里用到了属性的搜索,即原型链搜索,当一个对象要找某一个属性时,首先看自己有没有,如果有直接返回,如果没有,则在原型链上搜索,沿着原型链一层一层往上搜,比如在第一层原型链上没有找到,就在__proto__指向的第二层原型链上搜,如果没有搜到,则返回undefined

来张图会理解的更清楚

所以现在又有了另一种混合继承模式


//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

function ClassB(a,b,c) {
    ClassA.call(this,a,b);
    this.c = c;
}

ClassB.prototype = {
    __proto__:ClassA.prototype,//这里是重点
    constructor: ClassB,
    getC:function (){
        return this.c
    }
}
var b = new ClassB(1,2,3);
console.log(b.getA(),b.getB(),b.getC());

还可以利用Object.setPrototypeOf来实现

/*
ClassB.prototype = {
    __proto__:ClassA.prototype,//这里是重点
    constructor: ClassB,
    getC:function (){
        return this.c
    }
}
*/
//将上面的代码替换为下面的代码即可
Object.setPrototypeOf(ClassB.prototype,ClassA.prototype);
ClassB.prototype.constructor = ClassB;
ClassB.prototype.getC = function(){
    return this.c
}

当然以上的继承方式都没有将类属性继承下来比如如果ClassA.id = 1的话,那么id这个属性就没有继承下来

所以来看最后一种继承模式

5.完美模式


//完美模式
//类A
function ClassA(a,b){
    this.a = a;
    this.b = b;
} 
ClassA.prototype = {
    constructor:ClassA,
    getA:function() {
        return this.a
    },
    getB:function() {
        return this.b
    }
}

function ClassB(a,b,c) {
    ClassA.call(this,a,b);
    this.c = c;
}

ClassB.prototype = {
    __proto__:ClassA.prototype,//这里是重点
    constructor: ClassB,
    getC:function (){
        return this.c
    }
}
Object.setPrototypeOf(ClassB.prototype,ClassA.prototype);
Object.setPrototypeOf(ClassB,ClassA);//也就加了这句话,将ClassA的类属性加到ClassB中
ClassB.prototype.constructor = ClassB;
ClassB.prototype.getC = function(){
    return this.c
}
var b = new ClassB(1,2,3);
console.log(b.getA(),b.getB(),b.getC());

以上就时javaScript继承的一些方法,现在来想一个问题,在面向对象的语言中不仅有继承和多态(多态在js中可以忽略,js的任意类型可比多态强太多了),还有接口,抽象类(静态方法其实对应js就在函数上挂属性或方法)

那么接口和抽象类咋实现啊(尽管js不需要这个东西)

我个人有个小想法

//抽象类
function AbstractClass(){
    throw new Error("抽象类不能定义实例");
}
AbstractClass.prototype = {
    constructor:AbstractClass,
    AbstractMethod:function () {
        throw new Error("未重写抽象方法")
    },
    method:function() {
       console.log("这是一般方法")
    }
}

function Class(id) {
     this.id = id;
}
Class.prototype = {
    __proto__: AbstractClass.prototype,
    AbstractMethod:function() {
        console.log("实现抽象方法")
    },
    getId:function(){
       return this.id
    }
}

var c = new Class(1);
c.AbstractMethod();
c.method();
//这里就会报错了
var abstractClass = new AbstractClass();

接口也是如此,在C++中接口与抽象类的唯一区别就是,接口里面全是抽象方法,而抽象类里面可以有一般方法。

在es6中已经有了类的语法(其实本质上还是通过原型链来实现)

看看es6中的类和继承是如何来写的 


class Animal{
    constructor(name){
        this.name = name;
    }
    barking(){}
    run(){}
    static ClassName(){
        return "Animal"
    }
}
class Dog extends Animal{
    constructor(name,barking){
       super(name);
       this.barking = barking;
    }
    backing(){
        return this.backing
    }
    run(){
        console.log("crawl")
    }
    static ClassName(){
        return "Dog"
    }
}
class Bird extends Animal{
    constructor(name,backing){
        super(name);
        this.barking = barking;
    }
    backing(){
        return this.backing
    }
    run(){
        console.log("fly")
    }
    static ClassName(){
        return "Bird"
    }
}
let animal = new Animal("animal");
let dog = new Dog("dog","wan wan");
dog.run();
let bird = new Bird("bird","e e");
bird.run();

JavaScript是一门很有趣的语言,非常的灵活,不仅能写网页,还能使用nodejs编写服务端,甚至能写操作系统和手机app,如果想深入学习这门语言,欢迎下载我推荐的书单(包含《es6标准入门》,《js高级程序设计》,《js权威指南》,《高性能js》,《js设计模式》,《js DOM编程艺术》,这都是我花老多积分一本一本下的,希望对深入的学习javaScript由有帮助):https://download.csdn.net/download/daydream13580130043/10727921

猜你喜欢

转载自blog.csdn.net/daydream13580130043/article/details/83830420