ECMAScript 6教程 (三)

langyahappy發表於2015-07-27

Class

目錄:

  1. Class基本語法
  2. Class的繼承
  3. class的取值函式(getter)和存值函式(setter)
  4. Class的Generator方法
  5. Class的靜態方法
  6. new.target屬性
  7. 修飾器

Module

  1. export命令
  2. import命令
  3. 模組的整體輸入
  4. module命令
  5. export default命令
  6. 模組的繼承

Class基本語法

ES6提供了更接近傳統語言的寫法,引入了Class(類)這個概念,作為物件的模板。通過class關鍵字,可以定義類。

//定義類
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
    toString() {
         return '('+this.x+', '+this.y+')';
    }
}

上面程式碼定義了一個“類”,可以看到裡面有一個constructor方法,這就是構造方法,而this關鍵字則代表例項物件。

constructor方法

constructor方法是類的預設方法,通過new命令生成物件例項時,自動呼叫該方法。

例項物件

var point = new Point(2, 3);

name屬性

class Point {}
Point.name // "Point"

class表示式

與函式一樣,Class也可以使用表示式的形式定義。

const MyClass = class Me {
    getClassName() {
        return Me.name;
    }
};

Class的繼承

Class之間可以通過extends關鍵字,實現繼承。

子類會繼承父類的屬性和方法。

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方法之後就是正確的。

注意:ColorPoint繼承了父類Point,但是它的建構函式必須呼叫super方法。

下面是生成子類例項的程式碼。

let cp = new ColorPoint(25, 8, 'green');

cp instanceof ColorPoint // true
cp instanceof Point // true

class的取值函式(getter)和存值函式(setter)

在Class內部可以使用get和set關鍵字,對某個屬性設定存值函式和取值函式。

class MyClass {
  get prop() {
    return 'getter';
  }
  set prop(value) {
    document.write('setter: '+value);
  }
}

let inst = new MyClass();

inst.prop = 123;
// setter: 123

inst.prop
// 'getter'

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')) {
    document.write(x);
}
// hello
// world        

上面程式碼中,Foo類的Symbol.iterator方法前有一個星號,表示該方法是一個Generator函式。Symbol.iterator方法返回一個Foo類的預設遍歷器,for...of迴圈會自動呼叫這個遍歷器。

上面程式碼中,prop屬性有對應的存值函式和取值函式,因此賦值和讀取行為都被自定義了。

Class的靜態方法

類相當於例項的原型,所有在類中定義的方法,都會被例項繼承。如果在一個方法前,加上static關鍵字,就表示該方法不會被例項繼承,而是直接通過類來呼叫,這就稱為“靜態方法”。

class Foo {
    static classMethod() {
        return 'hello';
    }
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: undefined 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();

new.target屬性

new是從建構函式生成例項的命令。ES6為new命令引入了一個new.target屬性,(在建構函式中)返回new命令作用於的那個建構函式。如果建構函式不是通過new命令呼叫的,new.target會返回undefined,因此這個屬性可以用來確定建構函式是怎麼呼叫的。

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必須使用new生成例項');
  }
}

// 另一種寫法
function Person(name) {
  if (new.target === Person) {
    this.name = name;
  } else {
    throw new Error('必須使用new生成例項');
  }
}

var person = new Person('張三'); // 正確
var notAPerson = Person.call(person, '張三'); // 報錯

上面程式碼確保建構函式只能通過new命令呼叫。

  • Class內部呼叫new.target,返回當前Class。
  • 子類繼承父類時,new.target會返回子類。

修飾器

修飾器(Decorator)是一個表示式,用來修改類的行為。

修飾器函式可以接受三個引數,依次是目標函式、屬性名和該屬性的描述物件。後兩個引數可省略。上面程式碼中,testable函式的引數target,就是所要修飾的物件。如果希望修飾器的行為,能夠根據目標物件的不同而不同,就要在外面再封裝一層函式。

function testable(isTestable) {
return function(target) {
  target.isTestable = isTestable;
}
}

@testable(true) class MyTestableClass () {}
document.write(MyTestableClass.isTestable) // true

@testable(false) class MyClass () {}
document.write(MyClass.isTestable) // false

如果想要為類的例項新增方法,可以在修飾器函式中,為目標類的prototype屬性新增方法。

function testable(target) {
    target.prototype.isTestable = true;
}

@testable
class MyTestableClass () {}

let obj = new MyClass();

document.write(obj.isTestable) // true

export命令

模組功能主要由兩個命令構成:export和import。

export命令用於使用者自定義模組,規定對外介面;

  • import命令用於輸入其他模組提供的功能,同時創造名稱空間(namespace),防止函式名衝突。
  • ES6允許將獨立的JS檔案作為模組,允許一個JavaScript指令碼檔案呼叫另一個指令碼檔案。

現有profile.js檔案,儲存了使用者資訊。ES6將其視為一個模組,裡面用export命令對外部輸出了三個變數。

// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export {firstName, lastName, year};

import命令

使用export命令定義了模組的對外介面以後,其他JS檔案就可以通過import命令載入這個模組(檔案)。

// main.js
import {firstName, lastName, year} from './profile';

function sfirsetHeader(element) {
  element.textContent = firstName + ' ' + lastName;
}

上面程式碼屬於另一個檔案main.js,import命令就用於載入profile.js檔案,並從中輸入變數。import命令接受一個物件(用大括號表示),裡面指定要從其他模組匯入的變數名。大括號裡面的變數名,必須與被匯入模組(profile.js)對外介面的名稱相同。

如果想為輸入的變數重新取一個名字,import語句中要使用as關鍵字,將輸入的變數重新命名。

import { lastName as surname } from './profile';

ES6支援多重載入,即所載入的模組中又載入其他模組。

模組的整體輸入

export命令除了輸出變數,還可以輸出方法或類(class)。下面是一個circle.js檔案,它輸出兩個方法area和circumference。

// circle.js
export function area(radius) {
  return Math.PI * radius * radius;
}
export function circumference(radius) {
  return 2 * Math.PI * radius;
}

然後,main.js輸入circlek.js模組。

// main.js
import { area, circumference } from 'circle';
document.write("圓面積:" + area(4));
document.write("圓周長:" + circumference(14));

上面寫法是逐一指定要輸入的方法。另一種寫法是整體輸入。

import * as circle from 'circle';
document.write("圓面積:" + circle.area(4));
document.write("圓周長:" + circle.circumference(14));

module命令

module命令可以取代import語句,達到整體輸入模組的作用。

// main.js
module circle from 'circle';

document.write("圓面積:" + circle.area(4));
document.write("圓周長:" + circle.circumference(14));

module命令後面跟一個變數,表示輸入的模組定義在該變數上。

export default命令

為載入模組指定預設輸出,使用export default命令。

// export-default.js
export default function () {
  document.write('foo');
}

上面程式碼是一個模組檔案export-default.js,它的預設輸出是一個函式。

其他模組載入該模組時,import命令可以為該匿名函式指定任意名字。

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面程式碼的import命令,可以用任意名稱指向export-default.js輸出的方法。需要注意的是,這時import命令後面,不使用大括號。

模組的繼承

模組之間也可以繼承。

假設有一個circleplus模組,繼承了circle模組。

// circleplus.js

export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
  return Math.exp(x);
}

上面程式碼中的“export *”,表示輸出circle模組的所有屬性和方法,export default命令定義模組的預設方法。

這時,也可以將circle的屬性或方法,改名後再輸出。

// circleplus.js

export { area as circleArea } from 'circle';

上面程式碼表示,只輸出circle模組的area方法,且將其改名為circleArea。

載入上面模組的寫法如下。

// main.js

module math from "circleplus";
import exp from "circleplus";
document.write(exp(math.pi));

上面程式碼中的"import exp"表示,將circleplus模組的預設方法載入為exp方法。

對應的互動式免費學習課程在http://www.hubwiz.com/course/5594e91ac086935f4a6fb8ef/

相關文章