2. 变量声明 (declare variable)

TypeScript 变量的命名规则:

  • 变量名称可以包含数字和字母。

  • 除了下划线 _ 和美元 $ 符号外,不能包含其他特殊字符,包括空格。

  • 变量名不能以数字开头。

变量使用前必须先声明,可以使用 varletconst 来声明变量。

TypeScript 遵循强类型,如果将不同的类型赋值给变量会编译错误。

2.1. 变量声明

letconst 是 JavaScript 里相对较新的变量声明方式。

一直以来我们都是通过 var 关键字定义 JavaScript 变量。对于熟悉其它语言的人来说,var 声明有些奇怪的作用域规则。如下代码所示:

function f(shouldInitialize: boolean) {
    if (shouldInitialize) {
        var x = 10;
    }

    return x;
}

f(true);  // returns '10'
f(false); // returns 'undefined'

变量x是定义在if语句里面,但是我们却可以在语句的外面访问它。这是因为 var 声明可以在包含它的函数,模块,命名空间或全局作用域内部任何位置被访问 。 由于 var 定义的变量的作用域的模糊和没有限制,那么在程序中,会出现相同作用域范围内的同名变量被最后定义的同名变量覆盖。(它不在乎你声明多少次;你只会得到1个。)

2.1.1. let 声明

letvar 主要的区别不在语法上,而是语义:

  • 块作用域

  • 重定义及屏蔽

  • 块级作用域变量的获取

2.1.1.1. 块作用域

拥有块级作用域的变量的另一个特点是,它们不能在被声明之前读或写。注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。

function foo() {
    // okay to capture 'a'
    return a;
}

// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();

let a;

2.1.1.2. 重定义及屏蔽

在一个 嵌套作用域里引入 一个新名字的行为称做屏蔽。 它是一把双刃剑,它可能会不小心地引入新问题,同时也可能会解决一些错误。

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;
}

2.1.1.3. 块级作用域变量的获取

直观地讲,每次进入一个作用域时,它创建了一个变量的环境。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。

function theCityThatAlwaysSleeps() {
    let getCity;

    if (true) {
        let city = "Seattle";
        getCity = function() {
            return city;
        }
    }

    return getCity();
}
//因为我们已经在city的环境里获取到了city,所以就算if语句执行结束后我们仍然可以访问它。

2.1.2. const 声明

const 是对 let 的一个增强,它们拥有与let相同的作用域规则,还能阻止对一个变量再次赋值,即它们引用的值是不可变的

使用最小特权原则,所有变量除了你计划去修改的都应该使用 const 。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。 使用 const 也可以让我们更容易的推测数据的流动。

readonly & const

最简单判断该用 readonly 还是 const 的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用 const ,若做为属性则使用 readonly。

2.2. 解构 (析构声明)

2.2.1. 解构数组

let input = [1, 2];
let [first, second] = input;
console.log(first); // outputs 1
console.log(second); // outputs 2
//相当于使用了索引,但更为方便
// first = input[0];
// second = input[1];

还可以对函数参数解构:

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 [s] = [1, 2, 3, 4];
console.log(s); // outputs 1
let [, second, , fourth] = [1, 2, 3, 4];
console.log(second); //outputs 2
console.log(fourth); //outputs 4

2.2.2. 对象解构

let o = {
    a: "foo",
    b: 12,
    c: "bar"
};
let { a, b } = o;

// let { a, b } = o; 的析构声明可以简单写为:
// let newName1 = o.a;
// let newName2 = o.b;

//let { a, b } = o; 等价于:
({ a, b } = { a: "baz", b: 101 }); //使用不显示声明的赋值

// 可以使用 ... 语法来说创建剩余变量
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;

注意

注意,对一个对象进行不声明地析构时,我们需要用小括号将它括起来,因为Javascript通常会将以 { 起始的语句解析为一个块。

2.2.2.1. 属性的重命名

你也可以给属性以不同的名字:

let { a: newName1, b: newName2 } = o;

这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。

let {a, b}: {a: string, b: number} = o;

警告

要小心使用解构。因为即便是最简单的解构表达式也是难以理解的。解构表达式要尽量保持小而简单。