TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。
TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
什么是 TypeScript
TypeScript 是 JavaScript 的、带有类型 的超集 ,并且能够编译 成普通的 JavaScript。
编译:
类型:
超集:
TypeScript 本身支持所有 JavaScript 的语法,并在此基础上添加了额外的功能和特性。
TypeScript 是由微软开发的一款开源的编程语言。
TypeScript 是 Javascript 的超集,遵循最新的 ES6、Es5 规范。TypeScript 扩展了 JavaScript 的语法。
TypeScript 更像后端 java、C#这样的面向对象语言,可以让 js 开发大型企业项目。
谷歌也在大力支持 Typescript 的推广,谷歌的 angular2.x+就是基于 Typescript 语法。
最新的 Vue 、React 也可以集成 TypeScript。
Nodejs 框架 Nestjs、midway 中用的就是 TypeScript 语法。
带有类型,是说js在定义变量的时候,类型是动态的,只有在运行的时候才能知道它的具体类型,比如 number 或者 string,并且类型也是可以动态变化的,而 TypeScript 则是要求变量有确定的类型,并且在编写代码的时候就已经确定,如果把字符串赋给类型为 number ,数字类型的变量,就会出错。
为什么用 TypeScript
在 stackoverflow 发起的2020年程序员调查中,TypeScript 在程序员最爱的编程语言中排在了第二位
https://stackoverflow.blog/2020/05/27/2020-stack-overflow-developer-survey-results/
好处优势:
类型检查、代码补全、易于维护、入门简单
之所以大家喜欢 TypeScript,是因为:
TypeScript 有类型检查机制,我们可以在写代码的时候就能够发现错误,比如给函数误传了类型不同的参数,那么通过 VS Code 对 TypeScript 的强力支持,我们能立刻看到错误。
另外 VS Code 能根据 TypeScript 的类型信息提供更好的代码提示和补全功能。 此外,对于大型项目、多人协作编写代码时,类型起到了文档的作用,可以清楚的知道我这个变量是什么类型,或者我定义的函数需要什么样的参数,我的对象里又有哪些属性。这样让代码更易于维护,这也是为什么大公司、大型项目更偏爱 TypeScript
最后 TypeScript 入门的门槛低,只要你会 JavaScript,那么你就已经能编写 TypeScript 代码了。另外因为 JS 的快速发展,好多以前在 typescript 才能用的功能,你可能在JS 里已经用到了,所以说要学习的东西就更少了。
除了这些好处之外,它也有其他静态类型语言比如 Java/c++ 的通病,就是代码量会增加,并且有时候类型过于复杂反而使得代码显的更难阅读,不过跟它带来的优势相比,也显得不那么突出了。
安装TypeScript
有两种主要的方式来获取TypeScript工具:
通过npm(Node.js包管理器)
安装Visual Studio的 TypeScript 插件
Visual Studio 2017和Visual Studio 2015 Update 3默认包含了TypeScript。 如果你的Visual Studio还没有安装TypeScript,你可以下载 它。
针对使用npm的用户:
npm install -g typescript
构建你的第一个TypeScript文件
在编辑器,将下面的代码输入到greeter.ts
文件里:
编译代码
我们使用了.ts
扩展名,但是这段代码仅仅是JavaScript而已。 你可以直接从现有的JavaScript应用里复制/粘贴这段代码。
在命令行上,运行TypeScript编译器:
然后可以看到,输出结果为一个 greeter.js
文件,它包含了和输入文件中相同的JavsScript代码。
// greeter.js
function greeter(person) {
return "Hello, " + person;
}
var user = "Jane User";
document.body.innerHTML = greeter(user);
一切准备就绪,我们可以运行这个使用 TypeScript 写的 JavaScript 应用了!
接下来让我们看看 TypeScript 工具带来的高级功能。 给 person
函数的参数添加 :string
类型注解,如下:
function greeter(person: string) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
运行编译:
可以看到 greeter.js 的代码没变。
类型注解
TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。 在这个例子里,我们希望 greeter
函数接收一个字符串参数。
然后尝试把 greeter
的调用改成传入一个数组:
function greeter(person: string) {
return "Hello, " + person;
}
let user = [0, 1, 2];
document.body.innerHTML = greeter(user);
可以看到报错:
重新运行编译也可以看到报错:
类似地,尝试删除 greeter
调用的所有参数。 TypeScript会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。
要注意的是尽管有错误,greeter.js
文件还是被创建了。 就算你的代码里有错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。
新建一个 index.ts 文件:
let a:number = 10;
console.log(a);
编译并运行:可以看到
另一种编译方法是通过 deno:deno 本身就支持 TypeScript ,所以只需要安装 deno 的运行环境就可以了。
// 使用 Shell:
curl -fsSL https:// x.deno.js.cn/install.sh | sh
// 使用 PowerShell:
iwr https: // x.deno.js.cn/install.ps1 -useb | iex
安装完成后,输入:
也可以输出结果:10
打开 index.js 可以看到:除了去除了类型没有什么变化
var a = 10;
console.log(a);
有个问题:JavaScript 版本那么多,tsc 怎么知道要编译成那个版本呢?
tsc 默认会编译成 ES3 版。
下面我们写一个 async 函数:
let a:number = 10;
console.log(a);
async function func() {
}
再重新编译下,然后打开 index.js 文件,发现生成了很多复杂的代码来支持 async。
那如果我想要生成 ES2017 的代码呢?
需要在根目录下创建一个 tsconfigt.json 文件:
{
"compilerOptions": {
"target": "ES2017"
}
}
这时编译可以输入:
因为有了 tscofigt.josn 文件后,这个文件夹会自动成为 TypeScript 项目 ,tsc 会自动找到 .ts 文件,并进行编译。如果指定了文件名,那么 tscofigt.josn 设置就会被忽略 。
然后打开 index.js 文件可以看到:
let a = 10;
console.log(a);
async function func() {
}
还可以用开发工具 Vscode 自动编译.ts 文件
在终端中输入:可以生成配置文件 tsconfig.json
注意:如果你已经创建过了,就不会再生成了。
TypeScript 基本语法
布尔类型(boolean)、数字类型(number)、字符串类型(string)、数组类型(array)、元组类型(tuple)、枚举类型(enum)、任意类型(any)、任意类型(any)、对象类型(object)、void类型、never类型、组合类型、类型别名、null 和 undefined
1、基本类型
给变量定义类型有两种方式:隐式类型和显式类型
隐式类型 :是由 TypeScript 根据变量的值来推断类型,代码写法和js一样,但不同的是:后面不能用其他类型的值来给它重新赋值。
比如:
可以看到a上报错,鼠标移到a上可以看到:
显式类型 :和之前运行的ts代码示例一样,用 : + 类型 来规定这个变量是什么类型的。
·常用类型:
布尔类型(boolean)、数字类型(number)、字符串类型(string)、null 和 undefined
例如:
你还可以使用模版字符串,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围( `),并且以 ${ expr } 这种形式嵌入表达式
let username: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ username }. I'll be ${ age + 1 } years old next month.`;
console.log(sentence); // Hello, my name is Gene. I 'll be 38 years old next month.
这与下面定义sentence的方式效果相同:
let username: string = `Gene`;
let age: number = 37;
let sentence: string = "Hello, my name is " + username + ".\n" +
"I'll be " + (age + 1) + " years old next month.";
console.log(sentence);
// Hello, my name is Gene.
// I'll be 38 years old next month.
·任意类型 any
如果想让一个变量可以是任何类型 ,就像 js 中可以任意更改的话。那么我们可以把它的类型定义为 any 。
例如:这时可以看到不会报错
let a: any = 10;
a = "hello";
类型也可以用在函数的参数和返回值中
比如:
function add(a: number, b: number): number {
return a + b;
}
add( 1, 2);
// 小括号后面的:number 是返回值的类型,当然也可以省略不写
function add(a: number, b: number) {
return a + b;
}
add( 1, 2);
注意:调用函数时必须传递跟参数列表相同的参数(也就是说实参和形参的数量要一致) ,不像 js 可以不传或者只传前面的几个参数。
例如:我们只传a的值
function add(a: number, b: number): number{
return a + b;
}
add( 1);
会报错:
·void类型
某种程度上来说,void
类型像是与 any
类型相反,它表示没有任何类型。 如果函数不返回值的话,可以使用 :void 类型,代表函数没有返回值 。
例如:
function add(a: number, b: number): void {
console.log(a + b);
}
2、组合类型
如果一个变量可以有多个类型,但是又不想使用 any 破坏类型检查,那么可以使用组合类型。组合类型使用 | 操作符来定义 。
例如:
let a :number | string = 10;
a = "hello";
不过这样代码看起来不太方便,并且这个组合类型只能给 a 使用,如果再有一个变量 b,也可以是number 或者 string 类型,那么还需要再重复定义这个类型。
要解决这个问题,我们可以使用 type 关键字,来给这个组合类型起个别名 ,让代码更易读,也方便其他变量使用。
例如:
type NumStr = number | string;
let a: NumStr = 10;
a = "hello";
let b: NumStr = '123';
b = 123;
另外组合类型也可以直接使用字面值来定义 ,这样就规定了一个变量的取值范围。
例如:
let c: "on" | "off" = "on";
c = "off";
c = "other";
可以看到最后一条报错:
3、对象类型
使用 interface 接口,接口是用来规范一个对象里应该都有哪些属性,包括它的名字和类型
例子:如果有一个 post 文章变量,里面有 title 和 author 属性,并且都是 string 类型的。那么我们可以使用接口来定义一个 post 类型。
interface Post {
title: string;
author: string;
}
let post: Post = {
title: '标题',
author: '作者'
}
如果在增加一个属性就会报错:
同样,去掉 title 属性,一样会报错:
接口 除了可以检查对象是否符合规范外,也可以用于函数参数的类型检查
注意: 如果传递进来的对象没有定义类型的话,只要它的属性里包括接口中定义的规范,那么就可以通过检查,哪怕它有额外的属性。
例如:
interface Post {
title: string;
author: string;
}
function getTitle(post: Post) {
console.log(post.title)
}
let post = {
title: '标题',
author: '作者',
publishDate: '2020-10-10'
}
getTitle(post); // 标题
如果想要严格检查对象参数的话,可以像之前那样把 post 对象定义为 Post 接口类型的:
或者直接给函数传递对象字面值:
getTitle({
title: '标题',
author: '作者',
}); // 标题
4、数组类型
给数组规定类型,可以保证里面的元素都是同一个类型。以防在统一处理数组元素时,会混进来其他类型的元素,导致异常;或者防止意外给数组元素赋了其他类型的值。
let arr: number[] = [1, 2, 3];
还有一种写法是:使用数组泛型 Array<元素类型>
:
let arr: Array<number> = [1, 2, 3];
当然也可以使用 any 定义任意类型的数组:
let arr: any[] = [1, "fh", true ];
5、元祖 (tuple)
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同 。
有限元素数量的数组,每个元素要分别指定是什么类型
let tup: [number, string, boolean ] = [1, "fh", true ];
6、枚举类型(enum)
enum
类型是对 JavaScript 标准数据类型的一个补充。 像 C# 等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。
如果能在程序中用自然语言中有相应含义的单词来代表某一状态,则程序就很容易阅读和理解。也就是说,事先考虑到某一变量可能取的值,尽量用自然语言中含义清楚的单词来表示它的每一个值,这种方法称为枚举方法,用这种方法定义的类型称枚举类型。
enum Status {success=1, error=2};
let s:Status = Status.success;
console.log(s); //1
enum Color {blue, red, orange};
let c:Color = Color.red;
console.log(c); // 1 如果标识符没有赋值 它的值就是下标
枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到 Color 里的哪个名字,我们可以查找相应的名字:
enum Color {blue , red, orange}
let colorName1: string = Color[2];
console.log(colorName1); // orange
enum NewColor {blue = 1, red, orange}
let colorName2: string = NewColor[2];
console.log(colorName2); // // 显示'red'因为上面代码里它的值是2
7、对象类型(object)
object
表示非原始类型,也就是除number
,string
,boolean
,symbol
,null
或undefined
之外的类型。
使用object
类型,就可以更好的表示像Object.create
这样的API。例如:
declare function create(o: object | null ): void ;
create({ prop: 0 }); // OK
create(null ); // OK
let arr2:object = [11, 'asd', 1321];
console.log(arr2);
8、null 和 undefined:其他(never类型)数据类型的子类型
TypeScript里,undefined
和 null
两者各自有自己的类型分别叫做 undefined
和 null
。 和 void
相似,它们的本身的类型用处不是很大:
let num:number;
console.log(num) // 输出:undefined 报错
let num1:undefined;
console.log(num1) // 输出:undefined //正确
let num2:number | undefined;
num2 = 123;
console.log(num2);
默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和undefined 赋值给 number 类型的变量。
然而,当你指定了--strictNullChecks 标记,null和undefined只能赋值给void和它们各自。 这能避免 很多常见的问题。 也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined。 再次说明,稍后我们会介绍联合类型。
注意 :我们鼓励尽可能地使用--strictNullChecks,但在本手册里我们假设这个标记是关闭的。
9、never类型
never
类型表示的是那些永不存在的值的类型。 例如, never
类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never
类型,当它们被永不为真的类型保护所约束时。
never
类型是其他类型 (包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明 never 的变量只能被 never 类型所赋值。 即使 any
也不可以赋值给 never
。
let a: never;
a = 123; // 错误写法
a = (function () {
throw new Error("错误")
})();
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 推断的返回值类型为never
function fail() {
return error("Something failed");
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true ) {
}
}
类型断言
有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
类型断言有两种形式。 其一是“尖括号 ”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
另一个为 as
语法:
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as
语法断言是被允许的。
关于let
你可能已经注意到了,我们使用 let
关键字来代替大家所熟悉的JavaScript关键字 var
。 let
关键字是JavaScript的一个新概念 ,TypeScript实现了它。 我们会在以后详细介绍它,很多常见的问题都可以通过使用 let
来解决,所以尽可能地使用let
来代替var
吧 。
变量声明
let和const是JavaScript里相对较新的变量声明方式。 像我们之前提到过的, let在很多方面与var是相似的,但是可以帮助大家避免在JavaScript里常见一些问题。 const是对let的一个增强,它能阻止对一个变量再次赋值。 因为TypeScript是JavaScript的超集,所以它本身就支持let和const。 下面我们会详细说明这些新的声明方式以及为什么推荐使用它们来代替 var。
var 声明
一直以来我们都是通过 var 关键字定义JavaScript变量。
function f() {
var a = 10;
return function g() {
var b = a + 1;
return b;
}
}
var g = f();
g(); // returns 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(); // returns 2
作用域规则
对于熟悉其它语言的人来说,var
声明有些奇怪的作用域规则。 看下面的例子:
function f(shouldInitialize: 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 currentRow = matrix[i];
for (var i = 0; i < currentRow.length; i++) {
sum += currentRow[i];
}
}
return sum;
}
这里很容易看出一些问题,里层的for循环会覆盖变量i,因为所有i都引用相同的函数作用域内的变量。 有经验的开发者们很清楚,这些问题可能在代码审查时漏掉,引发无穷的麻烦。
捕获变量怪异之处
快速的猜一下下面的代码会返回什么:
for (var i = 0; i < 10; i++) {
setTimeout( function () {
console.log(i);
}, 100 * i);
}
好吧,看一下结果:
10
10
10
10
10
10
10
10
10
10
很多JavaScript程序员对这种行为已经很熟悉了,但如果你很不解,你并不是一个人。 大多数人期望输出结果是这样:
还记得我们上面提到的捕获变量吗?
我们传给 setTimeout 的每一个函数表达式实际上都引用了相同作用域里的同一个 i 。
让我们花点时间思考一下这是为什么。 setTimeout
在若干毫秒后执行一个函数,并且是在 for
循环结束后。 for
循环结束后,i
的值为 10
。 所以当函数被调用的时候,它会打印出 10
!
一个通常的解决方法是使用立即执行的函数表达式(IIFE)来捕获每次迭代时 i
的值:
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);
}
这种奇怪的形式我们已经司空见惯了。 参数 i
会覆盖 for
循环里的 i
,但是因为我们起了同样的名字,所以我们不用怎么改for
循环体里的代码。
let
声明
现在你已经知道了 var
存在一些问题,这恰好说明了为什么用 let
语句来声明变量。 除了名字不同外, let
与 var
的写法一致。
主要的区别不在语法上,而是语义,我们接下来会深入研究。
块作用域
当用 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可以告诉我们这些信息。
注意一点,我们仍然可以在一个拥有块作用域变量被声明前获取 它。 只是我们不能在变量声明前去调用那个函数。 如果生成代码目标为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;
}
}
在上面的例子里,所有x的声明实际上都引用一个相同的 x ,并且这是完全有效的代码。 这经常会成为 bug 的来源。 好的是, let 声明就不会这么宽松了。
let x = 10;
let x = 20; // 错误,不能在1个作用域里多次声明`x`
并不是要求两个均是块级作用域的声明TypeScript才会给出一个错误的警告。
function f(x) {
let x = 100; // error: interferes with parameter declaration
}
function g() {
let x = 100;
var x = 100; // error: can't have both declarations of '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;
}
这个版本的循环能得到正确的结果,因为内层循环的 i 可以屏蔽掉外层循环的 i 。
块级作用域变量的获取
在我们最初谈及获取用 var
声明的变量时,我们简略地探究了一下在获取到了变量之后它的行为是怎样的。 直观地讲,每次进入一个作用域时,它创建了一个变量的 环境 。 就算作用域内代码已经执行完毕,这个环境与其捕获的变量依然存在。
function theCityThatAlwaysSleeps() {
let getCity;
if (true ) {
let city = "Seattle";
getCity = function () {
return city;
}
}
return getCity();
}
console.log(theCityThatAlwaysSleeps()); // Seattle
因为我们已经在 city
的环境里获取到了 city
,所以就算 if
语句执行结束后我们仍然可以访问它。
回想一下前面 setTimeout 的例子,我们最后需要使用立即执行的函数表达式来获取每次 for 循环迭代里的状态。 实际上,我们做的是为获取到的变量创建了一个新的变量环境。 这样做挺痛苦的,但是幸运的是,你不必在TypeScript里这样做了。
当 let 声明出现在循环体里时拥有完全不同的行为。 不仅是在循环里引入了一个新的变量环境,而是针对 每次迭代都会创建这样一个新作用域。 这就是我们在使用立即执行的函数表达式时做的事,所以在 setTimeout 例子里我们仅使用 let 声明就可以了。
for (let i = 0; i < 10; i++) {
setTimeout( function () {
console.log(i);
}, 100 * i);
}
会输出与预料一致的结果:
const
声明
const
声明是声明变量的另一种方式。
const numLivesForCat = 9;
它们与 let
声明相似,但是就像它的名字所表达的,它们被赋值后不能再改变。 换句话说,它们拥有与 let
相同的作用域规则,但是不能对它们重新赋值 。
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 --;
console.log(kitty); // { name: 'Cat', numLives: 8 }
除非你使用特殊的方法去避免,实际上 const 变量的内部状态是可修改的。 幸运的是,TypeScript允许你将对象的成员设置成只读的。 接口一章有详细说明。
let
vs. const
现在我们有两种作用域相似的声明方式,我们自然会问到底应该使用哪个。 与大多数泛泛的问题一样,答案是:依情况而定。
使用最小特权原则,所有变量除了你计划去修改的都应该使用const 。 基本原则就是如果一个变量不需要对它写入,那么其它使用这些代码的人也不能够写入它们,并且要思考为什么会需要对这些变量重新赋值。 使用 const 也可以让我们更容易的推测数据的流动。
解构
解构数组
最简单的解构莫过于数组的解构赋值了:
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];
解构作用于已声明的变量会更好:
// 交换变量
[first, second] = [second, first];
作用于函数参数:
function f([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
f([ 1, 2]); // 1 // 2
你可以在数组里使用 ... 语法创建剩余变量:
let [first, ...rest] = [1, 2, 3, 4];
console.log(first); // outputs 1
console.log(rest); // outputs [ 2, 3, 4 ]
当然,由于是JavaScript, 你可以忽略你不关心的尾随元素:
let [first] = [1, 2, 3, 4];
console.log(first); // outputs 1
或其它元素:
let [, second, , fourth] = [1, 2, 3, 4];
console.log(second); // outputs 2
console.log(fourth); // outputs 4
对象解构
你也可以解构对象:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
console.log(a); // foo
console.log(b); // 12
这通过 o.a
and o.b
创建了 a
和 b
。 注意,如果你不需要 c
你可以忽略它。
就像数组解构,你可以用没有声明的赋值:
({ a, b } = { a: "baz", b: 101 });
注意,我们需要用括号将它括起来,因为Javascript通常会将以 {
起始的语句解析为一个块。
你可以在对象里使用 ...
语法创建剩余变量:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
console.log(total); // 15
console.log(passthrough); // { b: 12, c: 'bar' }
属性重命名
你也可以给属性以不同的名字:
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a: newName1, b: newName2 } = o;
console.log(newName1); // foo
console.log(newName2); // 12
这里的语法开始变得混乱。 你可以将 a: newName1
读做 "a
作为 newName1
"。 方向是从左到右,好像你写成了以下样子:
let newName1 = o.a;
let newName2 = o.b;
令人困惑的是,这里的冒号不是指示类型的。 如果你想指定它的类型, 仍然需要在其后写上完整的模式。
let o = {
a: "foo",
b: 12,
c: "bar"
};
let {a, b}: {a: string, b: number} = o;
console.log(a); // foo
console.log(b); // 12
默认值
默认值可以让你在属性为 undefined 时使用缺省值:
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
}
现在,即使 b
为 undefined , keepWholeObject
函数的变量 wholeObject
的属性 a
和 b
都会有值。
function keepWholeObject(wholeObject: { a: string, b?: number }) {
let { a, b = 1001 } = wholeObject;
return {a, b};
}
console.log(keepWholeObject({a: '11'})); // { a: '11', b: 1001 }
TypeScript中的函数
1、函数的定义
传递参数和返回值都要进行指定类型 返回类型必须是 string 类型,不能写成别的数据类型,否则会报错
// 有返回值
function run1(): string {
return 'run';
}
// 无返回值
function run2(): void {
console.log( 'run');
}
// 箭头函数
const materials = [
'Hydrogen',
'Helium',
'Lithium',
'Beryllium'
];
console.log(materials.map((material): any => material.length)); // [8, 6, 7, 9] 加不加any都可以
匿名函数的写法:
var run2=function ():string{// 指定返回值类型为string字符串类型
return '1243';
}
run2(); // 调用方法
console.log(run2()); // 1243
定义方法中的传参:
function getInfo(name: string, age: number): string {
return `${name} --- ${age}`;
}
console.log(getInfo( "zhangsan", 20)); //
请发表评论