变量声明
let
和const
是JavaScript里相对较新的变量声明方式。 像我们之前提到过的,let
在很多方面与var
是相似的,但是可以帮助大家避免在JavaScript里的常见一些问题。const
是对let
的一个增强,它能阻止对一个变量再次赋值。
1、var 声明
通过var
关键字定义JavaScript变量。
var a=10;
这里定义了一个名为a,值为10的变量;也可以在函数内部定义变量:
function f(){
var mes = "Hello world!";
return mes;
}
可以在其他函数内部访问相同的变量。
function f(){
var a = 10;
return function g(){
var b = a + 1;
return b;
}
}
var g = f();
g(); //return 11;
g可以获取到f函数里定义的a变量。每当g被调用时,它都可以访问到f里的a变量。
即当g在f已经执行完后才被调用,它仍然可以访问以及修改a。
function f(){
var a = 1;
a = 2;
var b = g();
a = 3;
return b;
function g(){
return a;
}
}
f(); //return 2;
作用域规则
对于熟悉其它语言的人来说,var声明有些奇怪的作用域规则。
function f(shouldInittialize:boolean){
if (shouldInitialize){
var x=10;
}
return x;
}
f(true); //returns '10'
f(false); //returns 'undefined'
变量 x是定义在if语句里面,但是我们却可以在语句的外面访问它。
因为 var
声明可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问,包含它的代码块对此没有什么影响。
有些人称此为var作用域或 函数作用域。 函数参数也使用函数作用域。
这些作用域规则可能会引发一些错误。 其中之一就是,多次声明同一个变量并不会报错:
function sumMatrix(matrix: number[][]){
var sum = 0;
for(var i = 0;i < matrix.length;i++){
var cur = matrix[i];
for (var i = 0;i < cur.length;i++){
sum += cur[i];
}
}
return sum;
}
这里很容易看出一些问题,里层的for
循环会覆盖变量i
,因为所有i都引用相同的函数作用域内的变量。 有经验的开发者们很清楚,这些问题可能在代码审查时漏掉,引发无穷的麻烦。
let 声明
已经知道了var
存在一些问题,这恰好说明了为什么用let
语句来声明变量。 除了名字不同外, let
与var
的写法一致。
let hello = "Hello world!"
主要的区别不在语法上,而是语义。
块作用域
当用let
声明一个变量,它使用的是词法作用域或块作用域。
不同于使用 var
声明的变量那样可以在包含它们的函数外访问,块作用域变量在包含它们的块或for
循环之外是不能访问的。
function f(input: boolean) {
let a = 100;
if (input) {
// Still okay to reference 'a'
let b = a + 1;
return b;
}
// Error: 'b' doesn't exist here
return b;
}
定义了2个变量a和b。 a的作用域是f函数体内,而b的作用域是if语句块里。
在catch
语句里声明的变量也具有同样的作用域规则。
try {
throw "oh no!";
}
catch (e) {
console.log("Oh well.");
}
// Error: 'e' doesn't exist here
console.log(e);
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。
虽然这些变量始终“存在”于它们的作用域里,但直到声明它的代码之前的区域都属于暂时性死区。
它只是用来说明我们不能在 let语句之前访问它们,幸运的是TypeScript可以告诉我们这些信息。
a ++; //在声明之前使用'a'是违法的;
let a;
注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。
只是我们不能在变量声明前去调用那个函数。
如果生成代码目标为ES2015,现代的运行时会抛出一个错误;然而,现今TypeScript是不会报错的。
function foo() {
// okay to capture 'a'
return a;
}
// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();
let a;
重定义及屏蔽
使用var
声明时,它不在乎你声明多少次;你只会得到1个。
function f(x) {
var x;
var x;
if (true) {
var x;
}
}
let x = 10;
let x = 20;// 错误,不能在1个作用域里多次声明`x`
并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告。
function f(x) {
let x = 100; // error: 干扰参数说明
}
function g() {
let x = 100;
var x = 100; // error: 不能同时声明一个“x”
}
并不是说块级作用域变量不能用函数作用域变量来声明。 而是块级作用域变量需要在明显不同的块里声明。
function f(condition, x) {
if (condition) {
let x = 100;
return x;
}
return x;
}
f(false, 0); // returns 0
f(true, 0); // returns 100
在一个嵌套作用域里引入一个新名字的行为称做屏蔽。
它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。
例如,假设我们现在用 let重写之前的sumMatrix函数。
function sumMatrix(matrix: number[][]) {
let sum = 0;
for (let i = 0; i < matrix.length; i++) {
var currentRow = matrix[i];
for (let i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
const 声明
const 声明是声明变量的另一种方式。
const num = 9;
它们与let
声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let
相同的作用域规则,但是不能对它们重新赋值。
这很好理解,const
引用的值是不可变的。
const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
}
// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat
};
// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
解构
Another TypeScript已经可以解析其它 ECMAScript 2015 特性了。
解构数组 []
最简单的解构莫过于数组的解构赋值了:
let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
这创建了2个命名变量 first 和 second。 相当于使用了索引,但更为方便:
first = input[0];
second = input[1];
解构作用于已声明的变量会更好:
// swap variables
[first, second] = [second, first];
作用于函数参数:
ts:
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f([23,2]);
js:
function f(_a) {
var first = _a[0], second = _a[1];
console.log(first);
console.log(second);
}
f([23, 2]);
你可以在数组里使用…语法创建剩余变量:
ts:
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
js:
var _a = [1, 2, 3, 4], first = _a[0], rest = _a.slice(1);
console.log(first);
console.log(rest);
由于是JavaScript, 你可以忽略你不关心的尾随元素:
ts:
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
js:
var first = [1, 2, 3, 4][0];
console.log(first); // outputs 1
或其它元素
let [, second, , fourth] = [1, 2, 3, 4];
对象解构 {}
ts:
let o = {
a: "foo",
b: 12,
c: "bar",
}
let { a, b } = o;
js:
var o = {
a: "foo",
b: 12,
c: "bar",
};
var a = o.a, b = o.b;
属性重命名
可以给属性以不同的名字:
ts:
let { a: newName1, b: newName2 } = o;
js:
var newName1 = o.a, newName2 = o.b;
将 a: newName1
读做 “a 作为 newName1
”。 方向是从左到右,写成以下样子:
let newName1 = o.a;
let newName2 = o.b;
这里的冒号不是指示类型的。
如果你想指定它的类型, 仍然需要在其后写上完整的模式。
let {a, b}: {a: string, b: number} = o;
默认值
默认值可以让你在属性为 undefined
时使用缺省值:
ts:
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
js:
function keepWholeObject(wholeObject) {
var a = wholeObject.a, _a = wholeObject.b, b = _a === void 0 ? 1001 : _a;
}