TypeScript 學習筆記(5) – Interface

今天是看這篇:Interfaces

TypeScript 有 anonymous interface ,就像下面的程式一樣:

// 有點 anonymous interface 的味道
function printLabel1(labelledObj: { label: string }) {
    console.log(labelledObj.label);
}

// 也可以明確的用 interface 寫
interface LabelledValue {
    label: string;
}

function printLabel2(labelledObj: LabelledValue) {
    console.log(labelledObj.label);
}

 

在宣告成員時,如果加入 ? ,表示可有可無,這個寫法應該是參考了 Ruby 的作法

interface SquareConfig {
    color?: string;
    width?: number;
}

 

用 readonly 修飾詞則是表示唯讀

interface Point {
    readonly x: number;
    readonly y: number;
}

 

如果要需告一個唯讀的陣列,可以用 ReadonlyArray

let ro: ReadonlyArray = a;

 

我覺得 Function types 比較不是那麼直覺,不容易看懂

interface SearchFunc {
    (source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result > -1;
}

大抵來說就是 python 的 __call__ 或是 c++ 的 operator()。

Indexable type 也是不太直覺

interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

大抵來說就是 python 的 __item__ 或是 c++ 的 operator[]。

要讓類別實作介面是用 implements 關鍵字

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

 

下面這例子是想要限定 ClockInterface 裡的 constructor 必須符合 ClockConstructor 規範的作法,第一眼不太容易看懂。

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}

// 傳進來的 ctor 必須符合 ClockConstructor 的規範,要有 hour 跟 minute 這兩個 number 型別的參數。
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

 

介面也可以繼承,跟類別一樣,是用 extends,而且允許繼承多個

interface Shape {
    color: string;
}
interface PenStroke {
    penWidth: number;
}
interface Square extends Shape, PenStroke {
    sideLength: number;
}

 

蠻令我驚訝的是,Interface 可以繼承 class … @_@

TypeScript 學習筆記(4) – 變數

JavaScript 提出了兩個新的關鍵字:let 與 const。使用 let 可以避免使用 var 的一些奇妙現象;而 const 就是跟 C 的 const 一樣,避免再次指定值給這個變數。

Variable Declarations 裡提了許多 JavaScript 使用 var 宣告變數時的奇妙現象,然後解說為什麼要改用 let。這部份我偷懶跳過去了,我的想法是,就都改用 let 吧。

另外有提到 Destructuring,就是類似 Python 的 unpack :

// JavaScript/TypeScript
let input = [1, 2];
let [first, second] = input;

Python 則是:

# Python
first, second = (1, 2)

也跟 Python 3 一樣,有取 remain items 的效果:

let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]

Object destructuring 是個比較特別的東西,意思跟 destructuring 差不多,但對象是 object,也就是可以將 object 的某幾個屬性放到變數裡。

let o = {
    a: "foo",
    b: 12,
    c: "bar"
}
let { a, b } = o;
({ a, b } = { a: "baz", b: 101 });  // 這樣也可

也支援 remaining items 的方法:

let { a, ...passthrough } = o;

Object destructuring 還支援 default values,意思是如果 wholeObject 沒有 b 屬性時,就把 b 指定為 1001:

function keepWholeObject(wholeObject: { a: string, b?: number }) {
    let { a, b = 1001 } = wholeObject;
}

也可以應用到 function 上,我覺得這個例子比較清楚,可以應用在只取物件的某幾個屬性來使用時:

function f({ a, b } = { a: "", b: 0 }): void {
    // ...
}
f(); // ok, default to { a: "", b: 0 }

Spread 算是陣列相加的一個 syntax sugar,可以把 first, second 塞到 bothPlus 這陣列裡去:

let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];

也可以應用在 object 上,目前想到可以用來做 object proxy 或是 composite 之類的,不過不適合也不一定。

let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { food: "rich", ...defaults };

 

TypeScript 學習筆記(3) – 基本型別

今天是看這篇:Basic Types

裏面直接建議用 ES6 的 let 替代 var 了。

  • boolean:布林型態
  • number:數字型態,沒有 integer, long, float, double 之類的,只有 number,JavaScript 裡的數字都是 float。
  • string:字串
  • array:陣列,在型別後面加上 [],例如: let list: number[] = [1, 2, 3]; ,要注意的是,無法指定固定個數。
  • tuple:就 tuple ,有點像陣列,但能指定每個元素的型態,這跟 Python 不一樣。例如:
    let x: [string, number];
    let x = ["Hello", 100];
    let y: [string, number] = ["John", 200];
    y[2] = "Doe";
    console.log(y);  // 輸出 [ 'John', 200, 'Doe' ]
    
  • enum:列舉,用法跟其他語言大致相似:
    enum Color {Red, Green, Blue};
    let c: Color = Color.Green;
    // 比較特別的用法
    let colorName: string = Color[2];
    console.log(colorName);  // 輸出 Blue
    
  • any:任意型態,但要注意的是跟其他語言所指的 Object 型別不同,TypeScript 有獨立一個 Object 型別。說起來跟 C# 的 dynamic 比較相似。
    let notSure: any = 4;
    notSure.ifItExists(); // 通過編譯,因為可能在執行時期就有 ifItExists()
    notSure.toFixed(); // 通過編譯,因為可能在執行時期就有 toFixed()
    
    let prettySure: Object = 4;
    prettySure.toFixed(); // 編譯錯誤,編譯時會有 Object 型別沒有 toFixed 的錯誤。
  • void:就 void ,表示不會傳回任何東西
  • null:就 null ,可以把任何型態的變數指定為 null
  • undefined:就 undefined,可以把任何型別的變數指定為 undefined
  • never:表示絕對不會傳回變數,
    // 只會丟出例外
    function error(message: string): never {
        throw new Error(message);
    }
    // 裏面是不會結束的迴圈
    function infiniteLoop(): never {
        while (true) {
        }
    }

Type assertion ,有 C/C# 強制轉型的意味。

let someValue: any = "this is a string";
let strLength: number = (someValue).length;
// 也可以用類似 c# as 的語法
let strLength: number2 = (someValue as string).length;

 

TypeScript 學習筆記(2) – Type annotation/Interface/Class

以下是看 Quick start 的紀錄:

  • Type annotation:一般動態語言是沒型別的,但 TypeScript 加上了這部份的支援,這可以讓開發者在編譯時期就預先發現型別錯誤,也可以編譯出更有效率的程式,讓程式執行的更快。語法像是 Go 或 Pascal (知道這語言的人應該不多了),是在變數後面加上 :type,例如:
    var str:string="hello world";
  • Interface:介面,沒啥特別的,看範例比較快
    interface Person {
        firstName: string;
        lastName: string;
    }
    
    function greeter(person: Person) {
        return "Hello, " + person.firstName + " " + person.lastName;
    }
    
    var user = { firstName: "Jane", lastName: "User" };
    
  • Class:大致上跟 java 的用法相似,不過在看到 QuickStart 範例時,還是驚訝了一下,主要是因為 TypeScript 語法並沒有明確指定類別實作了 Person 介面,但這個類別所產生的物件仍可以直接丟到只接介面的函式裡。後來仔細看了一下,才知道是因為 TypeScript 的 constructor (建構子)裡的參數加了 public,那麼會自動將這個參數放到同名的屬性去,也因此就符合了 Person 介面的要求。
    class Student {
        fullName: string;
        // 這裡的 public firstName,等同是函式裡有 this.firstName=firstName
        constructor(public firstName, public middleInitial, public lastName) {
            this.fullName = firstName + " " + middleInitial + " " + lastName;
        }
    }
    
    // 故意不要有 lastName
    class Employee {
        constructor(public firstName) {
        }
    }
    
    interface Person {
        firstName: string;
        lastName: string;
    }
    
    // 因為 Student 裡有 firstName, lastName 屬性,視同實作了 Person
    function greeter(person : Person) {
        return "Hello, " + person.firstName + " " + person.lastName;
    }
    
    var user = new Student("Jane", "M.", "User");
    var employee = new Employee("John");
    console.log(greeter(user));
    // console.log(greeter(employee));  // 加了這行,編譯會發生錯誤,告知 employee 不符合 Person 介面
    

TypeScript 學習筆記(1) – 安裝與執行

我習慣用的環境是 Ubuntu,目前用的是 16.04 Xenial。

第一步是安裝,安裝 nodejs 有幾種方法:

  1. nodejs.org 下載 tarball,手動安裝。
  2. 用 debian package 來安裝 (nodesource)
  3. 用 nvm 來安裝

之前有用過 nvm 了,這次我選擇用 debian package 來安裝 LTS 版的 nodejs。

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs

安裝好,就可以利用 npm 來安裝 TypeScript 了。不過,因為我不想把 typescript 安裝在系統路徑 (一般是用 sudo npm install -g typescript),所以我多設定了 .npmrc 以及環境變數

prefix = /home/user/.local
root = /home/user/.local/lib/node_modules
binroot = /home/user/.local/bin
manroot = /home/user/.local/share/man

 

export LOCAL_PATH="$HOME/.local"
export MANPATH="$LOCAL_PATH/share/man:$MANPATH"
export NODE_PATH="$LOCAL_PATH/lib/node_modules:$NODE_PATH"
export PATH="$LOCAL_PATH/bin:$PATH"

 

在設定好以後,使用 npm install -g 時,會將這些套件安裝到自己的 $HOME/.local 目錄下。

在使用 npm install -g typescript 以後,我另外安裝了去年推出的 yarn。

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt install yarn

 

首先寫一個 hello.ts

console.log("Hello world.");

然後用 tsc 來將 hello.ts 編譯為 hello.js: tsc hello.ts ,最後就可以用 node 執行 hello.js 印出 “Hello world.”

電影流水帳(2017/1/1~2017/1/11)

有村架純 #jdgmovie #jdgacter 有村架純

  • 寒戰2 (IMDB, Wikipedia)。整體來說,我覺得比前集稍弱了些。
    延續上集的發展,李文彬兒子李家俊入獄,但幕後的黑手蔡元祺以劉傑輝的家人作為人質跟劉傑輝談判、斡旋,劉傑輝不得已,只能隻身押解李家俊去指定的地點,但透過同黨的幫忙,李家俊逃走,劉傑輝因此面對調查會與輿論的壓力。
    蔡元祺這時出面拉攏李文彬,說明香港政治高層的佈局計劃,要求他一起加入這行動。於是就開始利用之前的衝鋒車與調查會來鬥爭劉傑輝。調查會裡的簡奧偉覺得事情不單純,派自己好友的女兒歐詠恩去調查。而劉傑輝也不甘示弱,結合了廉政署展開警政體制外的祕密調查行動。後來歐詠恩因為調查行動而被蔡元祺人馬害死,於是簡奧偉跟劉傑輝聯手,找出蔡元祺一行人窩藏衝鋒車的地方,並展開反擊。最後,劉傑輝與蔡元祺達成協議,蔡元祺遠走國外,劉傑輝穩住自己的位置以及香港高層的穩定,李文彬則被強制退休。從最後的故事走向,似乎是會有第三集的樣子。
  • ビリギャル (IMDB, Wikipedia),台譯:墊底辣妹。之前有買書,所以特別把電影也看過一次。我覺得改編的不錯,保留了書裡的故事性,把書裡大部份教怎麼唸書的地方抽離,卻又不失味道,相當勵志的故事。
    さやか是個沒怎麼唸書的少女,在母親的支持之下,到了青田塾。青田塾的坪田老師是個特別的老師,以一對一的對談去了解並引導學生, さやか因此慢慢開始轉變,並且開始對唸書有了興趣,也對自己有了信心。
    最後,在 さやか的努力下,家裡的氣氛改變了,自己的成績也愈來愈好,順利考上了慶應大學。
  • 盜墓筆記 (IMDB, Wikipedia)。前面的劇情還可以,到了後面就走味了,而且是濃濃的腐味。
    一開始是帶出悶油瓶跟外國人反派的過節,接著鋪陳出吳邪的身世以及銅片與蛇母的祕密。經過一番交代,三叔就帶著吳邪、悶油瓶跟潘子下墓探險了。值此同時,外國人反派也派了阿寧(馬思純)帶著一群傭兵去伺機搶奪。在蛇母墓展開一場你追我奪的探險,到了最後,蛇母出現,從這邊開始,莫名其妙的帶出阿寧跟胖子(阿寧隊上的翻譯)的愛情線,悶油瓶跟吳邪的腐味。與蛇母的大戰並不順利,吳邪天真的要求外國人反派幫忙,而反派居然答應了,於是順利的逆轉局勢,消滅蛇母。三叔、吳邪跟阿寧等主角順利的逃出,悶油瓶則犧牲了,看起來是還有續集的樣子?
  • 瘋狂的賽車 (IMDB, Wikipedia)。好幾年前看過一次,這次是重看的。話說啊,維基百科裡的劇情真的寫的超清楚的。
    耿浩在一次比賽後被設計了,導致拿到的銀牌飛了,也被禁賽。他的師傅氣到中風,耿浩只能開著貨車幫人送貨過活。後來師傅在電視上看到害他們的李法拉,再次氣到從樓上滾下去,死了。耿浩為了安葬師傅,決定找李法拉討錢。
    李法拉則是想把妻子殺了,所以找了殺手二人組來動手,結果反被妻子收買,改為去修理李法拉。後來陰錯陽差的變成李法拉親手殺了自己的妻子。從台灣來的烏龍幫四人組,來中國做毒品交易,結果一直沒能做成交易。故事就在這些人裡轉來轉去,產生出一連串的巧合,到了最後,李法拉跟烏龍幫四人組死了,殺手二人組成了証人,耿浩報了仇,拿到了錢,為師傅風光大葬。