之前介紹了一下TypeScript的基本配置和基本類型,接下來(lái)就開(kāi)始一些跟之前原生JS區(qū)別很大的內(nèi)容.
1.接口 interfaces
首先JS是沒(méi)有接口的這個(gè)概念的,但是接口還是在JAVA,OC里都是比較常見(jiàn)的概念.JAVA接口和OC的.h文件聲明接口,就是一些方法的集合,只有方法的聲明,沒(méi)有對(duì)應(yīng)方法的實(shí)現(xiàn),這些方法可能在不同的地方被實(shí)現(xiàn).在JS里只能模擬這種接口
2.鴨子類型 duck typing
動(dòng)態(tài)類型語(yǔ)言對(duì)變量類型的寬容給實(shí)際編碼帶來(lái)了很大的靈活性,無(wú)需檢測(cè),也不需要考慮變量是否擁有這個(gè)方法,隨意去調(diào)用任何的方法,這些便利都得益于鴨子類型.
鴨子類型這個(gè)概念源自一個(gè)諺語(yǔ)"當(dāng)看到一只鳥走起來(lái)像鴨子、游泳起來(lái)像鴨子阴孟、叫起來(lái)也像鴨子晌纫,那么這只鳥就可以被稱為鴨子",比如現(xiàn)在有兩個(gè)變量,一個(gè)是數(shù)組arr,另外一個(gè)變量也能能使用length,也能使用下標(biāo)來(lái)獲取值,那就可以認(rèn)為另外一個(gè)變量也是一個(gè)數(shù)組.
再舉一個(gè)例子:一只小老鼠被貓盯上了,情急之下永丝,它學(xué)了狗叫锹漱,貓撤了之后,小老鼠的媽媽不無(wú)感嘆的對(duì)它說(shuō):看吧慕嚷,我讓你學(xué)的這門兒外語(yǔ)多么重要啊.這里也使用了鴨子類型,當(dāng)貓抓老鼠的時(shí)候,老鼠學(xué)了狗叫,讓貓以為那是條狗,所以逃跑.
鴨子類型總得來(lái)說(shuō),關(guān)注的不是對(duì)象的類型本身哥牍,而是它是如何使用的.
3.JS的接口
通過(guò)鴨子類型實(shí)現(xiàn)接口,下面的代碼是在網(wǎng)上摘錄下來(lái)得,大家可以借鑒一下
//一: 接口類 Class Interface ==>實(shí)例化N多個(gè)接口
/**
* 接口類需要2個(gè)參數(shù)
* 參數(shù)1: 接口的名字 (string)
* 參數(shù)2: 接受方法名稱的集合(數(shù)組) (array)
*/
var Interface = function(name , methods){
//判斷接口的參數(shù)個(gè)數(shù)
if(arguments.length != 2){
throw new Error('argument.length must be 2');
}
this.name = name;
this.methods = [];//定義一個(gè)內(nèi)置的空數(shù)組對(duì)象 等待接受methods里的元素(方法名字)
for(var i = 0, len = methods.length ;i < len; i++ ){
if(typeof methods[i] != 'string'){
throw new Error('methods.type must be string');
}
this.methods.push(methods[i]);
}
}
// 二: 準(zhǔn)備工作:
// 1 實(shí)例化接口對(duì)象
var CompositeInterface = new Interface('CompositeInterface' , ['add' , 'remove']);
var FormItemInterface = new Interface('FormItemInterface' , ['update','select']);
// CompositeImpl implements CompositeInterface , FormItemInterface
// 2 具體的實(shí)現(xiàn)類
var CompositeImpl = function(){
}
// 3 實(shí)現(xiàn)接口的方法implements methods
CompositeImpl.prototype.add = function(){
alert('add...');
}
CompositeImpl.prototype.update = function(){
alert('update...');
}
CompositeImpl.prototype.select = function(){
alert('select...');
}
CompositeImpl.prototype.remove = function(){
alert('remove...');
}
// 三:檢驗(yàn)接口里的方法
// 如果檢驗(yàn)通過(guò) 不做任何操作 不通過(guò):瀏覽器拋出error
// 這個(gè)方法的目的 就是檢測(cè)方法的
Interface.ensureImplements = function(object){
// 如果檢測(cè)方法接受的參數(shù)小于2個(gè) 參數(shù)傳遞失敗!
if(arguments.length < 2){
throw new Error('Interface.ensureImplements method constructor arguments must be >= 2!');
}
// 獲得接口實(shí)例對(duì)象
for(var i = 1; i < arguments.length;i++){
var instanceInterface = arguments[i];
if(instanceInterface.constructor != Interface){
throw new Error('the arguments constructor not be Interface Class');
}
// 循環(huán)接口實(shí)例對(duì)象里面的每一個(gè)方法
for(var j=0;j < instanceInterface.methods.length;j++){
var methodName = instanceInterface.methods[j];
// object[key] 就是方法
if(!object[methodName] || typeof object[methodName] != 'function'){
throw new Error("the method name '" + methodName + "' is not found !");
}
}
}
}
// 最后使用
var c1 = new CompositeImpl();
Interface.ensureImplements(c1,CompositeInterface,FormItemInterface);
c1.add();
4.TypeScript接口
TypeScript里,接口的作用就是為類型命名和代碼體統(tǒng)的一套契約,通過(guò)契約來(lái)判斷屬性是否存在,也可以認(rèn)為接口就是一個(gè)自定義的類型
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue){
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
LabelledValue相當(dāng)于接口的名,在函數(shù)printLabel里,需要傳一個(gè)參數(shù),指定參數(shù)類型就是接口類型,就是LabelledValue類型,然后在函數(shù)里調(diào)用傳進(jìn)去對(duì)象的屬性,如果調(diào)用的是size,就會(huì)在文件里報(bào)錯(cuò),因?yàn)樵趯?duì)應(yīng)接口里,并沒(méi)有對(duì)應(yīng)屬性.接口里的屬性只要存在并且類型正確就可以.
5.可選屬性
在接口里,有些屬性不一定是必須的,有些屬性可能在某些條件下存在,這時(shí)候,為了避免在書寫,對(duì)這樣的屬性,在可選的屬性名字定的的后面加一個(gè)?符號(hào)
interface SquareConfig {
color?: string;
width?: number;
}
6.只讀屬性
屬性的設(shè)置前,需要在屬性名前,用readonly來(lái)修飾屬性,通過(guò)這個(gè)類型構(gòu)建出來(lái)的對(duì)象,屬性值不允許變化
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScrip還有一個(gè)類型ReadonlyArray<T>,這個(gè)類型跟之前說(shuō)過(guò)的數(shù)組類型相似,只是所有對(duì)數(shù)組操作的方法都去掉了
readonly和const很相似,區(qū)別就在于做為變量使用的話用const
,若做為屬性則使用readonly.
7.函數(shù)類型
除了帶屬性的普通對(duì)象之外,接口也可以描述函數(shù)類型.
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
if (result == -1) {
return false;
} else {
return true;
}
}
接口里括號(hào)里是函數(shù)類型的參數(shù),但是參數(shù)名可以與接口里定義的名不匹配,冒號(hào)后是返回值的類型,這個(gè)類型要相同,否則編譯器會(huì)有警告
8.可索引的類型
這種接口的使用類似數(shù)組和對(duì)象,通過(guò)索引找到對(duì)應(yīng)值,接口支持兩種類型的索引,數(shù)字和字符串
// 數(shù)組元素是數(shù)字
interface StringArray {
[index: number]: number;
}
var myArray: StringArray = [1, 2];
console.log(myArray[0]);
// 數(shù)組是字符串
interface StringArray {
[index: number]: string;
}
var myArray: StringArray = ["Bob", "Fred"];
console.log(myArray[0]);
接口里方括號(hào)里index代表了索引,冒號(hào)代表索引類型,方括號(hào)外的number代表了取值的類型.如果接口里同時(shí)使用兩種類型的索引,數(shù)字索引的返回值必須是字符串索引返回值類型的子類型,字符串對(duì)應(yīng)的是父類,數(shù)字對(duì)應(yīng)的是子類
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
interface NotOkay {
[x: number]: Dog;
[x: string]: Animal;
}
這是正確的寫法,Dog這個(gè)類是Animal這個(gè)類的子類,所以number對(duì)應(yīng)的是子類Dog,string對(duì)應(yīng)的是父類Animal.當(dāng)使用 number來(lái)索引時(shí)喝检,JavaScript會(huì)將它轉(zhuǎn)換成string然后再去索引對(duì)象.
index的類型可以用來(lái)面熟常用的數(shù)組和dictionary (字典),需要所有屬性和返回值匹配
interface Dictionary {
[index: string]: number;
length: number; // 可以嗅辣,length是number類型
name: string // 錯(cuò)誤,`name`的類型不是索引類型的子類型
}
可以理解為number代表了string的子類型,所以不會(huì)報(bào)錯(cuò).最后還能將index設(shè)置成只讀,只需要加上readonly就可以了
9.類類型
類類型就是明確用來(lái)強(qiáng)制一個(gè)類去符合某種契約
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
聲明了一個(gè)接口,一個(gè)日期類型的屬性,還有一個(gè)setTime的方法,這個(gè)方法在類里實(shí)現(xiàn).在類里方法和屬性分為靜態(tài)部分和實(shí)例部分,對(duì)應(yīng)接口來(lái)講,只能對(duì)實(shí)例部分進(jìn)行類型檢查
interface ClockConstructor {
new (hour: number, minute: number);
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
這里就會(huì)報(bào)錯(cuò),原因在于借口創(chuàng)建了一個(gè)類的實(shí)例,也就是new了一個(gè)對(duì)象,這個(gè)對(duì)象在創(chuàng)建的時(shí)候需要使用類的構(gòu)造器,也就是constructor,它就是類的靜態(tài)部分的內(nèi)容,接口檢測(cè)不到,所以會(huì)報(bào)錯(cuò).因此,在操作類的靜態(tài)部分,可以定義兩個(gè)接口
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick();
}
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");
}
}
let digital = createClock(DigitalClock, 12, 17);
通過(guò)兩個(gè)接口,ClockConstructor接口調(diào)用ClockInterface,ClockInterface接口調(diào)用tick方法,返回值就是一個(gè)新的對(duì)象,從而完成靜態(tài)部分內(nèi)容的調(diào)用