ECMAScript 6 类和类的继承

类和类的继承

一、类的声明

JS中到处都是对象,它是一门基于对象的语言,JS代码的执行都依托于对象,利用对象产生各种具备关联性的上下文环境,JS代码才能正常运行。

但是我们都知道在ES5中,没有class,我们只是利用函数、new关键词、原型等方式实现类的概念和基本使用,但是随着技术的发展和编程语言的互相靠拢,只使用构造函数或者工厂模式去实现类的封装调用很不现实,代码不明确,容易让学习者和开发者产生误会。

所以 ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

ES5声明类

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

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

let p = new Point(3, 4);

ES6声明类

class Point {
    // 构造函数
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    // 原型函数
    toString = function () {
        return '(' + this.x + ', ' + this.y + ')';
    };
}

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

Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

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

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true

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

注:

  • 依旧用new关键词从类里实例化对象;
  • 类必须使用new调用,否则会报错(ES5的构造函数可以被当做一个函数调用);

由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

class Point {

}

// 有时候根据代码逻辑的需求,我们在声明了类之后的某一处可能要为这个类添加若干个原型函数,如果使用 类名.prototype的方式添加,代码冗余会很高,所以可以使用Object.assign的方式
Object.assign(Point.prototype, {
  toString(){},
  toValue(){}
});

二、类的constructor函数

constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

constructor方法默认返回实例对象(即this),也可以指定返回另外一个对象。

class Point {
    constructor() {
       return {
           name: 'tom'
       };     
    }
}
let p = new Point(); // {name: 'tom'}

注:

  • 构造函数的prototype属性,在 ES6 的“类”上面继续存在;事实上,类中声明的所有方法都定义在类的prototype属性上面;
  • 类的prototype对象的constructor属性,直接指向“类”的本身,这与 ES5 的行为是一致的;

三、类的属性和方法

class Point {
    legs = 2; // 实例属性
    _mouth = 1; // 私有属性
    static eyes = 2; // 静态属性   
    constructor(name, sex) {
        // 实例属性
        this.name = name;
        this.sex = sex;
        // 实例方法
        this.saysomething = function() {
            console.log('saysomething');
        }

        this._getMoney(); // 调用私有方法
    }
    // 原型方法
    run() {
        console.log('run');
    }
    _getMoney () {
        console.log(this._mouth);  
    }
    // 静态方法
    static eat() {
        console.log('eat');
    }
}

// 实例化一个对象
let p = new Point();
console.log(p.legs,p.name,p.sex); // 实例属性
console.log(Point.eyes); // 静态属性
p.saysomething(); // 实例方法
p.run(); // 原型方法
Point.eat(); // 静态方法

注:

  • 静态属性和静态方法是类直接调用的属性和方法,类的实例无法使用。
  • 私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
  • 私有属性和私有方法的

练习:封装一个分数类,可以进行约分、加、减、乘、除运算。

四、取值函数(getter)和存值函数(setter)

有时候类的某些属性需要进行特殊的处理之后才能被取值或者赋值。这个时候就要用到getter和setter函数。
如:

class People {
    a = 1000; // 私有属性
    get money() {
        return this.a - 100;
    }
    set money(val) {
        this.a = val * 2;
    }
}

let p = new People();
// 取出money的值
console.log(p.money);
// 设置money的值
p.money = 3000;
console.log(p.money);

五、类的继承

  1. Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point {}

// ColorPoint通过extends继承Point
class ColorPoint extends Point {}

如果子类继承父类时,父类中有某些属性需要实参来确定,那必须要在子类的constructor构造函数中,用super函数向父类传参,否则浏览器会直接报错。

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

// ColorPoint类继承Point
class ColorPoint extends Point {
    constructor(color, x, y) {
        // 通过super函数向父类传参
        super(x, y);
        // 绑定子类自己的属性
        this.color = color;
    }
}

// 从ColorPoint类中实例化一个对象
let cp = new ColorPoint('#000', 100, 45);
console.log(cp);
  1. Object.getPrototypeOf方法可以用来从子类上获取父类,可以用这个方法判断一个类是否继承了另一个类。
class Point {}
class ColorPoint extends Point {}
Object.getProtorypeOf(Point);
Object.getPrototypeOf(ColorPoint);
  1. super函数除了可以在子类中代表父类的构造函数之外,还可以作为对象使用,当它作为对象时,在类的普通函数中指父类的原型对象,在静态方法中,指向父类。
class Point {
    // 实例属性
    x = 0;
    y = 0;
    // 原型方法
    move() {
        this.x += 1;
        this.y += 1;
    }
    // 静态方法
    static print() {
        console.log('父类的静态方法');
    }
}

// ColorPoint继承Point
class ColorPoint extends Point {
    color = '#000';
    change() {
        // 在原型方法中通过super调用父类的原型方法
        super.move();
        this.color = '#f00';
    }
    static printout() {
        // 在静态方法中通过super调用父类的静态方法
        super.print();
        console.log('子类的静态方法');
    }
}

let cp = new ColorPoint(); // 创建实例
cp.change(); // 调用实例的change函数
console.log(cp);

ColorPoint.printout(); // 调用类的静态方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章