ES6 - class

754 阅读4分钟

class

ES6 中引入了 class(类) 的概念。 通过 class 关键字,我们可以定义

class 可以看做是一个 语法糖,它的绝大部分功能,都可以用 ES5 做到, 它只是让 对象原型(prototype) 的写法 更加清晰更像面向对象编程 的语法而已。

// ES6 写法
class A {
    // 构造方法
    constructor(obj) {
        this.prop1 = obj.prop1
    }
    // 内部方法
    func1() {...}
}

var a = new A({prop1: '1'})
a.func1()

// ES5 写法
function A(obj) {
    this.prop1 = obj.prop1
}
Object.defineProperty(a.prototype, 'func1', {
        enumerable: false,   // prototype 中的属性不可枚举
        configurable: true,
        writable: true,
        value: function() {
           ...
        }
})
var b = new b({prop1: '1'})
b.func1()
  • constructor

    constructor方法类(class)默认方法,通过 new 命令生成 对象实例 时,自动调用 该方法。

    一个 类(class) 必须有 constructor 方法,如果 没有显式定义,一个 空的constructor 方法会被 默认 添加。

    // ES6
    class A {}
    
    // 等同于
    class A {
        constructor() {}
    }
    
    // ES5
    function A () {}
    
  • 实例属性

    实例属性 除了定义在 constructor()方法 里面的 this 上面,也可以直接定义在 类里面

    // ES6
    class A {
        count = 0
    }
    
    // 等同于
    class A {
        constructor() {
            this.count = 0
        }
    }
    
    // ES5
    function A() {
        this.count = 0
    }
    
  • 内部(原型)方法

    class 内部定义的方法即为 原型方法

    // ES6:
    class A  {
        constructor(name) {
            this.name = name
        }
        
        getName() {
            return this.name
        }
    }
    
    // ES5:
    function A(name) {
        this.name = name
    }
    Object.defineProperty(A.prototype, 'getName', {
        enumerable: false,   // prototype 中的属性不可枚举
        configurable: true,
        writable: true,
        value: function() {
            return this.name
        }
    })
    

    类的内部方法不可枚举

  • get / set

    class 的内部可以使用 getset 关键字,对某个属性设置 存值函数(setter)取值函数(getter)拦截 该属性的 存取行为

    // ES6:
    class HtmlElement {
        constructor(element) {
            this.element = element
        }
        get html() {
            return this.element.innerHtml
        }
        set html(value) {
            this.element.innerHtml = value
        }
    }
    
    // ES5
    function HtmlElement(element) {
        this.element = element
    }
    Object.defineProperty(HtmlElement.prototype, 'html', {
        enumerable: false,   // prototype 中的属性不可枚举
        configurable: true,
        set: function() {
            return this.element.innerHtml
        },
        get: function() {
            this.element.innerHtml = value
        }
    })
    
  • 静态属性/方法

    class 内部,可以通过关键字 static,将某个属性(方法)设置为 静态属性(方法)

    // ES6:
    class A {
        static count = 0
        static getCount() {
            return this.count  // this => A
        }
    }
    
    // ES5:
    function A() {}
    A.count = 0
    Object.defineProperty(A, 'getCount', {
        configurable: true,
        enumerable: false,   // 类的静态方法不可枚举
        writable: true,
        value: function() {
            return this.count
        }
    })
    

    类的静态方法不可枚举

继承

ES6 中, class 可以通过关键字 extends 实现继承,比通过 原型链 实现继承要清晰和方便。

// ES6:
class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
    getX() {
        return this.x
    }
    getY() {
        return this.y
    }
    static hello() { return 'hello'}
}

class ColorPoint extends Point {
    constructor(x, y, color) {
        super(x, y)
        this.color = color
    }
    getColor() {
        return this.color
    }
}

var point = new Point(0, 0)  // {x: 0, y:0}
var colorPoint = new ColorPoint(1, 1, 'red') // {x:1, y:1, color: 'red'}
colorPoint instanceof ColorPoint  // true
colorPoint instanceof Point  // true
ColorPoint.prototype.__proto__ === Point.prototype  // true
ColorPoint.prototype instanceof Point // true
ColorPoint.__proto === Point  // true
ColorPoint.hello()  // hello

ES6子类实例 的构建过程是比较特殊的。构建时,必须执行 super 方法,否则 构建失败。这是因为 子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象

类的继承 也可以通过 ES5原型链 实现:

function Point(x, y) {
    this.x = x
    this.y = y
}
Point.prototype.getX = function() { return this.x }
Point.prototype.getY = function() { return this.y }
Point.hello = function() { console.log('hello')}

function ColorPoint(x, y, color) {
    Point.call(this, [x, y])
    this.color = color
}
ColorPoint.prototype = Object.create(Point.prototype, {
    constructor: {
        configurable: true,
        enumerable: false, // 构造方法不可枚举
        writable: true,
        value: ColorPoint
    }
})
ColorPoint.prototype.getColor = function() {return this.color}
ColorPoint.__proto__ = Point


ES5继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面

ES6的继承机制 完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this

父类的静态方法,也会被子类继承, 原因是子类的内部[[prototype]]属性指向父类

  • super

    super 在不同的应用场景中意义不同:

    • 作为 函数 调用时,代表 父类的构造函数, 返回 子类实例

    • 作为 对象 使用时, 如果在 普通方法 中, 代表 父类原型对象; 如果在 静态方法 中,代表 父类

    class Point {
        constructor(x, y) {
            this.x = x
            this.y = y
        }
        
    }
    class ColorPoint extends Point {
        constructor(x, y, color) {
            super(x, y)         // super 代表父类构造函数
            this.color = color
        }
        
        func1() {
            super()   // 会报错
            console.log(super)  // super 代表 Point.prototype
        }
        
        static func2() {
            super()   // 会报错
            console.log(super)  // super 代表 Point
        }
    }
    

    子类普通方法 中通过 super调用父类的方法 时,方法内部的 this 指向当前的 子类实例

    子类的静态方法 中通过 super调用父类的方法时,方法内部的 this 指向 当前的子类,而不是子类的实例。

  • [[prototype]]

    class类 存在 两条继承链

    • 子类[[prototype]] 属性指向 父类, 继承 父类的静态方法

    • 子类原型(prototype)[[prototype]] 属性指向 父类原型, 继承 父类的内部(原型)方法

    class Parent {
        getName() {
            
        }
        static getCount() {}
    }
    
    class Child extends Parent {}
    var child = new Child()
    Child.getCount()
    child.getName()
    Child.__proto__ === Parent  // true
    Child.prototype.__proto__ === Parent.prototype  // true
    
    
  • 原生继承

    ES6 允许 继承原生构造函数 定义 子类

    ES6先新建父类的实例对象this,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承

    示例:

    class MyArray extends Array {
        constructor(...args) {
            super(...args)
        }
    }
    
    var list = new MyArray([1,2,3,4])
    list.length // 4
    

    原生构造函数 大致包括: Boolean()Number()String()Array()Date()Function()RegExp()Error()Object()...... 通过原生继承ES6自定义数据结构

  • 多继承

    实质: 拷贝父类的实例属性、静态方法、原型方法