原文地址 www.tslang.cn
let 声明
块作用域
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。 虽然这些变量始终 “存在” 于它们的作用域里,但在直到声明它的代码之前的区域都属于 暂时性死区。 它只是用来说明我们不能在 let
语句之前访问它们,幸运的是 TypeScript 可以告诉我们这些信息。
a++; // illegal to use 'a' before it's declared;
let a;
注意一点,我们仍然可以在一个拥有块作用域变量被声明前_获取_它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为 ES2015,现代的运行时会抛出一个错误;然而,现今 TypeScript 是不会报错的。
function foo() {
// okay to capture 'a'
return a;
}
// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();
let a;
块级作用域变量的获取
注释:一个函数的作用域在其定义时决定了。
在我们最初谈及获取用var
声明的变量时,我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲,每次进入一个作用域时,它创建了一个变量的 环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。
function theCityThatAlwaysSleeps() {
let getCity;
if (true) {
let city = "Seattle";
getCity = function() {
return city;
}
}
return getCity();
}
因为我们已经在city
的环境里获取到了city
,所以就算if
语句执行结束后我们仍然可以访问它。
当let
声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 _每次迭代_都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout
例子里我们仅使用let
声明就可以了。
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
会输出与预料一致的结果:
0
1
2
3
4
5
6
7
8
9
注释:循环进行了特殊处理,下例中虽然也创建了新的作用域,但是并没有在这个作用域创建一个 a 的副本,所以 setTimeout 后打印的结果是 222
function fun() {
let a = '111';
if (true) {
setTimeout(() => {
console.log(a) // 222
},1000)
}
a = '222'
console.log(a) // 222
}
fun()
const 声明
它们拥有与 let
相同的作用域规则,但是不能对它们重新赋值。
解构
解构数组
最简单的解构莫过于数组的解构赋值了:
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
你可以在数组里使用...
语法创建剩余变量:
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
当然,由于是 JavaScript, 你可以忽略你不关心的尾随元素:
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
或其它元素:
let [, second, , fourth] = [1, 2, 3, 4];
对象解构
你也可以解构对象:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
这通过 o.a
and o.b
创建了 a
和 b
。 注意,如果你不需要 c
你可以忽略它。
就像数组解构,你可以用没有声明的赋值:
let a,b;
({ a, b } = { a: "baz", b: 101 });
注意,我们需要用括号将它括起来,因为 Javascript 通常会将以 {
起始的语句解析为一个块。
你可以在对象里使用...
语法创建剩余变量:
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
属性重命名
你也可以给属性以不同的名字:
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;
默认值
默认值可以让你在属性为 undefined 时使用缺省值:
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
现在,即使 b
为 undefined , keepWholeObject
函数的变量 wholeObject
的属性 a
和 b
都会有值。
函数声明
解构也能用于函数声明。 看以下简单的情况:
type C = { a: string, b?: number }
function f({ a, b }: C): void {
// ...
}
其次,你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 C
的定义有一个 b
可选属性:
注释:解构的对象下的属性有默认值,被推断为这个属性可有可无,有时和默认值的类型一致
注释:解构的对象本身有默认值,被推断为这个对象可有可无。如果这个默认值包含属性的默认值,这个属性会被推断为必传属性,且类型和默认值一致
function f({ a, b = 0 } = { a: "" }): void {
// ...
}
f({ a: "yes" }); // ok, default b = 0
f(); // ok, default to {a: ""}, which then defaults b = 0
f({}); // error, 'a' is required if you supply an argument
展开
对象展开还有其它一些意想不到的限制。 首先,它仅包含对象 自身的可枚举属性。 大体上是说当你展开一个对象实例时,你会丢失其方法:
注释:可枚举属性包括了继承来的属性
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
其次,TypeScript 编译器不允许展开泛型函数上的类型参数。 这个特性会在 TypeScript 的未来版本中考虑实现。
注释:以下是在 4.1.2 中的测试
function a<T extends { a:string;b:number }>({ a, b }: T ): Partial<T>{
return {a,b} as T
}
a<{a:string,b:number,c:boolean}>({a:'1',b:1,c:true})
function b<T>({ a, b }: { a: T, b: number; }): T{
return a
}
b<string>({a:'1',b:2})