var声明
var的作用域是函数作用域,(类似全局变量),不建议使用
let声明
let的作用域是块级作用域(块级作用域变量在包含它们的块或者for循环之外是不能访问的)
块级作用域变量的另一个特点是:它们不能在被声明之前读或写(虽然这些变量始终“存在”于它们的作用域里,但直到声明它的代码之前的区域都属于 暂时性死区 )
a++; // illegal to use 'a' before it's declared;
let a;
不过我们仍然可以在一个块级作用域变量被声明之前获取它。只不过不能在变量声明之前去调用那个函数
function foo() {
// okay to capture 'a'
return a;
}
// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();
let a;
重定义及屏蔽
用var声明变量时,它不在乎你声明多少次,你只会得到1个
而用let声明变量,不能在1个作用域内声明同名的参数
let x = 10;
let x = 20; // 错误,不能在1个作用域里多次声明`x`
不过在嵌套作用域内,可以声明与上一层中同名的变量,但与上一层的变量不是同一个变量,这种行为称为 屏蔽(类似Java中同名的成员变量和局部变量)
块级作用域变量的获取
每进入一个作用域时,它会创建一个 变量的环境,就算作用域内的代码已经执行完毕,这个环境与其捕获的变量依然存在
关于var setTimeOut的例子
for (var i = 0; i < 10; i++) {
// capture the current state of 'i'
// by invoking a function with its current value
(function(i) {
setTimeout(function() { console.log(i); }, 100 * i);
})(i);
}
用let声明变量,不仅在循环里引入了一个新的环境变量,而且针对每次迭代都会创建一个新的作用域
for (let i = 0; i < 10 ; i++) {
setTimeout(function() {console.log(i); }, 100 * i);
}
const声明
const变量被赋值后不能再改变,它拥有与 let相同的作用域规则,但是不能对它们重新赋值
不过实际上const变量的内部状态是可以修改的,不过TypeScript允许将对象设置为只读(这在 接口 中有详细说明)
解构
解构数组
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
解构作用域已声明的变量会更好
// swap variables
[first, second] = [second, first];
作用于函数参数
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f(input);
可以在数组中使用…语法创建剩余变量
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
对象解构
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
通过o.a和o.b创建了a 和 b, (如果不需要c可以忽略它)
可以在对象里使用…语法创建剩余变量
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
也可以给属性不同的名字
let { a: newName1, b: newName2 } = o;
可以将a: newName1 读作 “a 作为 newName1”
这里的冒号 不是 指示类型的,如果你想自定它的类型,仍然需要在其后写上完整的模式
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 {
// ...
}
但通常情况下更多的是指定默认值,解构默认值有些棘手。 首先,你需要在默认值之前设置其格式。
function f({ a="", b=0 } = {}): void {
// ...
}
f();
其次,你需要知道在解构属性上给予一个默认或可选的属性用来替换主初始化列表。 要知道 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
展开
展开操作符与解构相反,允许将一个数组展开成为另一个数组,或将一个对象展开为另一个对象。
let first = [1, 2];
let second = [3, 4];
let bothPlus = [0, ...first, ...second, 5];
这会令bothPlus的值为[0, 1, 2, 3, 4, 5]。 展开操作创建了 first和second的一份 浅拷贝 。 它们不会被展开操作所改变。
也可以展开对象
let defaults = { food: "spicy", price: "$$", ambiance: "noisy" };
let search = { ...defaults, food: "rich" };
search的值为{ food: “rich”, price: “$$”, ambiance: “noisy” }。从左至右处理,出现在展开对象后面的属性会 覆盖 前面的属性。
不过对象展开还要一些限制。
首先,它仅包含对象 自身的可枚举属性,大体上是说当你展开一个对象实例时,你会 丢失其方法
class C {
p = 12;
m() {
}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
其次,TypeScript编译器不允许展开泛型函数上的类型参数。