本文简单介绍TypeScript语言中的以下特性:
❏ 解构赋值
❏ 函数介绍
❏ 命名空间

解构&展开

解构赋值语法使得我们可以将值从数组或者将属性从对象中提取对应的变量中。下面我们将简单介绍解构赋值特性在数组、对象以及函数声明中的用法。因为解构特性存在被误用的问题且复杂的表达式常常难以理解,所以建议我们在设计代码的时候表达式要尽量保持小而简单

解构数组

1
2
3
4
5
6
7
8
9
10
11
//文件路径  ../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];分别取值并赋值操作。

下面给出代码示例,简单演示了如何解构数组中的部分元素(包括:开头位置、结尾位置、指定位置等情况)。

1
2
3
4
5
6
7
8
9
10
11
12
13
//[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

解构对象

除了解构数组外,我们还能够以同样的方式来提取对象中的属性值赋值给声明的同名变量,下面给出代码示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//文件路径 ../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)的方式处理,所以我们在解构对象的时候,声明的变量需要和对象中待提取的键值对同名,顺序关系并不重要

对象在解构的时候要求声明的变量和提取的对象属性同名,但是在业务场景中我们可能希望声明新的变量(即不使用属性的同名变量),这种情况需要在解构的时候进行重命名操作。

1
2
3
4
5
6
7
8
//[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均不能使用。此外,对象解构的时候还支持对没使用关键字声明的变量解构以及静态类型声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
//[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中的解构特性为我们提供了一种方便的交换两个变量值的机制。

1
2
3
4
5
6
7
//[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的数据,我们可以在解构的时候为它们设置默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
//[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,则检查该变量是否设置默认值,若有则使用

解构用于函数参数

解构除用于对象和数组外,还能作用于函数参数,如果函数调用时候可能缺省部分参数,那么还可以设置默认值增加函数的健壮性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//文件路径 ../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

剩余模式(…语法)

解构特性支持剩余模式也就是...语法,这种模式能够帮助我们将剩余的数组元素或者是对象内容赋值给一个变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//文件路径 ../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都具有唯一性,所以这也就意味着在对象的展开操作可能存在属性覆盖的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//文件路径 ../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会进行类型检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//文件路径 ../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关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//文件路径 ../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

接下来我们给出一个高阶函数的例子,所谓高阶函数指的是参数或返回值也是函数的函数。

1
2
3
4
5
6
7
8
9
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关键字来暴露接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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对应的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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托管仓库获取