javascript 的七种继承方式(七)类的继承

前言

前面我们已经介绍了javascript中6种继承方式,这6中继承方式都是基于es5的,那么接下来我们要讲的是es6中新增的一种继承方式—— 类的继承

在es6中新引进了类的概念,作为对象的模板。类是对现实生活中一类具有相同特征的事物的抽象。相信学过后端语言的同学对类并不陌生,类的实质是一种引用数据类型,类似于byte,short,int,long,float,double等基本数据类型,但不同的是它是一种复杂的数据类型。因为它的本质是数据类型,而不是具体的数据所以不存在于内存中,不能直接被操作,只有被实例化为对象时才变得可操作。

在JavaScript中通过 class 关键字来定义类,基本上es6中的类可以看做是一个语法糖,它的大部分功能,在ES5中也都可以做到。新的class 写法只是让对象原型的写法更加清晰,更像面向对象编程的语法而已。下面我们来看一段代码:

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
    }

    toString(){
        return `(${this.x},${this.y})`
    }
}

上面的代码中就定义了一个类,类中有一个constructor方法,这就是构造方法,而this关键字则代表类的实例对象,也就是说ES5中的构造函数对应着ES6中的构造方法。

该类中除了构造方法还定义了一个toString方法。这里需要注意的是,在定义类的方法的时候,前面不需要用function关键字修饰,直接把函数定义放进去就可以了。另外方法之间不需要逗号分隔,否则会报错。

在使用的时候也是直接使用new关键字进行实例化,跟ES5中构造函数的用法完全一致。

constructor方法

constructor方法是类的默认方法,通过new关键字来创建对象的实例时,会自动调用此方法,一个类必须要有constructor方法,如果没有显示定义则会自动添加一个默认的空的constructor方法。

与es5一样,实例属性除非显示定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上),看下面代码:

class Point{
    constructor(x, y){
        this.x = x;
        this.y = y;
    }

    toString(){
        return `(${this.x},${this.y})`
    }
}


var p = new Point(2,3);

p.toString();//(2,3)

p.hasOwnProperty('x'); //true
p.hasOwnProperty('y'); //true
p.hasOwnProperty('toString'); //false

p.__proto__.hasOwnProperty('toString') //true

上面代码中,x和y都是实例对象p的自身属性(因为定义在this变量上),所以hasOwnProperty方法返回的是true,而toString是原型对象的属性(因为定义在类Point上),所以hasOwnProperty返回false。这些与es5的行为保持一致。

类的所有实例共享一个原型对象

var p1 = Point(1,2);
var p2 = Point(2,3);

p1.__proto__ === p2.__proto__; ///true

类的继承

class的继承是通过extends关键字实现,然后通过supper方法来调用父类的构造方法,这比es5的通过修改原型链实现继承,要清晰方便的多。

class ColorPoint extends Point{}

上面的代码简单的实现了类的继承,定义了一个ColorPointer类通过关键字extends实现对类Point的继承,这样ColorPoint就继承了Point的所有的属性和方法,但是由于ColorPoint内部没有任何代码,所以现在这两个类完全一样,相当于复制了一个Point类,下面我们来补充一些代码进来:

class ColorPoint extends Point{
    constructor(x,y,color){
        supper(x,y)//调用父类的constructor方法并传入x和y
        this.color = color; //ColorPoint新增属性color
    }

    toString(){
        return this.color + ' '+ super.toString();//调用父类toString()
    }
}

上面代码中,constructor方法和toString方法中都出现了super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

  1. 子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。
  2. 如果子类没有显示添加constructor方法,则这个方法会被默认添加。也就是说不管有没有显示定义,任何一个子类都有constructor方法
  3. 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错,这是因为子类实例的构建,是基于对父类实例的加工,只有super方法才能返回父类实例。

类的proshtotype属性和__proto__属性

大多数浏览器的es5实现中,每一个对象都有__proto__属性,指向对应的构造函数的prototype属性。class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链

  1. 子类的__proto__属性,表示构造函数的继承,总是指向父类
  2. 子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性
class A{}

class B extends A{}

B.__proto__ === A //true
B.prototype.__proto__ === A.prototype //true

上面的代码中,子类B的__proto__属性执行父类A,子类B的prototype属性的__proto__属性指向父类A的prototype属性。这样的结果是因为,类的继承是按照下面的模式实现的:

class A{}

class B{}

//B的实例继承A的实例
Object.setPrototypeOf(B.prototype, A.prototype);

// B继承A的静态属性
Object.setPrototypeOf(B, A);

其中Object.setPrototypeOf方法是按如下模式实现的:

Object.setPrototypeOf = function(obj, proto){
    obj.__proto__ = proto;
    return obj;
}

因此就得到上面代码中的结果:

Object.setPrototypeOf(B.prototype, A.prototype)
//等同于
B.prototype.__proto__ = A.Prototype

Object.setPrototypeOf(B, A)
//等同于
B.__proto__ = A;

这两天继承链可以这样理解:作为一个对象,子类B的原型__proto__属性是父类A;作为一个构造函数,子类B的原型prototype属性是父类的实例。

以上就是关于ES6中类的继承。到此JavaScript中的七种继承方式就介绍完了。

猜你喜欢

转载自blog.csdn.net/lixiaosenlin/article/details/108214982