ES6中开始引入了
class
这一关键字,使得能够以更加清晰的方法来定义类和使用类。实际上,class
本质上是一个语法糖
一、示例
定义一个类形如:
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return `(${this.x}, ${this.y})`
}
}
它实际上相当于:
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)
我们可以发现有以下的情况:
typeof Point // "function"
Point === Point.prototype.constructor // true
因此,本质上而言,ES6的类完全可以看做是构造函数的另一种写法。而且,在ES6的类中,仍然保留了prototype
,类的所有方法,本质上是定义在prototype
对象上的。可以用Object.assign
为类添加新的方法,如:
Object.assign(Point.prototype, {
add(x, y) {
this.x += x
this.y += y
}
})
但是,ES6的类,和prototype
还是有点不同的,类中定义的方法,是不可枚举的,所以有:
Object.keys(Point.prototype) // []
再者,类中的属性名,可以采用表达式的形式,如:
let methodName = 'add'
class Point {
// ...
[methodName]() {
// ...
}
}
// 那么,可以这么使用:
p.add(3, 4)
二、constructor
constructor方法是类的默认方法,在通过new
命令生成对象实例时,就会调用该方法。一个类必定有一个constructor
方法,如果没有显示指定,则constructor
默认是一个空方法。
注意: constructor方法默认返回的是实例对象,当然也可以指定返回其他对象,但这可能会导致使用instanceof
时出现意外的情况,如:
class Foo {
constructor() {
return Object.create(null)
}
}
new Foo() instanceof Foo // 返回 false
此外,和构造函数不同的是,ES6中的类,如果没有实例化的话,是没有办法调用的,会报错。即:
Foo() // 报错
new Foo() // 正确
所有的实例,共享同一个原型对象(与ES5一样),如:
var p1 = new Point(1, 2)
var p2 = new Point(3, 4)
p1.__proto__ === p2.__proto__ // true
注意:类和模块的内部,默认是严格模式。只要代码写在类或者模块中,就只有严格模式可用
三、class表达式
1)和function
类似,class
也可以用表达式的形式定义,如:
const MyClass = class Me {
getClassName() {
// 类的名字是MyClass,Me只能在内部使用
return Me.name
}
}
2)此外,也可以省略类名,如:
const MyClass = class {
// ...
}
3)和函数类似,类中存在有立即执行类
,如:
let p = new class {
constructor(x, y) {
this.x = x
this.y = y
}
describe() {
console.log(`(${this.x}, ${this.y})`)
}
}(1, 2)
p.describe() // (1, 2)
注意点
1)class
不存在变量提升,如下会报错:
new Foo()
class Foo {}
为什么class
不存在变量提升?原因如下:
考虑有情况如:
{
let Foo = class {}
class Bar extends Foo {
}
}
假如class
存在变量提升,那么class Bar extends Foo
会被提升到代码头部,而由于let
不存在变量提升,所有就会导致Bar类
继承Foo类
时,Foo类
还未定义。
四、私有方法
ES6中的类,默认是不同私有方法功能的,如果我们想要实现私有方法功能,只能使用变通的方法实现,如:
1、命名上加以区分
使用_
开头标识私有方法,但是这种方式,是不保险的,因为类外仍然可以访问得到,即:
class Circle {
area () {
// ...
}
_check () {
// ...
}
}
2、将私有方法移出类
由ES6的模块实现机制我们可以知道,有:
class Cirlce {
// ...
}
export default Circle;
如果我们没有将一个方法、常量、变量export
出,那么在其他模块中是访问不了的。借助这种特性,我们可以这么实现私有方法,如:
function _getRadius() {
return this.radius;
}
class Circle {
constructor(radius) {
this.radius = radius;
}
getRadius() {
return _getRadius.call(this);
}
}
export default Circle;
五、name属性
name
属性可以获取类的名称,如:
Point.name; // 'name'
六、getter和setter
可以使用get
和set
关键字,来设置一个属性的getter和setter,如:
class Person {
get name() {
return 'getter';
}
set name(value) {
console.log('setter');
return value;
}
}
const p = new Person();
p.name; // 'getter'
p.name = 'ABC';
/*
setter
'ABC'
*/
注意: getter和setter,都是定义在属性的Descriptor
对象上的
七、generator
如果要使某个方法称为generator,那么可以在方法之前加上型号*
,如:
class Foo {
* gen() {
yield 1;
yield 2;
yield 3;
}
}
const p = new Foo;
const [a, b, c] = p.gen();
a; // 1
b; // 2
c; // 3
八、静态方法
可以使用static
关键字来定义静态方法:静态方法只能使用类名来调用,不能使用实例调用,如:
class Foo {
static someMethod() {
console.log('called!!');
}
}
const p = new Foo;
p.someMethod(); // Uncaught TypeError: p.someMethod is not a function
Foo.someMethond(); // called
父类的静态方法,是可以被子类继承的,如:
class Bar extends Foo {}
Bar.someMethod(); // called
静态方法,可以通过super
对象调用,如:
class Bar extends Foo {
static someMethod() {
super.someMethod();
console.log('called again!!');
}
}
Bar.someMethod();
/*
called!!
called again!!
*/
九、静态属性
ES6中,class内部只有静态方法,没有静态属性,即:
class Foo {
prop: 1
static prop: 2
}
都是无效的,要添加静态属性,可以有:
class Foo {}
Foo.prop = 1;
Foo.prop; // 1
提案:
1)类的实例属性
类的实例属性可以写在类定义里面,如:
class Foo {
prop = 123;
constructor() {
console.log(this.prop); // 123
}
}
2)类的静态属性
加上static
关键字即可,如:
class Foo {
static prop = 123;
constructor() {
console.log(Foo.prop); // 123
}
}
十、new.target
属性
ES6中,为new
命令引入了new.target
属性,用于确定构造函数是怎么调用的。如:
const p = new Person;
// p是通过new命令建立的,所以在构造函数内部有 new.target === Person
再看以下的情况:
const p = Person.call(person);
// p不是通过new命令建立的,所以 new.target === undefined
此外,还需注意:当子类继承父类时,new.target
返回的是子类