類
介紹
從 ES6 開始,JS 程序員將能夠使用基於類的面向對象的方式。 使用TypeScript,我們允許開發者現在就使用這些特性,並且編譯後的 JS 可以在所有主流瀏覽器和平臺上運行,而不需要等到下個 JS 版本。
使用
class Greeter {
greeting: string
constructor (message: string) {
this.greeting = message;
}
greet(){
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter('world')
console.log(greeter.greet());
在 TS 中可以約束參入的值
繼承
和 ES6 的語法一樣,如果你不會 ES6 的語法,最好還是看一下阮老師寫的:https://es6.ruanyifeng.com/#docs/class
class Animal {
name: string;
constructor(theName: string) { this.name = theName; }
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
class Horse extends Animal {
constructor(name: string) { super(name); }
move(distanceInMeters = 45) {
console.log("Galloping...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
let tom: Animal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
定義一個可以繼承方法和對象的類。
修飾符
默認爲公共(public)
我們之前在屬性和方法前沒有寫任何東西,就意味着默認是 public
,就像下面一樣
public
意味着 我們可以自由的訪問程序裏定義的成員
class Animal {
public name: string;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
私有(private)
private
意味着 它就不能在聲明它的類的外部訪問,是可以繼承 ,例如:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // 錯誤: 'name' 是私有的.
當我們比較兩種不同的類型時,並不在乎它們從何處而來,如果所有成員的類型都是兼容的,我們就認爲它們的類型是兼容的。然而,當我們比較帶有
private
或protected
成員的類型的時候,情況就不同了。如果其中一個類型裏包含一個private
成員,那麼只有當另外一個類型中也存在這樣一個private
成員, 並且它們都是來自同一處聲明時,我們才認爲這兩個類型是兼容的。 對於protected
成員也使用這個規則。
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // 錯誤: Animal 與 Employee 不兼容.
在這裏爲什麼Animal 與 Employee 不兼容,而 Animal 和 Rhino 就兼容呢?雖然 Animal 和 Employee 一樣,僅僅 名字不同。就是因爲 private,上面說的比較規則。因爲 Animal 和 Rhino 是繼承關係,屬於同一聲明。
protected
protected
修飾符與 private
修飾符的行爲很相似,但有一點不同, protected
成員在派生(繼承)類中仍然可以訪問
class Person {
protected name: string;
constructor(name: string) { this.name = name; }
}
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name)
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 錯誤
注意,我們不能在 Person
類外使用 name
,但是我們仍然可以通過 Employee
類的實例方法訪問,因爲 Employee
是由 Person
派生而來的。
構造函數也可以被標記成 protected
。 這意味着這個類不能在包含它的類外被實例化,但是能被繼承。比如,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
// Employee 能夠繼承 Person
class Employee extends Person {
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 錯誤: 'Person' 的構造函數是被保護的.
static
之前的代碼都是在討論實例的,staic 修飾符是 存在於類本身上面而不是類的實例上,被標記的靜態屬性或方法都只可以在類上訪問,而在類創建的實例上是不可以進行訪問的。
class Grid {
static origin = {x: 0, y: 0};
static calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
scale:number
constructor (scale: number) {
this.scale = scale;
}
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.scale);
grid1.origin // 訪問不了
Grid.origin // 可以訪問
readonly
你可以使用 readonly
關鍵字將屬性設置爲只讀的。 只讀屬性必須在聲明時或構造函數裏被初始化。
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 錯誤! name 是隻讀的.
其實我們可以在構造函數中使用 readonly
,這個叫做 參數屬性
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {
}
}
最好還是不要使用 參數屬性,使用第一種方法比較好(清晰明瞭)
存取器
其實就是 ES5中的 getters/setters
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
this._fullName = newName;
}
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
console.log(employee.fullName);
}
注意 typescript 默認輸出 ES3 版本的代碼,getters/setters 是 ES5 的語法,如果想要使用就必須把輸出帶包調整到 ES5
抽象類
抽象類做爲其它派生類的基類使用。 它們一般不會直接被實例化。 不同於接口,抽象類可以包含成員的實現細節。 abstract
關鍵字是用於定義抽象類和在抽象類內部定義抽象方法。
abstract class Department {
name:string
constructor(name: string) {
this.name = name
}
printName(): void {
console.log('Department name: ' + this.name);
}
abstract printMeeting(): void; // 必須在派生類中實現
}
class AccountingDepartment extends Department {
constructor() {
super('Accounting and Auditing'); // 在派生類的構造函數中必須調用 super()
}
printMeeting(): void {
console.log('The Accounting Department meets each Monday at 10am.');
}
generateReports(): void {
console.log('Generating accounting reports...');
}
}
let department: Department; // 允許創建一個對抽象類型的引用
department = new Department(); // 錯誤: 不能創建一個抽象類的實例
department = new AccountingDepartment(); // 允許對一個抽象子類進行實例化和賦值
department.printName();
department.printMeeting();
department.generateReports(); // 錯誤: 方法在聲明的抽象類中不存在
函數
其實前面我也提到過一些函數,我認爲這個沒什麼好說的,如果你還不瞭解,可以看一下:https://www.tslang.cn/docs/handbook/functions.html
值得說一下的是 重載,其實這也沒什麼好說的,這個是 JS 函數自代的特性,下面是 JS 處理不同參數的重載
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
TS 的重載(java,c#類似的重載)
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);
泛型
在像C#和Java這樣的語言中,可以使用泛型
來創建可重用的組件,一個組件可以支持多種類型的數據。 這樣用戶就可以根據自己的數據類型來使用組件。
下面我們實現一個例子,你傳入什麼類型的數據,就返回什麼類型的數據
不用泛型的話,這個函數可能是下面這樣:
function identity(arg: number): number {
return arg;
}
或者,我們使用any
類型來定義函數:
function identity(arg: any): any {
return arg;
}
使用any
類型會導致這個函數可以接收任何類型的arg
參數,這樣就丟失了一些信息:傳入的類型與返回的類型應該是相同的。如果我們傳入一個數字,我們只知道任何類型的值都有可能被返回。下面的函數就會返回字符串,不符合我們的要求。
function identity(arg: any): any {
return arg + 'sd';
}
因此,我們需要一種方法使返回值的類型與傳入參數的類型是相同的。 這裏,我們使用了 類型變量,它是一種特殊的變量,只用於表示類型而不是值。下面我們定義了一個泛型函數,可以根據我們傳入的類型來返回數據
function identity<T>(arg: T): T {
return arg;
}
let output = identity<string>('hello'); //第一種
let output = identity('hello'); //第二種 (推薦)
// 其實第一種和第二種方法一樣,第二種只不過是利用了類型推斷
console.log(output);
泛型變量
使用泛型創建像identity
這樣的泛型函數時,編譯器要求你在函數體必須正確的使用這個通用的類型。 換句話說,你必須把這些參數當做是任意或所有類型。
看下之前identity
例子:
function identity<T>(arg: T): T {
return arg;
}
但是如果我們想同時打印出arg
的長度。 我們很可能會這樣做:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
如果這麼做,編譯器會報錯說我們使用了arg
的.length
屬性,但是沒有地方指明arg
具有這個屬性。 記住,這些類型變量代表的是任意類型,所以使用這個函數的人可能傳入的是個數字,而數字是沒有 .length
屬性的。
現在假設我們想操作T
類型的數組而不直接是T
。由於我們操作的是數組,所以.length
屬性是應該存在的。 我們可以像創建其它數組一樣創建這個數組:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以這樣理解loggingIdentity
的類型:泛型函數loggingIdentity
,接收類型參數T
和參數arg
,它是個元素類型是T
的數組,並返回元素類型是T
的數組。 如果我們傳入數字數組,將返回一個數字數組,因爲此時 T
的的類型爲number
。 這可以讓我們把泛型變量T當做類型的一部分使用,而不是整個類型,增加了靈活性。
我們也可以這樣實現上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
泛型類型
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
我們也可以使用不同的泛型參數名,只要在數量上和使用方式上能對應上就可以。
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: <U>(arg: U) => U = identity;
我們還可以使用帶有調用簽名的對象字面量來定義泛型函數:
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;
這引導我們去寫第一個泛型接口了。 我們把上面例子裏的對象字面量拿出來做爲一個接口:
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
一個相似的例子,我們可能想把泛型參數當作整個接口的一個參數。 這樣我們就能清楚的知道使用的具體是哪個泛型類型(比如: Dictionary<string>而不只是Dictionary
)。 這樣接口裏的其它成員也能知道這個參數的類型了。
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
// 推薦這種方式
注意,我們的示例做了少許改動。 不再描述泛型函數,而是把非泛型函數簽名作爲泛型類型一部分。 當我們使用 GenericIdentityFn
的時候,還得傳入一個類型參數來指定泛型類型(這裏是:number
),鎖定了之後代碼裏使用的類型。 對於描述哪部分類型屬於泛型部分來說,理解何時把參數放在調用簽名裏和何時放在接口上是很有幫助的。
除了泛型接口,我們還可以創建泛型類。 注意,無法創建泛型枚舉和泛型命名空間。
泛型類
泛型類看上去與泛型接口差不多。 泛型類使用( <>
)括起泛型類型,跟在類名後面。
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
GenericNumber
類的使用是十分直觀的,並且你可能已經注意到了,沒有什麼去限制它只能使用number
類型。 也可以使用字符串或其它更復雜的類型。
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
與接口一樣,直接把泛型類型放在類後面,可以幫助我們確認類的所有屬性都在使用相同的類型。
我們在類那節說過,類有兩部分:靜態部分和實例部分。 泛型類指的是實例部分的類型,所以類的靜態屬性不能使用這個泛型類型。
泛型約束
你應該會記得之前的一個例子,我們有時候想操作某類型的一組值,並且我們知道這組值具有什麼樣的屬性。 在 loggingIdentity
例子中,我們想訪問arg
的length
屬性,但是編譯器並不能證明每種類型都有length
屬性,所以就報錯了。
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
相比於操作any所有類型,我們想要限制函數去處理任意帶有.length
屬性的所有類型。 只要傳入的類型有這個屬性,我們就允許,就是說至少包含這一屬性。 爲此,我們需要列出對於T的約束要求。
爲此,我們定義一個接口來描述約束條件。 創建一個包含 .length
屬性的接口,使用這個接口和extends
關鍵字來實現約束:
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
現在這個泛型函數被定義了約束,因此它不再是適用於任意類型:
loggingIdentity(3); // Error, number doesn't have a .length property
我們需要傳入符合約束類型的值,必須包含必須的屬性:
loggingIdentity({length: 10, value: 3});
在泛型約束中使用類型參數
你可以聲明一個類型參數,且它被另一個類型參數所約束。 比如,現在我們想要用屬性名從對象裏獲取這個屬性。 並且我們想要確保這個屬性存在於對象 obj
上,因此我們需要在這兩個類型之間使用約束。
function getProperty<T,K extends keyof T>(obj: T,key: K){
return obj[key];
}
let x = {one:1,two:2,three:3};
console.log(getProperty(x,'one'));
console.log(getProperty(x,'four')); // Argument of type '"four"' is not assignable to parameter of type '"one" | "two" | "three"'
說明一下 keyof
是返回對象的所有索引構成的聯合類型
類型推斷
TypeScript裏,在有些沒有明確指出類型的地方,類型推論會幫助提供類型。如下面的例子
let x = 3;
變量x
的類型被推斷爲數字。 這種推斷髮生在初始化變量和成員,設置默認參數值和決定函數返回值時。
大多數情況下,類型推論是直截了當地。 後面的小節,我們會瀏覽類型推論時的細微差別。
最佳通用類型
當需要從幾個表達式中推斷類型時候,會使用這些表達式的類型來推斷出一個最合適的通用類型。例如,
let x = [0, 1, null];
爲了推斷x
的類型,我們必須考慮所有元素的類型。 這裏有兩種選擇: number
和null
。 計算通用類型算法會考慮所有的候選類型,並給出一個兼容所有候選類型的類型。
由於最終的通用類型取自候選類型,有些時候候選類型共享相同的通用類型,但是卻沒有一個類型能做爲所有候選類型的類型。例如:
let zoo = [new Rhino(), new Elephant(), new Snake()];
這裏,我們想讓zoo被推斷爲Animal[]
類型,但是這個數組裏沒有對象是Animal
類型的,因此不能推斷出這個結果。 爲了更正,當候選類型不能使用的時候我們需要明確的指出類型:
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
如果沒有找到最佳通用類型的話,類型推斷的結果爲聯合數組類型,(Rhino | Elephant | Snake)[]
。
上下文類型
TypeScript類型推論也可能按照相反的方向進行。 這被叫做“按上下文歸類”。按上下文歸類會發生在表達式的類型與所處的位置相關時。比如:
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); //<- Error
};
這個例子會得到一個類型錯誤,TypeScript類型檢查器使用Window.onmousedown
函數的類型來推斷右邊函數表達式的類型。 因此,就能推斷出 mouseEvent
參數的類型了。 如果函數表達式不是在上下文類型的位置, mouseEvent
參數的類型需要指定爲any
,這樣也不會報錯了。
如果上下文類型表達式包含了明確的類型信息,上下文的類型被忽略。 重寫上面的例子:
window.onmousedown = function(mouseEvent: any) {
console.log(mouseEvent.button); //<- Now, no error is given
};
這個函數表達式有明確的參數類型註解,上下文類型被忽略。 這樣的話就不報錯了,因爲這裏不會使用到上下文類型。
上下文歸類會在很多情況下使用到。 通常包含函數的參數,賦值表達式的右邊,類型斷言,對象成員和數組字面量和返回值語句。 上下文類型也會做爲最佳通用類型的候選類型。比如:
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
這個例子裏,最佳通用類型有4個候選者:Animal
,Rhino
,Elephant
和Snake
。 當然, Animal
會被做爲最佳通用類型。
高級類型
交叉類型
交叉類型是將多個類型合併爲一個類型。 這讓我們可以把現有的多種類型疊加到一起成爲一種類型,它包含了所需的所有類型的特性。 例如, Person & Serializable & Loggable
同時是 Person
和 Serializable
和 Loggable
。 就是說這個類型的對象同時擁有了這三種類型的成員。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{}; // {} as T & U
for (let id in first) {
result[id] = first[id] as any;
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
result[id] = second[id] as any;
}
}
return result;
} // 定義
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger()); // 使用
var n = jim.name;
jim.log();
聯合類型
偶爾你會遇到這種情況,一個代碼庫希望傳入 number
或 string
類型的參數,你就可以使用聯合類型
function padLeft(value: string, padding: string | number) {
// ...
}
padding 就既可以是字符串或數字類型
如果是你自己定義的類,接口,泛型也是可以的
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
類型保護和區分類型
還是上一個例子,但當我們想確切地瞭解是否爲 Fish
時怎麼辦? JavaScript裏常用來區分2個可能值的方法是檢查成員是否存在。
當然我們可以通過 if 的方式確認,類型斷言 也是可以的。
這裏介紹類型保護,下面一個例子,判斷到底是否是Fish
接口?
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
類型保護 通過謂詞爲 parameterName is Type
這種形式, parameterName
必須是來自於當前函數簽名裏的一個參數名。
還有一種 typeof
提供的類型保護
function isNumber(x: any): x is number {
return typeof x === "number";
}
function isString(x: any): x is string {
return typeof x === "string";
}
function padLeft(value: string, padding: string | number) {
if (isNumber(padding)) {
return Array(padding + 1).join(" ") + value;
}
if (isString(padding)) {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
現在我們不必將 typeof x === "number"
抽象成一個函數,因爲TypeScript可以將它識別爲一個類型保護。 也就是說我們可以直接在代碼裏檢查類型了。
我們還可以使用instanceof
來進行類型保護
interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
return this.value;
}
}
function getRandomPadder() {
return Math.random() < 0.5 ?
new SpaceRepeatingPadder(4) :
new StringPadder(" ");
}
// 類型爲SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();
if (padder instanceof SpaceRepeatingPadder) {
padder; // 類型細化爲'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
padder; // 類型細化爲'StringPadder'
}
instanceof
的右側要求是一個構造函數,TypeScript將細化爲:
- 此構造函數的
prototype
屬性的類型,如果它的類型不爲any
的話 - 構造簽名所返回的類型的聯合
以此順序。
手動取消類型斷言
如果編譯器不能夠去除 null
或 undefined
,你可以使用類型斷言手動去除。 語法是添加 !
後綴: identifier!
從 identifier
的類型裏去除了 null
和 undefined
:
function broken(name: string | null): string {
function postfix(epithet: string) {
return name.charAt(0) + '. the ' + epithet; // error, 'name' is possibly null
}
name = name || "Bob";
return postfix("great");
}
function fixed(name: string | null): string {
function postfix(epithet: string) {
return name!.charAt(0) + '. the ' + epithet; // ok
}
name = name || "Bob";
return postfix("great");
}
字符串字面量
字符串字面量類型允許你指定字符串必須的固定值。 在實際應用中,字符串字面量類型可以與聯合類型,類型保護和類型別名很好的配合。 通過結合使用這些特性,你可以實現類似枚舉類型的字符串。
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}
let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here
通過使用 type
來實現
你只能從三種允許的字符中選擇其一來做爲參數傳遞,傳入其它值則會產生錯誤。
當然也可以使用數字字面量
其實還有其他的一些特性,我只總結了一些常用的東西,剩下的就需要去看官方的文檔了。