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 的内部可以使用 get 和 set 关键字,对某个属性设置 存值函数(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 可 自定义数据结构。
-
多继承
实质: 拷贝父类的实例属性、静态方法、原型方法。