天天看点

【ES6】08-面向对象Class类和继承

对象

在javascript中,对象是一组无序的相关属性和方法的集合,所有的事物都是对象,例如字符串,数值,数组,函数等。

对象是由属性和方法组成的:

  • 属性:事物的特征,在对象中用属性来表示(常用名词)
  • 方法:事物的行为,在对象中用方法来表示(常用动词)

类 class

在ES6中新增了类的概念,可以使用​

​class​

​关键字来声明一个类,之后使用这个类来实例化对象

类抽象了对象的公共部分,它泛指某一大类

对象特指某一个,通过类实例化一个具体的对象

class的基本语法

1.简介

JavaScript语言中,生成实例对象的传统方法是通过构造函数。

function Point(x,){
    this.x = x
    this.y = y
}
Point.prototype.toString = function() {
    return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);      

ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过​

​class​

​关键字,可以定义类。

基本上,ES6的​

​class​

​​可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到,新的​

​class​

​写法只是让对象原型的写法更加清晰,更像面向对象便形成的语法而已。

class Point {
    constructor(x,){
        this.x = x
        this.y = y
    }
    toString() {
       return '(' + this.x + ', ' + this.y + ')';
    }
}      

ES6的类,完全可以看做构造函数的另一种写法

class Point {
    //...
}
typeof Point  //'function'
Point === Point.prototype.constructor      

上面代码表明,类的数据类型就是函数,类本身就是指向构造函数

使用的时候也是直接使用​​

​new​

​命令,跟构造函数的用法完全一致。

class Bar {
    doStuff() {
        console.log('stuff')
    }
}
var b = new Bar()
b.doStuff()   //stuff      

构造函数的​

​prototype​

​​属性,在ES6的类上面继续存在。事实上,类的所有方法都定义在类的​

​prototype​

​属性上面。在类的实例上面调用方法,其实就是调用原型上的方法。

clas B {}
let b = new B
b.constructor === B.prototype.constructor      

上面代码中,​

​b​

​​是​

​B​

​​类的实例,它的​

​constructor​

​​方法就是​

​B​

​​类原型的​

​constructor​

​方法

由于类的方法都定义在​

​prototype​

​​对象上面,所以类的新方法可以添加在​

​prortotype​

​​对象上面。​

​Object.assign​

​方法可以很方便地一次性添加多个方法。

class Point{
    constructor(){
        //...
    }
}
Object.assign(Point.prototype,{
    toString(){},
    toValue(){}
})      

​prototype​

​​对象的​

​constructor​

​属性,直接指向类的本身,这与ES5的行为是一致的。

Point.prototype.constructor === Point    //true      

另外,类的内部所有定义的方法,渎职不可枚举的(non-enumerable)

class Point{
    constructor(x,){
        //...
    }
    toString() {
        //...
    }
}
Object.keys(Point.prototype)
//[]
Object.getOwnPropertyNames(Point.prototype)      

2. constructor方法

​constructor​

​​方法是类的默认方法,通过​

​new​

​​命令生成对象实例,自动调用该方法。

一个类必须有​​

​constructor​

​​方法,如果没有显示定义,一个空的​

​constructor​

​方法会被默认添加。

class Point {
    
}

//等同于
class Point {
    constructor(){
        
    }
}      

​constructor​

​​方法默认返回实例对象(即​

​this​

​),完全可以指定返回另外一个对象

类必须使用​

​new​

​​ 调用,否则会报错。这是它跟普通函数的一个主要区别,后者不用​

​new​

​也可以执行。

3. 类的实例

生成类的实例的写法,与ES5完全一致,也是使用​

​new​

​​命令,但必须使用​

​new​

​调用,否则报错。

实例的属性除非显示定义在其本身(即定义在​

​this​

​​对象上),否则都是定义在原型上(即定义在​

​class​

​上)。

//定义类
class Point {
    //构造器
    constructor(name,){
        //实例属性,这里的this指向Point的实例对象
        this.name = name
        this.age = age
    }
    //实例方法
    sayName() {
        console.log(this.name)
    }
    //静态方法
    static say() {
        console.log('静态方法')
    }
}
//静态属性
Point.staticAttr = '静态属性'

//创建实例
let point = new Point('zhangsan', 18)
//调用实例方法
point.sayName()    //'zhangsan'

//访问静态属性,静态方法
console.log(Point.staticAttr)
Point.say()      

4. 静态方法

类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上​

​static​

​关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

class Point{
    //静态方法,只能通过类来调用,实例是不会继承静态方法的
    static say(){
        console.log('静态方法')
    }
}      

5. 实例方法

类的实例方法可以直接定义在类中,不用static修饰的方法都会被类的实例继承

class Point{
    //实例方法
    sayName(){
        console.log('实例方法')
    }
}      

6. 静态属性

直接在类上定义的属性是类的静态属性,注意:并不是写在class里面,而是下面这种写法:

class Point{
}
Point.staticName = 'lisi'      

7. 实例属性

类的实例属性可以定义在构造函数中(constructor)

class Point{
    constructor(id,name){
        this.id = id
        this.name = name
    }
}      

class的继承

1. 简介

class可以通过​

​extends​

​关键字实现继承,这比ES5的通过修改原型链实现继承,要清晰和方便很多。

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

class ColorPoint extends Point {
    constructor(x, y,){
        super(x, y)   //调用父类的constructor(x,y)
        this.color = color
    }
    toString() {
        return this.color + ' ' + super.toString()   //调用父类的toString()
    }
}      

上面代码定义了一个​

​ColorPoint​

​​类,该类通过​

​extends​

​​关键字,继承了​

​Point​

​类的所有属性和方法(包括静态的和实例的)

子类必须在​

​constructor​

​​方法中调用​

​super​

​​方法,否则新建实例会报错。这是因为子类自己的​

​this​

​​对象,必须先通过父类的构造函数完成塑造,得到与弗雷同样的实例属性和方法,然后再对其经行加工,加上子类自己的实例属性和方法。如果不调用​

​super​

​​方法,子类就得不到​

​this​

​对象。

2. Object.getPrototypeOf()

​Object.getPrototypeOf​

​方法可以用来从子类上获取父类

Object.getPrototypeOf(ColorPoint) ===      

因此,可以用这个方法判断一个类是否继承了另一个类。

3. super关键字

​super​

​这个关键字,既可以当做函数调用,也可以当做对象使用。在这两种情况下,它的用法完全不同。

  • 第一种情况,​

    ​super​

    ​​作为函数调用时,代表父类的构造函数。ES6要求,子类的构造函数必须执行一次​

    ​super​

    ​函数。
class A {}
  class B extends A {
      constructor() {
          super()
      }
  }      

上面代码中,子类​

​B​

​​的构造函数之中的​

​super()​

​,代表调用父类的构造函数。这是必须的,否则报错。

注意:​

​super​

​​虽然代表了父类​

​A​

​​的构造函数,但是返回的是子类​

​B​

​​的实例,即​

​super​

​​内部的​

​this​

​​指的是​

​B​

​​的实例,因此​

​super​

​​在这里相当于​

​A.prototype.constructor.call(this)​

作为函数时,​

​super()​

​只能用在子类的构造函数之中,用在其他地方就会报错。

  • 第二种情况,​

    ​super​

    ​作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
    p(){
        return 2
    }
}

class B extends A {
    constructor() {
        super()
        console.log(super.p())   //2  这里其实是调用了父类A的p方法
    }
}      

这里需要注意,由于​

​super​

​​指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过​

​super​

​调用的。

class A {
    constructor(){
        this.p = 2
    }
}

class B extends A {
    get m() {
        return super.p
    }
}
let b = new B()
b.m   //undefined      

4. 类的prototype属性和__ptoto__属性

大多数浏览器的ES5实现之中,每一个对象都有​

​__proto__​

​​属性,指向对应的构造函数的​

​prototype​

​​属性。class作为构造函数的语法糖,同时有​

​prototype​

​​属性和​

​__proto__​

​属性,因此同时存在两条继承链

class A {}
class B extends A {}
B.__proto__ === A   //true
B.prototype.__proto__ === A.prototype   //true      
class A {}

class B {}

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

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

const b = new B()