1. 程式人生 > 其它 >前端開發系列043-基礎篇之TypeScript語言特性(三)

前端開發系列043-基礎篇之TypeScript語言特性(三)

title: '前端開發系列043-基礎篇之TypeScript語言特性(三)'
tags:
  - javaScript系列
  - TypeScript系列

categories: []
date: 2017-11-21 18:05:13

本文簡單介紹TypeScript語言中的以下特性:
❏ 解構賦值
❏ 函式介紹
❏ 名稱空間

一、解構&展開

解構賦值語法使得我們可以將值從陣列或者將屬性從物件中提取對應的變數中。下面我們將簡單介紹解構賦值特性在陣列、物件以及函式宣告中的用法。因為解構特性存在被誤用的問題且複雜的表示式常常難以理解,所以建議我們在設計程式碼的時候表示式要儘量保持小而簡單。

解構陣列

//檔案路徑  ../04-解構和展開/01-解構陣列.ts

//[001] 解構陣列簡單介紹
//宣告變數arrM(陣列)
let arrM:number[] = [100,200];

//解構操作
//從arrM陣列中提取索引為0和1的元素賦值給新宣告的變數one和two
let [one,two] = arrM;
console.log("one = " + one);    //one = 100
console.log("two = " + two);    //one = 200

我們通過let [one,two] = arrM這行程式碼,提取了arrM陣列中的前兩個元素分別賦值給新宣告的變數one和two,這行程式碼等價於var one = arrM[0], two = arrM[1];

分別取值並賦值操作。

下面給出程式碼示例,簡單演示瞭如何解構陣列中的部分元素(包括:開頭位置、結尾位置、指定位置等情況)。

//[002] 解構陣列中的部分元素
let arr:string[] = ["one","two","three","four"];
//開始位置
let [first] = arr;
console.log("first = " + first); //first = one

//結尾位置
let [,,,last] = arr;
console.log("last = " + last); //last = four

//指定位置(提取陣列中第一個元素賦值給a,第三個元素賦值給c)
let [a,,c,] = arr;
console.log("a = " + a + " c = " + c); //a = one c = three

解構物件

除了解構陣列外,我們還能夠以同樣的方式來提取物件中的屬性值賦值給宣告的同名變數,下面給出程式碼示例。

//檔案路徑 ../04-解構和展開/02-解構物件.ts

//[001] 解構物件簡單介紹
let objM:any = {author:"文頂頂",address:"wendingding.com",age:18,color:"red"};
let {author,age,address} = objM;

//解構的順序並不重要
//let {author,address,age} = objM;  
console.log("author = "+author);
console.log("age = "+age);
console.log("address = "+address);

//解構物件的操作說白就就是通過指定的key到物件中取值
//let {author,age,address} = objM;程式碼和下面的程式碼等價
var author = objM.author, age = objM.age, address = objM.address;
注意因為物件和陣列組織資料的方式不太一樣,物件在儲存資料的時候是以鍵值對(key-value)的方式處理,所以我們在解構物件的時候,宣告的變數需要和物件中待提取的鍵值對同名,順序關係並不重要

物件在解構的時候要求宣告的變數和提取的物件屬性同名,但是在業務場景中我們可能希望宣告新的變數(即不使用屬性的同名變數),這種情況需要在解構的時候進行重新命名操作。

//[002] 物件解構屬性重新命名
let obj:any = {className:"軟體工程",classNumber:"軟體工程_02",date:"2016-09-01"};

//在解構物件的時候,支援重新命名的方式
let {className,classNumber:classNO,date:classDate} = obj;
console.log("班級名稱 className = " + className); //班級名稱 className = 軟體工程
console.log("班級編號 classNO = " + classNo);     //班級編號 classNO = 軟體工程_02
console.log("開學日期 classDate = " + classDate); //開學日期 classDate = 2016-09-01

上面的程式碼中,在解構物件obj的時候,classNumber被重新命名為classNO,date被重新命名為classDate,需要注意的是重新命名後,classNumber和date均不能使用。此外,物件解構的時候還支援對沒使用關鍵字宣告的變數解構以及靜態型別宣告。

//[003] 支援使用字面量物件來解構賦值

let a:string;
let b:string;
({a , b } = {a:"我是A",b:"我是B"});
console.log("a = " + a);  //a = 我是A
console.log("b = " + b);  //b = 我是B

//[004]支援對變數進行型別宣告
let o = {"des":"描述資訊","num":20};
let {des, num}: {a: string, b: number} = o;
console.log(des);   //描述資訊
console.log(num);   //20
({a , b } = {a:"我是A",b:"我是B"})外層必須要加上(),因為JavaScript在解析的時候通常把 { 起始的語句解析為一個塊。

交換變數和設定預設值

TypeScript中的解構特性為我們提供了一種方便的交換兩個變數值的機制。

//[001] 交換變數
let first:string = "我是第一個_first";
let last:string = "我是最後一個_last";

[first , last] = [last,first];
console.log("first =>" + first);  //first =>我是最後一個_last
console.log("last  =>" + last);   //last  =>我是第一個_first

說明:交換變數在具體處理的時候,會生成一個內部的臨時變數(請參考編譯成JavaScript後的程式碼)。

為了有效防止從陣列或物件中取出一個值為undefined的資料,我們可以在解構的時候為它們設定預設值。

//[002] 解構陣列或物件的時候設定預設值
let [a,b = 20,c] = [10];  //解構操作
console.log(a,b,c);       //10 20 undefined

let color,age;
({color = "yellow",age} = {age:18,color:undefined});
console.log(color);   //yellow
console.log(age);     //18

//解構物件的時候重新命名屬性並設定預設值
var {one:_one = "哈哈哈", two:_two = "嘿嘿"} = {one:"我是one"};
console.log(_one);    // 我是one
console.log(_two);    // 嘿嘿

觀察上面的程式碼,陣列中只有一個元素,物件中的color屬性對應的值為undefined,我們可以通過在解構的程式碼中為變數設定預設值的方式來避免取出的值為undefined,其邏輯是:如果解構提取的值非undefined那麼就使用,如果是undefined,則檢查該變數是否設定預設值,若有則使用

解構用於函式引數

解構除用於物件和陣列外,還能作用於函式引數,如果函式呼叫時候可能預設部分引數,那麼還可以設定預設值增加函式的健壯性。

//檔案路徑 ../04-解構和展開/03-解構用於函式引數.ts

//[001] 解構用於函式引數(陣列)
function x([first, second]: [number, number]) {
    console.log(first);   //1
    console.log(second);  //2
}
x([1,2]);

//[002] 解構用於函式引數(物件)
function f({ a, b }: { a: string, b?: number }): void {
    console.log("執行函式");
    console.log("a的值為: " + a,"b的值為: " + b);
}

f({a:"AAAA",b:20});   //a的值為: AAAA b的值為: 20

/*****=======================******/
//[003]解構用於函式引數並設定預設值
function t({ color, age = 99 }: { color: string, age?: number }): void {
    console.log("color的值為: " + color,"age的值為: " + age);
}

t({color:"red",age:20});  //color的值為: red age的值為: 20
t({color:"red"});         //color的值為: red age的值為: 99

剩餘模式(...語法)

解構特性支援剩餘模式也就是...語法,這種模式能夠幫助我們將剩餘的陣列元素或者是物件內容賦值給一個變數。

//檔案路徑 ../04-解構和展開/04-剩餘模式.ts

//將陣列中除了前兩個之外的元素(剩餘的元素)賦值給c
let [a,b, ...c] = [1, 2, 3, 4, 8];
console.log(a); // 1
console.log(b); // 2
console.log(c); // [3,4,8]

//剩餘模式用於物件解構
let o = {testA:"我是A",testB:"我是B",testC:"我是C"};
let { testA, ...passthrough } = o;
console.log(testA);       // 我是A
console.log(passthrough); // {testB:"我是B",testC:"我是C"}

//報錯: A rest parameter or binding pattern may not have a trailing comma.
//let { testB, ...Other,} = o;  //錯誤的演示
剩餘元素必須是陣列的最後一個元素,如果剩餘元素右側有一個逗號,則會丟擲SyntaxError錯誤。

陣列和物件的展開操作

展開操作與解構操作剛好相反。展開操作它允許我們將陣列展開為另一個數組,或將物件展開為另一個物件。

在運算元組的時候展開操作會對指定的陣列執行淺拷貝,它們本身並不會因展開操作被修改。物件的展開操作和陣列相比略顯不同,雖然它和陣列展開操作一樣也是從左至右進行處理的,但因為其處理的結果仍然是物件,而物件中要求所有的key都具有唯一性,所以這也就意味著在物件的展開操作可能存在屬性覆蓋的問題

//檔案路徑 ../04-解構和展開/06-陣列和物件的展開操作.ts

//[001] 陣列的展開操作
let one:number[] = [1, 2];
let two:number[] = [3, 4];
let resultArrM = [0, ...one, ...two, 5];
console.log(one);   //[1,2]
console.log(two);   //[3,4]
console.log(resultArrM);  //[0,1,2,3,4,5]

//[002] 物件的展開操作
let defaultObj = { name: "zhangsan", age: 18,color: "yellow" };
let targetObj = { ...defaultObj, des:"描述資訊",name: "文頂頂" };
console.log(defaultObj);  //{ name: "zhangsan", age: 18,color: "yellow" };
console.log(targetObj);   //{ name: "文頂頂", age: 18,color: "yellow",des:"描述資訊"};

注意 物件的展開操作存在限制情況

>首先,它僅包含物件自身的可列舉屬性(不包括原型成員)。
>其次,TypeScript編譯器不允許展開泛型函式上的型別引數。

二、函式說明

TypeScript語言中的函式在JavaScript函式基礎上增加了引數型別宣告、返回值型別指定、箭頭函式等特徵,這裡簡單介紹。

指定引數和返回值型別

同JavaScript一樣,我們可以用函式宣告的方式或函式表示式的方式來建立得到一個函式物件,在定義(宣告)函式的時候,我們可以選擇性的給函式的引數加上型別,也可以指定函式返回值的型別。如果指定了型別,那麼TypeScript會進行型別檢查。

//檔案路徑 ../05-函式說明/01-函式簡單說明.ts

//[001] javaScript風格的函式(宣告函式)
//下面的程式碼以“函式宣告”的方式建立了add函式
//add函式擁有兩個引數,形參a和形參b
//add函式的作用為對傳入的兩個引數進行+運算子計算,並返回結果
//如果設計的該函式只能接收number型別的兩個引數,那麼函式體中還應該對引數型別進行校驗
function add(a,b){
  return a + b;
}

//[002] TypeScript中的函式(宣告函式)
//f1函式接收兩個引數,並指定了引數的型別均為number
function f1(a:number,b:number)
{
  return a + b;
}
//函式呼叫
let result = f1(10,20);    //返回結果為30

//報錯:Argument of type '"字串"' is not assignable to parameter of type 'number'.
//result = f1(10,"字串");  錯誤的演示


//[003] TypeScript中的函式(匿名函式|函式表示式)
//f2函式的指定了返回值型別為number
let f2 = function f(a:number,b:number) : number
{
  return a + b;
}

f2(100,200);    //300

箭頭函式

TypeScript遵循ES6的規範,提供了表示函式的另外一種方式即箭頭函式。箭頭函式最典型的特徵是在建立函式的時候在函式返回值型別的後面加上箭頭(=>)操作符而並不使用function關鍵字。

//檔案路徑 ../05-函式說明/02-箭頭函式.ts

/*
* 這是TypeScript中典型的普通函式,下面提供等價的箭頭函式程式碼
let add = function (a:number,b:number) : number{
  return a + b;
}
*/

let add = (a:number,b:number) :number => {
  return a + b;
}

add(10,200);  //210

接下來我們給出一個高階函式的例子,所謂高階函式指的是引數或返回值也是函式的函式。

let sume = (a:number,b:number,callBack:(result:number) => void) : void =>{
  callBack(a +b);
}

sume(10,20,(result:number) : void =>{
  console.log("result = " + result);
});

//輸出結果為:result = 30

程式碼稍微有點複雜,簡單來說,我們聲明瞭兩個函式,其中一個是sume匿名函式,另外一個是呼叫sume時作為引數傳遞給sume的匿名函式,這兩個函式均使用箭頭函式表示法

作為引數傳遞給sume的匿名函式需要接受一個number型別的引數(形參為result),沒有返回值。而sume這個函式需要接受三個引數,其中前兩個引數是number型別的值(形參a和b), 第三個引數為函式(形參為callBack)。

需要注意的是(result:number) => void作為引數的匿名函式的型別。

通過下面的圖示,可以加深大家對箭頭函式的理解。

(函式宣告圖示)

(函式呼叫圖示)

三、名稱空間

名稱空間,在以前也稱為內部模組。名稱空間主要用來把一些存在內在聯絡的變數、類、介面以及物件等程式碼組織起來。使用名稱空間來組織程式碼,可以讓程式碼的整體結構更清晰,而且通過把某些相關聯的程式碼包裹起來放在一個名稱空間內,而不是暴露在全域性名稱空間中可以避免命名衝突。

在TypeScript中,可以通過namespace關鍵字來宣告名稱空間,其語法格式為namespace 名稱空間的名稱{ //.......被包裹的程式碼塊}。名稱空間中的程式碼無法被外界直接訪問,如果外界需要訪問可以通過export關鍵字來暴露介面。

let a:string = "全域性作用域中的變數";

namespace WenDemo{

  let a:string = "名稱空間WenDemo中的變數a";

  //介面
  interface PersonInterface {
      name: string;
      getInfo();
  }

  //實現了PersonInterface介面的Person類
  //通過export關鍵字暴露介面
  export class Person implements PersonInterface{
    name:string;
    constructor(name:string){
      this.name = name;
    }
    getInfo(){      //getInfo方法
      return "姓名:" + this.name;
    }
  }
}

//錯誤的演示
//let p = new Person("wendingding.com");

//如果把相關程式碼包裹到指定名稱空間中,那麼需要通過包裹的名稱空間來訪問暴露的介面
let p = new WenDemo.Person("wendingding.com");
console.log(p);   //{name:"wendingding.com"}
console.log(a);   //全域性作用域中的變數

把上面TypeScript程式碼編譯為JavaScript的程式碼,我們可以發現名稱空間實現的機制非常簡單,只是使用了立即呼叫函式(閉包)包裹程式碼而已,下面貼出JavaScript對應的程式碼。

var a = "全域性作用域中的變數";
var WenDemo;
(function (WenDemo) {
    var a = "名稱空間WenDemo中的變數a";
    //實現了PersonInterface介面的Person類
    //通過export關鍵字暴露介面
    var Person = /** @class */ (function () {
        function Person(name) {
            this.name = name;
        }
        Person.prototype.getInfo = function () {
            return "姓名:" + this.name;
        };
        return Person;
    }());
    WenDemo.Person = Person;
})(WenDemo || (WenDemo = {}));
//錯誤的演示
//let p = new Person("wendingding.com");
//如果把相關程式碼包裹到指定名稱空間中,那麼需要通過包裹的名稱空間來訪問暴露的介面
var p = new WenDemo.Person("wendingding.com");
console.log(p); //{name:"wendingding.com"}
console.log(a); //全域性作用域中的變數

備註:該文章所有的示例程式碼均可以點選在Github託管倉庫獲取