在前一篇文章,我们了解了TS的安装使用以及基础类型,本文接着学习类型推断以及变量声明的内容。
类型推断
TS的核心功能就是类型检查
,我们需要给变量或方法添加类型注解来表明数据类型。当没有提供类型注解时,TS编译器会利用类型推断
来推断类型。
var num = 2; // 类型推断为 number
num = "12"; // 编译错误,不能将类型“string”分配给类型“number”
如果由于缺乏声明而不能推断出类型,那么它的类型被视作默认的动态 any
类型。
类型断言 Type Assertion
有些情况下,我们会比TS清楚更确切的类型,可以使用类型断言
手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。它没有运行时的影响,只是在编译阶段
起作用。
类型断言有两种形式,第一种是尖括号
语法:
<类型>值
第二种是 as
语法:
值 as 类型
举个例子:
// typescript
// 类型推断为 string
var str = '1'
// 成功,类型断言先转成any,再转成number
var str2: number = <number><any>str
// 失败,string直接转成number会报错
var str3: number = <number>str
// 编译为
// javascript
var str = '1';
var str2 = str;
通过上面的例子可以知道,使用类型断言转换需要两个类型间有关联,比如string
是any
的子集,那么它们就能互相转换,而number
也是any
的子集,那么它们也是可以互相转换,因此str2
可以成功被赋值;但是string
和number
两个类型不能充分重叠,因此是不能转换的。只有在明确类型的前提下才能使用类型断言,毫无根据的断言是很危险的。
变量声明
TS和JS的变量声明方式是一样的,这里简单讲一下var、let和const的关系与区别。
var
一直以来,我们使用var
语句来声明一个函数范围
或全局范围
的变量。
// 全局范围
var a = 1;
function fn() {
// 函数范围
var message = "Hello, world!";
return message;
}
var有一些奇怪的特性,有一个特性叫变量提升
,无论你在哪里声明变量,都会在执行任何代码之前进行处理:
function fn(flag) {
if (flag) {
var x = 10;
}
return x;
}
fn(true); // 10
fn(false); // undefined
上面的函数fn相当于
function fn(flag) {
// 变量x的初始值为 undefined
var x;
if (flag) {
x = 10;
}
return x;
}
在TS中,上面的代码增加了校验,虽然依旧能够运行,但是会给出提示
// typescript
function fn(flag: boolean) {
if (flag) {
var x = 10;
}
return x; // 报错:在赋值前使用了变量“x”
}
另一个例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[1](); // 输出10
这是因为在循环时传入函数的变量i
是全局变量
,在for
循环结束后,i
的值为10,调用函数输出的就是10。
另外,使用var可以重复声明相同变量而不报错:
var a;
var a;
var a;
let
正因为 var
存在一些问题,在 ES6
新增了 let
语句。除了名字不同外, let
与 var
的写法一致。
let hello = "Hello!";
当用 let
声明一个变量,它使用的是 块作用域
。 不同于使用 var
声明的变量那样可以在包含它们的函数外访问,块作用域
变量在包含它们的块
或for
循环之外是不能访问的。比如上面的例子:
function fn(flag) {
if (flag) {
let x = 10;
}
return x;
}
fn(true); // 10
fn(false); // 报错:x is not defined
另一个例子:
let a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[1](); // 输出1
拥有块级作用域的变量的另一个特点是,它们不能在被声明之前
读或写。
// 报错:Cannot access 'a' before initialization
a++;
let a;
上面并没有报错说 a
未定义,而是说不能在初始化之前访问它。意思是变量a存在于作用域中,但是直到声明它的代码之前的区域都属于暂时性死区
,是无法访问它的。在TS中,上述代码会收到错误提示声明之前已使用的块范围变量“a”
。
注意
一点,我们仍然可以在一个拥有块作用域变量被声明前获取它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为ES2015,现代的运行时会抛出一个错误。
function foo() {
// 可以成功捕获到a
// 运行时会报错:Cannot access 'a' before initialization
return a;
}
// 不能在'a'被声明前调用'foo'
// 运行时应该抛出错误
foo();
let a;
上面代码在TS中是不会提示报错的,需要注意。
在上面我们提到过,使用var可以重复声明,但是let就没那么宽松了:
let a;
let a;
上述代码在运行时报错:Identifier 'a' has already been declared
,而在TS中也会提示无法重新声明块范围变量“a”
,同一个块作用域中只能声明一次。
并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告:
// case1
function f(x) {
// TS警告:标识符“x”重复
let x = 100; // 运行时报错:Identifier 'x' has already been declared
}
// case2
function g() {
// TS警告:无法重新声明块范围变量“x”
let x = 100;
var x = 100; // 运行时报错:Identifier 'x' has already been declared
}
如果要在函数作用域中声明相同名称的块作用域变量,需要放在不同的块中:
function fn(flag, x) {
if (flag) {
let x = 100;
return x;
}
return x;
}
fn(false, 0); // 0
fn(true, 0); // 100
在一个嵌套作用域里引入一个新名字的行为称做屏蔽
const
const
语句是声明变量的另一种方式,与 let
声明方式相似,区别是声明赋值后不能重新赋值
:
const role = 'Tom'
// TS警告:无法分配到 "role" ,因为它是常数
role = 'Jerry'
上述代码运行时报错:Assignment to constant variable
const 引用的值不可改变,但是内部状态可以改变:
const person = {
name: 'Tom'
}
// 失败
person = {
name: 'Jerry'
}
// 成功
person.name = 'Jerry'
总的来说,let
和 const
使用方式接近,基本原则就是如果一个变量不需要重新赋值就使用 const
,其他情况则使用 let
。