引入了Class(類)這個概念,作為對象的模板。通過class關鍵字,可以定義類。
//定義類
class Point {
constructor(x, y) {
this.x = x; this.y = y;
}
//定義方法
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
上面代碼表明,類的數據類型就是函數,類本身就指向構造函數。類的所有方法都定義在類的prototype屬性上面。
Object.assign(Point.prototype, {
sayAge(){},
toValue(){}
});
類的屬性名,可以采用表達式。
let methodName = "getArea";
class Square{
constructor(length) { // ... }
[methodName]() {
// ... }
}
constructor方法
constructor方法是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,如果沒有顯式定義,一個空的constructor方法會被默認添加。
constructor方法默認返回實例對象(即this),完全可以指定返回另外一個對象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo // false
Class表達式
與函數一樣,類也可以使用表達式的形式定義。
const MyClass = class Me {
getClassName() {
//me僅用于內部調用使用,外部無效
return Me.name;
}
};
//如果內部無須引用 Me,可簡寫
const MyClass = class {...}
采用Class表達式,可以寫出立即執行的Class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('張三');
person.sayName(); // "張三"
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());
Class的繼承
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方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然后對其進行加工。如果不調用super方法,子類就得不到this對象。
如果子類沒有定義constructor方法,這個方法會被默認添加
Class的取值函數(getter)和存值函數(setter)
class CustomHTMLElement {
constructor(element) { this.element = element; }
get html() { return this.element.innerHTML; }
set html(value) { this.element.innerHTML = value; }
}
var descriptor = Object.getOwnPropertyDescriptor( CustomHTMLElement.prototype, "html");
"get" in descriptor // true
"set" in descriptor // true
Class的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'; }
}
var foo=new Foo()
foo.classMethod();// TypeError: foo.classMethod is not a function
class Bar extends Foo {}
Bar.classMethod();
// 'hello'
Class的靜態屬性和實例屬性
靜態屬性指的是Class本身的屬性,即Class.propname,而不是定義在實例對象(this)上的屬性。
ES7有一個靜態屬性的[提案]properties),目前Babel轉碼器支持。
類的實例屬性可以用等式,寫入類的定義之中。
class MyClass {
myProp = 42;
constructor() {
console.log(this.myProp);
// 42
}
}
class ReactCounter extends React.Component { state = { count: 0 };}
//對于已經在對于那些在constructor里面已經定義的實例屬性,新寫法允許直接列出。
class ReactCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
state;
}
類的靜態屬性只要在上面的實例屬性寫法前面,加上static關鍵字就可以了
class MyClass {
static myStaticProp = 42;
constructor() {
console.log(MyClass.myProp);
// 42
}
}
new.target屬性
new是從構造函數生成實例的命令。ES6為new命令引入了一個new.target屬性,(在構造函數中)返回new命令作用于的那個構造函數。如果構造函數不是通過new命令調用的,new.target會返回undefined因此這個屬性可以用來確定構造函數是怎么調用的。
用處1:可以寫出不能獨立使用、必須繼承后才能使用的類
用處2:確保構造函數只能通過new,不是通過call方式命令調用
Mixin模式的實現
Mixin模式指的是,將多個類的接口“混入”(mix in)另一個類。它在ES6的實現如下。
function mix(...mixins) {
class Mix {}
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) { // ...}