你會玩Javascript多級繼承嗎怠益?
你能說明白js的作用域嗎贮乳?
曾經(jīng)有個(gè)面試題忧换,用console.log寫一個(gè)方法,要求輸入1向拆,輸出的是0亚茬,結(jié)果
const console = Math
console.log(1) // 0
實(shí)際上,JavaScript里面充斥著大量的彩蛋浓恳,消耗大家非常多的時(shí)間刹缝。
JavaScript這門語言一直以來為什么會表現(xiàn)得這么詭異呢葡兑?一定要看阮老師的這篇文章Javascript誕生記
我們再看看TS的代碼長什么樣子
Validation.ts
export interface StringValidator {
isAcceptable(s: string): boolean;
}
LettersOnlyValidator.ts
import { StringValidator } from "./Validation";
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
仔細(xì)看你會發(fā)現(xiàn),TS跟我們現(xiàn)在用的es6很像赞草,我們學(xué)習(xí)TS并沒有什么學(xué)習(xí)成本。
而TS無論可讀性吆鹤、可維護(hù)性厨疙、上手的容易程度、寫代碼的速度疑务,都非常明顯地優(yōu)于JS沾凄。
TS入門
基礎(chǔ)類型
布爾值、數(shù)字知允、字符串撒蟀、數(shù)組、元組温鸽、枚舉保屯、any、void涤垫、Null 和 Undefined姑尺、Never、類型斷言
let isDone: boolean = false; // 布爾值
// 數(shù)字類型蝠猬,支持十進(jìn)制和十六進(jìn)制切蟋,二進(jìn)制和八進(jìn)制字面量
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let sentence: string = `Hello, my name is ${ name }.`; // 字符串類型
// 數(shù)組類型 類型+[]
let list: number[] = [1, 2, 3]; // 可以在元素類型后面接上 [],表示由此類型元素組成的一個(gè)數(shù)組
let list: Array<number> = [1, 2, 3];// 使用數(shù)組泛型榆芦,Array<元素類型>
// (number | string)[]這是聯(lián)合類型和數(shù)組的結(jié)合
// any[] 任意類型和數(shù)組的結(jié)合
// 元組類型 元組類型允許表示一個(gè)已知元素?cái)?shù)量和類型的數(shù)組柄粹,各元素的類型不必相同
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error
// 枚舉類型
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
// 任意類型
let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
// void 表示沒有任何類型
let unusable: void = undefined;// 聲明一個(gè)void類型的變量沒有什么大用,因?yàn)槟阒荒転樗x予undefined和null
// null和undefined 類型 Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
// never 類型 返回never的函數(shù)必須存在無法達(dá)到的終點(diǎn)
function error(message: string): never {
throw new Error(message);
}
類型斷言
語法
<類型>值
值 as 類型(JSX)
let strLength: number = (<string>someValue).length; //(尖括號)
let strLength: number = (someValue as string).length; // as語法
當(dāng) TypeScript 不確定一個(gè)聯(lián)合類型的變量到底是哪個(gè)類型的時(shí)候匆绣,我們只能訪問此聯(lián)合類型的所有類型里共有的屬性或方法
// error
function getLength(something: string | number): number {
return something.length;
}
// error
function getLength(something: string | number): number {
if (something.length) {
return something.length;
} else {
return something.toString().length;
}
}
// 使用類型斷言驻右,將something斷言成 string
function getLength(something: string | number): number {
if ((<string>something).length) {
return (<string>something).length;
} else {
return something.toString().length;
}
}
// 類型斷言不是類型轉(zhuǎn)換,斷言成一個(gè)聯(lián)合類型中不存在的類型是不允許的
// error
function toBoolean(something: string | number): boolean {
return <boolean>something;
}
類型推論
TypeScript 會在沒有明確的指定類型的時(shí)候推測出一個(gè)類型犬绒,這就是類型推論旺入。
// 類型推論
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;// error TS2322: Type 'number' is not assignable to type 'string'.
// 等價(jià)于
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// 如果定義的時(shí)候沒有賦值,不管之后有沒有賦值凯力,都會被推斷成 any 類型而完全不被類型檢查
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
聯(lián)合類型
聯(lián)合類型使用 | 分隔每個(gè)類型
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
myFavoriteNumber = true; // Type 'boolean' is not assignable to type 'string | number'.
變量聲明
let 變量聲明
const 常量聲明
ts跟es6一樣推薦使用 let 和 const茵瘾,因?yàn)樗麄兌际菈K級作用域;不存在變量提升咐鹤;
var 作為一個(gè)全局變量拗秘,易造成環(huán)境的變量污染;
”變量提升“祈惶,即變量可以在聲明之前使用雕旨,值為undefined扮匠。這種現(xiàn)象多多少少是有些奇怪的,按照一般的邏輯凡涩,變量應(yīng)該在聲明語句之后才可以使用棒搜。為了糾正這種現(xiàn)象,let命令改變了語法行為活箕,它所聲明的變量一定要在聲明后使用力麸,否則報(bào)錯(cuò)。
// var 的情況
console.log(foo); // 輸出undefined
var foo = 2;
// let 的情況
console.log(bar); // 報(bào)錯(cuò)ReferenceError
let bar = 2;
”暫時(shí)性死區(qū)“(temporal dead zone育韩,簡稱 TDZ)克蚂,即只要塊級作用域內(nèi)存在let命令,它所聲明的變量就“綁定”(binding)這個(gè)區(qū)域筋讨,不再受外部的影響埃叭。
var tmp = 123;
if (true) {
// TDZ開始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ結(jié)束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
結(jié)構(gòu)賦值
// 數(shù)組結(jié)構(gòu)賦值
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
// 展開
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
// 對象的結(jié)構(gòu)賦值
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
// 屬性重命名
let { a: newName1, b: newName2 } = o;
結(jié)構(gòu)賦值同樣適用于函數(shù)聲明
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
函數(shù)類型
函數(shù)聲明
function sum(x: number, y: number): number {
return x + y;
}
// 輸入多余的(或者少于要求的)參數(shù),是不被允許的
sum(1, 2, 3); // error
sum(1); // error
函數(shù)表達(dá)式
let mySum = function (x: number, y: number): number {
return x + y;
};
// 等價(jià)于
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
// 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>悉罕。
// 在 TypeScript 的類型定義中赤屋,=> 用來表示函數(shù)的定義,左邊是輸入類型蛮粮,需要用括號括起來益缎,右邊是輸出類型。
// 在 ES6 中然想,=> 叫做箭頭函數(shù)
可選參數(shù)
function buildName(x: string, y?: string): string {
if (y) {
return `${x}${y}`;
} else {
return x;
}
}
注意莺奔,可選參數(shù)必須接在必需參數(shù)后面。換句話說变泄,可選參數(shù)后面不允許再出現(xiàn)必須參數(shù)了
參數(shù)默認(rèn)值
function buildName(x: string, y: string = 'Cat') {
return `${x}${y}`;
}
剩余參數(shù)
function push(array: any[], ...items: any[]) {
items.forEach(function(item) {
array.push(item);
});
}
重載
重載允許一個(gè)函數(shù)接受不同數(shù)量或類型的參數(shù)時(shí)令哟,作出不同的處理;
比如妨蛹,我們需要實(shí)現(xiàn)一個(gè)函數(shù) reverse屏富,輸入數(shù)字 123 的時(shí)候,輸出反轉(zhuǎn)的數(shù)字 321蛙卤,輸入字符串 'hello' 的時(shí)候狠半,輸出反轉(zhuǎn)的字符串 'olleh'。
// 利用聯(lián)合類型颤难,我們可以這么實(shí)現(xiàn)
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
然而這樣有一個(gè)缺點(diǎn)神年,就是不能夠精確的表達(dá),輸入為數(shù)字的時(shí)候行嗤,輸出也應(yīng)該為數(shù)字已日,輸入為字符串的時(shí)候,輸出也應(yīng)該為字符串栅屏。
這時(shí)飘千,我們可以使用重載定義多個(gè) reverse 的函數(shù)類型
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
我們重復(fù)定義了多次函數(shù) reverse堂鲜,前幾次都是函數(shù)定義,最后一次是函數(shù)實(shí)現(xiàn)护奈。在編輯器的代碼提示中缔莲,可以正確的看到前兩個(gè)提示。
注意霉旗,TypeScript 會優(yōu)先從最前面的函數(shù)定義開始匹配酌予,所以多個(gè)函數(shù)定義如果有包含關(guān)系,需要優(yōu)先把精確的定義寫在前面奖慌。
接口Interfaces
對行為的抽象,接口一般首字母大寫松靡。有的編程語言中會建議接口的名稱加上 I
前綴
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
// 定義的變量比接口少了一些屬性是不允許的
let tom: Person = {
name: 'Tom'
};
// 多一些屬性也是不允許的
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
可見简僧,賦值的時(shí)候,變量的形狀必須和接口的形狀保持一致雕欺。
注意岛马,類型檢查器不會去檢查屬性的順序,只要相應(yīng)的屬性存在并且類型也是對的就可以;
有時(shí)我們希望不要完全匹配一個(gè)形狀屠列,那么可以用可選屬性:
可選屬性
interface Person {
name: string;
age?: number;
}
// ok
let tom: Person = {
name: 'Tom'
};
// ok
let tom: Person = {
name: 'Tom',
age: 25
};
// oh no 這時(shí)仍然不允許添加未定義的屬性
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
- 對可能存在的屬性進(jìn)行預(yù)定義
- 捕獲引用了不存在的屬性時(shí)的錯(cuò)誤
任意屬性
需要注意的是啦逆,一旦定義了任意屬性,那么確定屬性和可選屬性都必須是它的子屬性
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
// ok
let tom: Person = {
name: 'Tom',
gender: 'male'
};
// not ok
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// 任意屬性的值允許是 string笛洛,但是可選屬性 age 的值卻是 number夏志,number 不是 string 的子屬性,所以報(bào)錯(cuò)了苛让。
只讀屬性
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
};
tom.id = 9527; // Cannot assign to 'id' because it is a constant or a read-only property.
注意:只讀的約束存在于第一次給對象賦值的時(shí)候沟蔑,而不是第一次給只讀屬性賦值的時(shí)候
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
// ok
tom.id = 89757;
TypeScript具有ReadonlyArray<T>類型,它與Array<T>相似狱杰,只是把所有可變方法去掉了瘦材,因此可以確保數(shù)組創(chuàng)建后再也不能被修改:
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
readonly vs const
最簡單判斷該用readonly還是const的方法是看要把它做為變量使用還是做為一個(gè)屬性。 做為變量使用的話用 const仿畸,若做為屬性則使用readonly食棕。
函數(shù)類型
interface SearchFunc {
(source: string, subString: string): boolean;
}
對于函數(shù)類型的類型檢查來說,函數(shù)的參數(shù)名不需要與接口里定義的名字相匹配
let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
let result = src.search(sub);
return result > -1;
}
函數(shù)的參數(shù)會逐個(gè)進(jìn)行檢查错沽,要求對應(yīng)位置上的參數(shù)類型是兼容的簿晓。 如果你不想指定類型,TypeScript的類型系統(tǒng)會推斷出參數(shù)類型甥捺,因?yàn)楹瘮?shù)直接賦值給了 SearchFunc類型變量抢蚀。 函數(shù)的返回值類型是通過其返回值推斷出來的(此例是 false和true)。 如果讓這個(gè)函數(shù)返回?cái)?shù)字或字符串镰禾,類型檢查器會警告我們函數(shù)的返回值類型與 SearchFunc接口中的定義不匹配皿曲。
let mySearch: SearchFunc;
mySearch = function(src, sub) {
let result = src.search(sub);
return result > -1;
}
可索引的類型
支持兩種索引簽名:字符串和數(shù)字唱逢。
可以同時(shí)使用兩種類型的索引,但是數(shù)字索引的返回值必須是字符串索引返回值類型的子類型屋休。 這是因?yàn)楫?dāng)使用 number來索引時(shí)坞古,JavaScript會將它轉(zhuǎn)換成string然后再去索引對象。 也就是說用 100(一個(gè)number)去索引等同于使用"100"(一個(gè)string)去索引劫樟,因此兩者需要保持一致
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
// NumberArray 表示:只要 index 的類型是 number痪枫,那么值的類型必須是 number
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
類類型
繼承接口
一個(gè)接口可以繼承多個(gè)接口,創(chuàng)建出多個(gè)接口的合成接口
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
混合類型
可以同時(shí)具有上面提到的多種類型叠艳。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
接口繼承類
參考文檔:TS官方文檔