ES6的class
可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
//定义类 class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return ‘(‘ + this.x + ‘, ‘ + this.y + ‘)‘; } }
上面代码定义了一个“类”,可以看到里面有一个constructor
方法,这就是构造方法,而this
关键字则代表实例对象。
Point
类除了构造方法,还定义了一个toString
方法。注意,定义“类”的方法的时候,前面不需要加上function
这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
类的数据类型就是函数,类本身就指向构造函数。
使用的时候,直接对类使用new
命令
class Bar { doStuff() { console.log(‘stuff‘); } } var b = new Bar(); b.doStuff() // "stuff"
类的所有方法都定义在类的prototype
属性上面
class Point { constructor(){ // ... } toString(){ // ... } toValue(){ // ... } } // 等同于 Point.prototype = { toString(){}, toValue(){} };
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
上面代码中,b
是B类的实例,它的constructor
方法就是B类原型的constructor
方法。
另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Point { constructor(x, y) { // ... } toString() { // ... } } Object.keys(Point.prototype) // [] Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
constructor方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
constructor() {}
类的构造函数,不使用new
是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者不用new
也可以执行。
class Foo { constructor() { return Object.create(null); } } Foo() // TypeError: Class constructor Foo cannot be invoked without ‘new‘
类的实例对象
生成类的实例对象的写法,与ES5完全一样,也是使用new
命令。如果忘记加上new
,像函数那样调用Class
,将会报错。
不存在变量提升
Class不存在变量提升(hoist),这一点与ES5完全不同。
new Foo(); // ReferenceError class Foo {}
Class表达式
与函数一样,类也可以使用表达式的形式定义
const MyClass = class Me { getClassName() { return Me.name; } };
上面代码使用表达式定义了一个类。需要注意的是,这个类的名字是MyClass
而不是Me
,Me
只在Class的内部代码可用,指代当前类。
let inst = new MyClass(); inst.getClassName() // Me Me.name // ReferenceError: Me is not defined
上面代码表示,Me
只在Class内部有定义。
如果类的内部没用到的话,可以省略Me
,也就是可以写成下面的形式。
const MyClass = class { /* ... */ };
采用Class表达式,可以写出立即执行的Class。
let person = new class { constructor(name) { this.name = name; } sayName() { console.log(this.name); } }(‘张三‘); person.sayName(); // "张三"
上面代码中,person
是一个立即执行的类的实例。
私有方法
一种方法是将私有方法移出模块,因为模块内部的所有方法都是对外可见的
class Widget { foo (baz) { bar.call(this, baz); } // ... } function bar(baz) { return this.snaf = baz; }
上面代码中,foo
是公有方法,内部调用了bar.call(this, baz)
。这使得bar
实际上成为了当前模块的私有方法。
还有一种方法是利用Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值。
const bar = Symbol(‘bar‘); const snaf = Symbol(‘snaf‘); export default class myClass{ // 公有方法 foo(baz) { this[bar](baz); } // 私有方法 [bar](baz) { return this[snaf] = baz; } // ... };
上面代码中,bar
和snaf
都是Symbol
值,导致第三方无法获取到它们,因此达到了私有方法和私有属性的效果。
this的指向
类的方法内部如果含有this
,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
class Logger { printName(name = ‘there‘) { this.print(`Hello ${name}`); } print(text) { console.log(text); } } const logger = new Logger(); const { printName } = logger; printName(); // TypeError: Cannot read property ‘print‘ of undefined
上面代码中,printName
方法中的this
,默认指向Logger
类的实例。但是,如果将这个方法提取出来单独使用,this
会指向该方法运行时所在的环境,因为找不到print
方法而导致报错。
一个比较简单的解决方法是,在构造方法中绑定this
,这样就不会找不到print
方法了。
class Logger { constructor() { this.printName = this.printName.bind(this); } // ... }
另一种解决方法是使用箭头函数。
class Logger { constructor() { this.printName = (name = ‘there‘) => { this.print(`Hello ${name}`); }; } // ... }
还有一种解决方法是使用Proxy
,获取方法的时候,自动绑定this
。
function selfish (target) { const cache = new WeakMap(); const handler = { get (target, key) { const value = Reflect.get(target, key); if (typeof value !== ‘function‘) { return value; } if (!cache.has(value)) { cache.set(value, value.bind(target)); } return cache.get(value); } }; const proxy = new Proxy(target, handler); return proxy; } const logger = selfish(new Logger());
严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。只要你的代码写在类或模块之中,就只有严格模式可用。
name属性
class Point {} Point.name // "Point"
name
属性总是返回紧跟在class
关键字后面的类名。
Class的继承
Class之间可以通过extends
关键字实现继承
class ColorPoint extends Point {}
上面代码定义了一个ColorPoint
类,该类通过extends
关键字,继承了Point
类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Point
类。下面,我们在ColorPoint
内部加上代码。
class ColorPoint extends Point { constructor(x, y, color) { super(x, y); // 调用父类的constructor(x, y) this.color = color; } toString() { return this.color + ‘ ‘ + super.toString(); // 调用父类的toString() } }
上面代码中,constructor
方法和toString
方法之中,都出现了super
关键字,它在这里表示父类的构造函数,用来新建父类的this
对象。
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类没有自己的this
对象,而是继承父类的this
对象,然后对其进行加工。如果不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp = new ColorPoint(); // ReferenceError
上面代码中,ColorPoint
继承了父类Point
,但是它的构造函数没有调用super
方法,导致新建实例时报错。
另一个需要注意的地方是,在子类的构造函数中,只有调用super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super
方法才能返回父类实例。
class Point { constructor(x, y) { this.x = x; this.y = y; } } class ColorPoint extends Point { constructor(x, y, color) { this.color = color; // ReferenceError super(x, y); this.color = color; // 正确 } }
上面代码中,子类的constructor
方法没有调用super
之前,就使用this
关键字,结果报错,而放在super
方法之后就是正确的。
Class的取值函数(getter)和存值函数(setter)
class MyClass { constructor() { // ... } get prop() { return ‘getter‘; } set prop(value) { console.log(‘setter: ‘+value); } } let inst = new MyClass(); inst.prop = 123; // setter: 123 inst.prop // ‘getter‘
上面代码中,prop
属性有对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。
Class 的 Generator 方法
如果某个方法之前加上星号(*
),就表示该方法是一个 Generator 函数。
class Foo { constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo(‘hello‘, ‘world‘)) { console.log(x); } // hello // world
上面代码中,Foo
类的Symbol.iterator
方法前有一个星号,表示该方法是一个 Generator 函数。Symbol.iterator
方法返回一个Foo
类的默认遍历器,for...of
循环会自动调用这个遍历器。
Class 的静态方法
如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo { static classMethod() { return ‘hello‘; } } Foo.classMethod() // ‘hello‘ var foo = new Foo(); foo.classMethod() // TypeError: foo.classMethod is not a function
上面代码中,Foo
类的classMethod
方法前有static
关键字,表明该方法是一个静态方法,可以直接在Foo
类上调用(Foo.classMethod()
),而不是在Foo
类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。
父类的静态方法,可以被子类继承。
class Foo { static classMethod() { return ‘hello‘; } } class Bar extends Foo { } Bar.classMethod(); // ‘hello‘
上面代码中,父类Foo
有一个静态方法,子类Bar
可以调用这个方法。
静态方法也是可以从super
对象上调用的。
class Foo { static classMethod() { return ‘hello‘; } } class Bar extends Foo { static classMethod() { return super.classMethod() + ‘, too‘; } } Bar.classMethod();
Class的静态属性和实例属性
静态属性指的是Class本身的属性,即Class.propname
,而不是定义在实例对象(this
)上的属性。
class Foo { } Foo.prop = 1; Foo.prop // 1
上面的写法为Foo
类定义了一个静态属性prop
。
目前,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。
// 以下两种写法都无效 class Foo { // 写法一 prop: 2 // 写法二 static prop: 2 } Foo.prop // undefined
ES7有一个静态属性的提案,目前Babel转码器支持。
..........
Mixin模式的实现
...............
原文:http://jsrun.net/tutorial/SZKKp