解构
Another TypeScript已经可以解析其它 ECMAScript 2015 特性了。 完整列表请参见
。 本章,我们将给出一个简短的概述。
数组解构
let a, b, rest;[a, b] = [10, 20];console.log(a); // 10console.log(b); // 20// ...用剩余变量创建一个新数组[a, b, ...rest] = [10, 20, 30, 40, 50];console.log(a); // 10console.log(b); // 20console.log(rest); // [30, 40, 50]// 语法糖,等同于let { a, b } = { a: 10, b: 20 }({ a, b } = { a: 10, b: 20 });console.log(a); // 10console.log(b); // 20// Stage 4(finished) proposal({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});console.log(a); // 10console.log(b); // 20console.log(rest); // {c: 30, d: 40}
解构作用于已声明的变量会更好:
// 交换两个变量的值[first, second] = [second, first];// 作用于函数的参数function f([first, second]: [number, number]) {console.log(first);console.log(second);}f(input);
当然,由于是JavaScript, 你可以忽略你不需要的元素:
let [first] = [1, 2, 3, 4];console.log(first); // outputs 1let [, second, , fourth] = [1, 2, 3, 4];console.log(second,fourth) // 2 4
对象解构
let o = {a: "foo",b: 12,c: "bar"};let { a, b } = o;
就像数组解构,你可以用没有声明的赋值:
({ a, b } = { a: "baz", b: 101 });// 语法糖 等同于 let { a, b } = { a: "baz", b: 101 }
注意,我们需要用括号将它括起来,因为Javascript通常会将以 { 起始的语句解析为一个块。
你也可以在对象里使用 … 语法创建剩余变量:
let o = {a: "foo",b: 12,c: "bar"};let { a, ...passthrough } = o; // a = "foo",passthrough = { b: 12, c: "bar" }
属性重命名
你也可以给属性以不同的名字:
let { a: newName1, b: newName2 } = o;
这里的语法开始变得混乱。 你可以将 a: newName1 读做 “a 作为 newName1”。 方向是从左到右,好像你写成了以下样子:
let newName1 = o.a;let newName2 = o.b;
令人繁琐的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
let {a, b}: {a: string, b: number} = o;
默认值
像Python一样,你可以给函数的参数设置默认值,这样当该参数的值为null或undefined的时候,就会使用默认值。
function keepWholeObject(wholeObject: { a: string, b?: number }) {let { a, b = 1001 } = wholeObject;}function go(href = "https://localhost") {console.log(href)}
不难发现,重命名,默认值和类型注解所用的都是 : 号表示,所以在函数中即使是简单的解构,阅读起来也是非常复杂的,谨慎使用。
带有解构的函数声明 (很tm复杂)
解构也能用于函数声明。 看以下简单的情况:
type C = { a: string, b?: number }function f({ a, b }: C): void {// ...}
但是,通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式。
function f({ a = "a", b = 0 } = {}): void {// ...console.log(a);console.log(b):}// 其中{ a="a", b = 0 } 表示对传入的对象进行解构,从中拿到属性 a 和 b ,如果a和b都不存在,则设置为默认值 "a" 和 0, 比如传入一个对象{a:"hello",b:10},则最后输出的就是hello和10// = {} 表示如果没有参数传进来,则将整个参数的默认值设为空对象 {},但由于a和b都设置了默认值,所以最后还是用{ a: "a", b: 0 }作为最后的参数f() // a 0
上面的代码使用了类型推断,将在本手册后文介绍。
如果,在参数解构后,你还可以设置一个默认值,如下中 { a, b = 0 } = { a: “” } 一样,如果传入的对象中没有 a这个属性,则最终会把最后的默认值 { a: “” } 作为a的值。注意:参数最终的默认值会和参数内的默认值进行合并,并且同名的类型会被参数默认值覆盖,如果没有同名则不会覆盖。
比如下例中:我们在参数内给b设置了默认值,而参数默认值中没有设置,则最后会将 { b: 0 } 和 { a: “” } 进行合并,得到最终的默认值就是 { a: “”, b: 0 },就是这样。
function f({ a, b = 0 } = { a: "" }): void {// ...console.log(a);console.log(b):}f({ a: "yes" }); // b默认为0,输出 yes 0f(); // 允许,在参数为空时,有一个默认值 {a:""},同时b会被初始化为0,最终参数为{a:"",b:0}f({}); // 错误!,因为a没有在参数列表中设置默认值,所以a是必选项,ts中编辑器报错。
要小心使用解构。 从前面的例子可以看出,就算是最简单的解构表达式也是难以理解的。 尤其当存在深层嵌套解构的时候,就算这时没有堆叠在一起的重命名,默认值和类型注解,也是令人难以理解的。 解构表达式要尽量保持小而简单。 你自己也可以直接使用解构将会生成的赋值表达式。
展开
也就是 … 语法,它允许你将一个数组展开为另一个数组,或将一个对象展开为另一个对象。 例如:
let first = [1, 2];let second = [3, 4];let bothPlus = [0, ...first, ...second, 5]; // [0, 1, 2, 3, 4, 5]
展开操作创建了 first和second的一份浅拷贝。 它们不会被展开操作所改变。
展开对象:
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };let search = { ...defaults, food: "rich" };
像数组展开一样,它是从左至右进行处理,但结果仍为对象。 这就意味着出现在展开对象后面的属性会覆盖前面的属性。 也就是说,上例中的 food: “rich” 将会覆盖展开中的 food: “spicy”。因此,要注意展开的位置和时机。
对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 简单来说,就是只能展开复制基本类型和复杂类型数据,而不包括函数和方法。
class C {p = 12;m() {}}let c = new C();let clone = { ...c };clone.p;clone.m(); // 错误,展开会去除方法和函数
