在前一篇文章,我们了解了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
。