简介
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。下面是一个例子。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
上面这种写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习这门语言的程序员感到困惑。
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class
关键字,可以定义类。
ES6 的class
可以看作只是一个语法糖
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class
改写,就是下面这样。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
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
命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
var b = new Bar();
b.doStuff() // "stuff"
类的所有方法都定义在类的prototype
属性上面
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的**prototype**
属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
上面代码中,b
是B
类的实例,实例b
的constructor
方法就是B
类原型的constructor
方法。
Object.assign
方法一次向类添加多个方法
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。**Object.assign**
方法可以很方便地一次向类添加多个方法。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
constructor 属性,直接指向“类”的本身
**prototype**
对象的**constructor**
属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
Point.prototype.constructor === Point // true
内部所有定义的方法,都是不可枚举的
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype)
// []
Object.getOwnPropertyNames(Point.prototype)
// ["constructor","toString"]
上面代码中,toString
方法是Point
类内部定义的方法,它是不可枚举的。这一点与 ES5 的行为不一致。
类的属性名,可以采用表达式。
let methodName = 'getArea';
class Square {
constructor(length) {
// ...
}
[methodName]() {
// ...
}
}
上面代码中,Square
类的方法名getArea
,是从表达式得到的。
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
考虑到未来所有的代码,其实都是运行在模块之中,所以 ES6 实际上把整个语言升级到了严格模式。
定义 class 类
类声明和类表达式
在ES6标准中,JavaSCript 也支持了 class
这种创建对象的语法。
与函数类型相似,定义类也有两种主要方式:类声明和类表达式。这两种方式都使用 class 关键字加大括号:
// 类声明
class Person {}
// 类表达式
const Animal = class {};
class Animal {
constructor(name) { // 构造方法
this.name = name
}
sleep() {
return 'zzZZ~'
}
}
let cat = new Animal('cat')
let dog = new Animal('dog')
console.log(cat.name) // cat
console.log(dog.name) // dog
console.log(cat.sleep === dog.sleep) // true
constructor 类构造函数
constructor 关键字用于在类定义块内部创建类的构造函数。 方法名 constructor 会告诉解释器在使用 new 操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。
一个类必定有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
上面代码中,定义了一个空的类Point
,JavaScript 引擎会自动为它添加一个空的constructor
方法。
constructor
方法默认返回实例对象(即**this**
),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
上面代码中,constructor
函数返回一个全新的对象,结果导致实例对象不是Foo
类的实例。
类必须使用new
调用
类必须使用**new**
调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用**new**
也可以执行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
extends 继承
class 的继承方法。
class Animal {
constructor(name) { // 构造方法
this.name = name
}
sleep() {
return 'zzZZ~'
}
}
class Flyable extends Animal {
constructor(name) {
super(name) // 执行父类构造方法
}
fly() {
return 'flying...'
}
}
var brid = new Flyable('brid')
console.log(brid.name) // bire
console.log(brid.sleep()) // zzZZ~
console.log(brid.fly()) // flying...
this类型
链式调用
类的成员方法可以直接返回一个 this,这样就可以很方便地实现链式调用。
class StudyStep {
step1() {
console.log('listen')
return this
}
step2() {
console.log('write')
return this
}
}
const s = new StudyStep()
s.step1().step2() // 链式调用