浅谈ES6 中的Class
一、Class 的基本语法
静态方法与静态属性
\1. 不会被类实例所拥有的属性与方法 只是类自身拥有
\2. 只能通过类调用
静态方法与普通方法重名,不会冲突
static 关键字(静态方法)
静态属性
类名.属性名 = 属性值;
-
静态属性的声明,应该在类外部,使用“类名.属性名”的方式声明。
-
静态方法的调用,应该直接在类上调用,而不是在类的实例上调用。
1.静态方法
class Foo {
static baz(){
console.log('hello');
}
baz(){
console.log('world');
}
}
class Bar extends Foo {
static bar(){
this.baz();
}
}
Bar.bar(); //"hello"
上面代码中,Foo
类的第一个baz
方法前有static
关键字,表明该方法是一个静态方法,可以直接在Foo
类上调用(Foo.baz()
),而不是在Foo
类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。父类Foo
有一个静态方法,子类Bar
可以调用这个方法。静态方法bar
调用了this.baz
,这里的this
指的是Foo
类,而不是Foo
的实例,等同于调用Foo.baz
。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
- 如果静态方法包含
this
关键字,这个this
指的是类,而不是实例。
2.静态属性
//职业类
class Profession{
}
class Character {
constructor(pfs) {
this.pfs = pfs;
}
}
// 静态属性做配置
Character.config = {
profession: {
'法师': 1,
'刺客': 2
}
}
// 创建类的实例
new Character(Character.config.profession['法师']);
上面的写法为Character
类定义了一个静态属性config
。
3.私有方法
私有方法和私有属性,是只能在类的内部访问的方法和属性,外部不能访问。这是常见需求,有利于代码的封装,但 ES6 不提供,只能通过变通方法模拟实现。
class Widget {
foo (baz) {
bar.call(this, baz);
}
// ...
}
function bar(baz) {
return this.snaf = baz;
}
将私有方法移出模块,因为模块内部的所有方法都是对外可见的。foo
是公开方法,内部调用了bar.call(this, baz)
。这使得bar
实际上成为了当前模块的私有方法。
4.new.target
- 判断构造函数是否通过new调用
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
new.target的作用
------限制类的调用方法,判断new.target是不是未定义
-----可以写出不能独立使用、必须继承后才能使用的类。
上面代码中,Shape
类不能被实例化,只能用于继承。
注意,在函数外部,使用new.target
会报错。
二、Class 的继承
- Class 可以通过
extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
1.Object.getPrototypeOf()
Object.getPrototypeOf
方法可以用来从子类上获取父类。
Object.getPrototypeOf(ColorPoint) === Point
// true
因此,可以使用这个方法判断,一个类是否继承了另一个类。
2.super关键字
- ES6中,子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,然后再加上子类自己的实例属性和方法
在JavaScript中,this关键字总是指向函数所在的当前对象,ES6中增加了一个与this对应的super关键字,指向的是当前对象(this指向的对象)的原型对象。
const demo1 = {
prop1 : '属性1'
};
const demo2 = {
find(){
//返回demo2 的原型对象的foo属性
return super.foo
}
};
Object.setPrototypeOf(demo2,demo1);
obj.find() //"hello"
需要注意的是,当super关键字表示原型对象时,只能用在对象的方法中。而在这里JavaScript引擎有一个bug。只有用对象方法的简写写法时,引擎才能确认这个函数是对象的方法,其他写法应用super的时候都会报错。
const proto = {
title :'原型对象'
sayTitle(){
console.log(this.title);
}
};
const obj = {
title : '对象',
sayPrototypeTitle(){
super.sayTitle();
}
}
Object.setPrototypeOf(obj,proto);obj.sayPrototypeTitle();// 结果:// 原型对象
下面是几种会报错的写法:
//报错,因为super不能应用在方法之外。
const obj = {
protoTitle : super.foo
}
//因为此时JS引擎不能识别function是对象的方法,所以调用super会报错。
const obj = {
title : '对象',
sayPrototypeTitle : function(){
super.sayTitle();
}
}
//因为此时JS引擎不能识别“箭头函数”是对象的方法,所以调用super会报错。
const obj = {
title : '对象',
sayPrototypeTitle => super.sayTitle();
}
2.Mixin模式的实现
Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。
const a = {
a: 'a'
};
const b = {
b: 'b'
};
const c = {...a, ...b}; // {a: 'a', b: 'b'}
上面代码中,c
对象是a
对象和b
对象的合成,具有两者的接口。
下面是一个更完备的实现,将多个类的接口“混入”(mix in)另一个类。
function mix(...mixins) {
class Mix {
constructor() {
for (let mixin of mixins) {
copyProperties(this, new mixin()); // 拷贝实例属性
}
}
}
for (let mixin of mixins) {
copyProperties(Mix, mixin); // 拷贝静态属性
copyProperties(Mix.prototype, mixin.prototype); // 拷贝原型属性
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if ( key !== 'constructor'
&& key !== 'prototype'
&& key !== 'name'
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
上面代码的mix
函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。
class DistributedEdit extends mix(Loggable, Serializable) {
// ...
}