目录
一、TypeScript是什么?有何特点?
TypeScript 是一种脚本语言。
它是JavaScript的超集,包含了 JavaScript 的所有功能,与JavaScript有相同的语法和语义。
它在JavaScript的功能之上添加了一层:TypeScript 的类型系统。
TypeScript 代码在运行前需要编译成 JavaScript 代码,然后由浏览器或 JavaScript 运行时环境执行。
二、为什么使用它?
那肯定是因为JavaScript存在不好的地方。
JavaScript存在什么缺点或不足?
1、缺点一
JavaScript作为一门动态类型和弱类型的脚本语言,其特点就是代码中的错误需要在运行时才能检测出来,但是这时候已经为时已晚,这个错误已经是bug了,而且此时如果代码已经上线,那往往导致严重的后果。
而如果使用TypeScript,它的静态类型检查(器)使得我们的代码在编译的时候就会发现错误(至少绝大部分的错误诸如拼写错误、语法错误等),不至于要等到运行的时候才能发现错误。
注:不运行代码的情况下检测其中的错误称为 静态检查。根据被操作的值的种类来确定是什么错误和什么不是错误,这称为 静态类型检查。
下面是一个TypeScript静态类型检查器基于值的类型检查程序是否有错误的例子:
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;
//Property 'heigth' does not exist on type '{ width: number; height: number; }'. Did you mean 'height'?
再比如,语法错误也能检查出来:
let a = (4
//')' expected.
2、缺点二
JavaScript遵循ECMAScript(ES)标准,同时JavaScript也是ES标准的最知名的一个实现,两者相辅相成。
但是,ES在修订和发展新特性的时候,比如类、async和await等新特性,往往由于兼容性问题不能很好的在项目里面使用,因为JavaScript运行环境通常不会很快支持这些新特性。
而TypeScript代码就不用担心这个问题,这些新特性——比如async和await(ECMAScript 2017引入的新特性)可以直接在项目里面使用而不必担心兼容问题,TypeScript编译器将负责把你的代码编译成指定的ECMAScript版本的JavaScript代码。
ES指定JavaScript代码编译版本
怎样指定呢?
TypeScript 编译器可以通过 tsconfig.json 文件中的 compilerOptions 配置来指定编译的目标 ES 版本和使用的模块化规范。
具体来说:
指定 ES 版本:使用 `target` 选项来指定 TypeScript 编译成 JavaScript 时的目标ECMAScript 版本。
可选的版本包括 `'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'`。
例如,如果要编译成 ES6 版本的代码,可以在 `tsconfig.json` 中设置 `"target": "es6"` 。
指定模块化规范:使用 `module` 选项来指定编译文件使用的模块化规范。
可选的模块化规范包括 `'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node16', 'nodenext'`。
例如,如果要使用 ES6 模块,可以设置 `"module": "es6"` 。
这些配置项允许开发者根据项目需求和目标运行环境来定制 TypeScript 代码的编译输出。
如果需要对编译器的行为进行更细致的控制,tsconfig.json 文件还提供了其他多种编译选项,如 `moduleResolution`、`baseUrl`、`paths` 等,用于控制模块解析的方式和路径映射 。
下面是一个 tsconfig.json 文件的示例,它展示了如何配置 TypeScript 编译器以编译到特定版本的 ECMAScript 并使用特定的模块系统:
{
"compilerOptions": {
// 指定编译目标为 ES2020
"target": "es2020",
// 指定模块系统为 ES6 模块
"module": "es2020",
// 指定输出的 JavaScript 文件中包含 source map
"sourceMap": true,
// 启用 JSX 支持,适用于 React 项目
"jsx": "react",
// 指定 JSX 工厂函数,React 项目中通常使用 'React.createElement'
"jsxFactory": "React.createElement",
// 启用装饰器,如果你在使用类装饰器的话
"experimentalDecorators": true,
// 启用装饰器元数据发射
"emitDecoratorMetadata": true,
// 指定模块解析策略为 Node.js 的模块解析策略
"moduleResolution": "node",
// 指定编译器在查找模块时的基本目录
"baseUrl": ".",
// 指定要包含的文件或文件模式
"include": [
"src/**/*"
],
// 指定要排除的文件或目录
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
}
三、如何使用TypeScript?
1、在线运行
TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript
2、本地运行
支持TypeScript的编辑器有很多,这里推荐使用Visual Studio Code。
注:Visual Studio Code是一款使用TypeScript语言基于Electron框架开发的免费跨平台集成开发环境。
安装 Visual Studio Code
下载地址:https://code.visualstudio.com/
安装 TypeScript
推荐使用npm进行安装。
使用npm前提是首先检查你的计算机是否安装Node.js,安装Node.js也会自动安装npm工具,如未安装Node.js请到其官网安装:Node.js — 在任何地方运行 JavaScript
检查是否安装成功:
node -V
全局安装TypeScript:
npm install -g typescript
检查是否安装成功:
tsc --version
四、TypeScript语言基础
一门编程语言的基础往往包括几个部分:变量、注释、数据类型、字面量、对象、数组、函数等。
注:语法和语句(比如if、while、for等)这些和JavaScript一样,限于篇幅就略过了,只说上面几部分。
1、变量
计算机程序中,变量是指给定特定符号名,然后这个符号名与计算机内存中某个存储地址相关联,并可以容纳某个值;而变量的值可在程序执行过程中发生改变,当操作变量时,实际是在操作变量对应的存储地址中的数据,因此,在程序中可以使用变量来存储和操作数据。
变量名
和JavaScript一样,每个变量名(标识符)遵循以下规则:
允许包含字母、数字、下划线、美元符号“$”、Unicode转义序列(如“\u0069\u{6F}”);
不允许数字作为第一个字符,其余可以;
标识符区分大小写,如jason和JASON是两个不同的标识符;
严格模式下,保留字不可作为标识符,非严格模式下部分可以(自己查)。
变量声明
JavaScript支持三种变量声明方式:var(ECMAScript 2015之前已支持)、let和const(let和const是ECMAScript 2015中新引入)。
var声明
可赋予初始值,也可不赋予初始值,不赋予的情况则其默认值为undefined。
var x = 0;
var y; //undefined
let和const声明
两者声明的变量具有块级作用域:变量的作用域是指该变量可访问的区域,出了这个区域则不可访问;块,是指块语句,语法上用大括号“{}”来表示。
let声明示例:
{
let x = 1;
console.log(x); // 输出 1
}
console.log(x); // 报错,x 在这里未定义
const声明示例:
const声明的变量也具有块级作用域,但它用于声明一个常量,即一旦被赋值后,其值不能被重新赋值。如果尝试重新赋值,将会引发错误。
const obj = { a: 1 };
console.log(obj.a); // 输出 1
obj.a = 2;
console.log(obj.a); // 输出 2
// 尝试重新赋值会引发错误
// obj = { b: 2 }; // 错误,不允许重新赋值
注:虽然const变量的值不能被重新赋值,但如果该值是一个对象或数组,对象或数组内的属性或元素仍然可以被修改。
2、注释
在 TypeScript 中,你可以使用两种类型的注释来为你的代码添加说明或解释:
单行注释
使用两个斜杠 `//` 开始,直到行尾都是注释。例如:
// 这是一个单行注释
let message = "Hello, World!";
多行注释
使用 `/*` 开始,并以 `*/` 结束,可以跨越多行。例如:
/*
这是一个
多行注释
*/
let message = "Hello, World!";
此外,TypeScript 和 JavaScript 都支持 JSDoc 注释风格,这种注释可以提供更详细的文档信息,如参数类型、返回类型等,这对于生成 API 文档和提高代码可读性非常有帮助。JSDoc 注释通常使用 `/** ... */` 的形式。例如:
/**
* 返回两个数字的和
* @param x 第一个加数
* @param y 第二个加数
* @returns 两数之和
*/
function add(x: number, y: number): number {
return x + y;
}
在上面的例子中,`@param` 标签用于描述函数参数,`@returns` 标签用于描述函数的返回值。这些信息可以在生成的文档中看到。
区域注释
在 TypeScript 或 JavaScript 中,并没有原生支持的“区域注释”(region comments)概念,像 C# 或 Visual Basic .NET 中那样可以折叠的区域注释。
不过,许多 IDE 和代码编辑器,如 Visual Studio Code,提供了类似的功能,允许用户通过使用特定的注释标签来自定义代码的折叠区域。
例如,在 Visual Studio Code 中,你可以使用以下注释样式来创建可折叠的区域:
// #region MyRegionName
// 这里的代码将被折叠到"MyRegionName"区域下
let myVariable = "Hello";
let anotherVariable = "World";
// #endregion
当你在编辑器中看到这样的注释时,你可以点击旁边的折叠图标或者直接点击 `#region` 或 `#endregion` 来展开或折叠这个区域。
然而,请注意,这种格式并不属于 TypeScript 或 JavaScript 的语言特性,而是编辑器提供的功能。这意味着它不会影响代码的编译或运行,仅作为编辑器的辅助功能存在。
在不同的编辑器中,创建可折叠区域的具体语法可能会有所不同,因此你应该查阅你所使用的编辑器的文档来了解具体的支持情况和语法。
注:
区域注释(如 `#region` 和 `#endregion`)在 TypeScript 或 JavaScript 编译过程中实际上没有任何特殊意义,它们仅仅被视为普通的多行注释。也就是说,当 TypeScript 编译器处理代码时,它会将这些区域注释当作普通的文本注释处理,最终在编译后的输出中,这些注释会被完全移除,就像任何其他注释一样。
这些注释的存在仅是为了增强源代码的可读性和组织结构,以及在支持的代码编辑器中提供代码折叠功能。它们对实际的编译结果、运行时行为或性能没有任何影响。所以,使用区域注释是一种编辑器级别的功能,而不是语言特性。
如果你在代码中使用了 `#region` 和 `#endregion`,并且想要在编译后的代码中保留注释(例如,为了调试或维护目的),你需要确保你的编译选项允许保留注释。然而,即使是这样,区域注释也不会以任何特殊的方式被保留或解释,它们仍然会被视为普通的注释文本。
3、数据类型
截至2024年,ECMAScript(JavaScript)定义了以下原始数据类型(Primitive Types)和非原始数据类型(Non-primitive Types)。
请注意,ECMAScript 的规范随时间不断演进,但以下信息基于 ECMAScript 最近的版本,包括 ES2020 及之前的版本。
原始数据类型(Primitives)
在 TypeScript 中,根据官方文档的定义,原始类型(Primitives)是指那些不可变的基本数据类型,它们在语言层面上直接支持,并且没有内部状态可以更改。在 TypeScript(以及底层的 JavaScript)中,原始类型包括:
undefined
- 一个变量被声明但未赋值时的默认值。
null
- 一个特殊值,表示“没有对象”或“空”的值。
boolean
- 布尔值,有两个可能的值:true
和false
。
number
- 包含所有数值,包括整数和浮点数。
string
- 用于存储文本数据。
symbol
- 一种新的原始类型,用于创建唯一的键,常用于对象属性的键名,自 ECMAScript 2015 (ES6) 引入。
bigint
- 一种新的原始类型,用于表示任意精度的大整数,自 ECMAScript 2020 引入。
这些原始类型是 JavaScript 的一部分,TypeScript 继承并扩展了这些类型。
undefined
let x;
console.log(x); // undefined
console.log(typeof x); // "undefined"
null
let y = null;
console.log(y); // null
console.log(typeof y); // "object"
boolean
let truthy = true;
console.log(truthy); // true
console.log(typeof truthy); // "boolean"
number
let pi = 3.14;
console.log(pi); // 3.14
console.log(typeof pi); // "number"
string
let greeting = "Hello, world!";
console.log(greeting); // Hello, world!
console.log(typeof greeting); // "string"
symbol
let uniqueId = Symbol("id");
console.log(uniqueId); // Symbol(id)
console.log(typeof uniqueId); // "symbol"
bigint
let largeNum = 123456789012345678901234567890n;
console.log(largeNum); // 123456789012345678901234567890n
console.log(typeof largeNum); // "bigint"
非原始数据类型(Non-primitives)
在 TypeScript 中,还有一些其他类型,它们不属于原始类型,但属于 TypeScript 的类型系统,例如:
void
- 用于表示没有类型,通常用于函数没有返回值的情况。
any
和unknown
- 分别表示任何类型和未知类型,前者类型检查较弱,后者类型检查较强。
never
- 用于表示永远不会发生的类型,如无限循环函数的返回类型或抛出异常的函数的返回类型。
object
- 表示所有非原始类型的基类型,包括数组、函数、类实例等。
function
- 虽然typeof
操作符会返回"function"
,但在 TypeScript 中,函数类型是object
的子类型,它有自己的类型定义。
class
- TypeScript 中的类类型,可以继承、封装和多态。
interface
- 用于定义对象的形状,即对象应该具有的属性、方法和事件。
type
和enum
-type
用于创建类型别名,enum
用于定义一组命名的常量。
TypeScript 的类型系统允许你定义更复杂的类型,如元组类型、联合类型、交集类型、泛型类型等,这些类型构建在原始类型和对象类型之上,提供更丰富的类型表达能力。
object
let obj = { name: "Alice", age: 30 };
console.log(obj); // { name: 'Alice', age: 30 }
console.log(typeof obj); // "object"
function
function sayHello(name) {
return "Hello, " + name;
}
console.log(sayHello); // [Function: sayHello]
console.log(typeof sayHello); // "function"
array
let arr = [1, 2, 3];
console.log(arr); // [1, 2, 3]
console.log(typeof arr); // "object"
error
let err = new Error("Something went wrong");
console.log(err); // Error: Something went wrong
console.log(typeof err); // "object"
date
let now = new Date();
console.log(now); // 当前时间
console.log(typeof now); // "object"
regexp
let pattern = /hello/;
console.log(pattern); // /hello/
console.log(typeof pattern); // "object"
……
请注意,`typeof null` 返回 `"object"`,这与 `null` 实际上是原始类型的事实不符,这主要是由于历史原因造成的。
同样地,`function` 类型在 `typeof` 操作符中返回 `"function"`,但在内部实现上,函数也是对象类型的一种。
注:
在 JavaScript 中,typeof 操作符用于查询变量或表达式的类型(在 TypeScript 中,typeof的行为与 JavaScript 中相同。)。
当你使用 typeof 检查一个函数时,它会返回 "function" 而不是 "object",这是因为函数在 JavaScript 中被视为一种特殊的对象类型,但它们同时也是一种独立的类型。
在 ECMAScript 规范中,函数被定义为可调用的对象。这意味着函数不仅具有普通对象的属性和方法,而且还可以被调用。由于函数具有这种独特的双重身份,typeof 操作符特别处理函数,将其分类为 "function" 类型,以便与其他对象类型区分开来。
这里有一个简单的示例来展示这一点:
function sayHello() { console.log("Hello!"); } console.log(typeof sayHello); // 输出: "function" // 函数可以像对象一样具有属性 sayHello.name = "greetingFunction"; console.log(sayHello.name); // 输出: "greetingFunction" // 同时,函数可以被调用 sayHello(); // 输出: "Hello!"
在这个示例中,`sayHello` 函数被赋予了一个名为 `name` 的属性,这表明函数可以像普通对象一样拥有属性。然而,当使用 typeof 检查它的类型时,结果是 "function",这突出了函数的调用能力。
这种设计决策使得在 JavaScript 中处理函数变得简单且一致,同时也保持了语言的动态特性。
由于函数是第一等公民(first-class citizens),它们可以被传递给其他函数作为参数,也可以从函数中返回,甚至可以存储在变量中。
因此,将函数明确地识别为 `"function"` 类型是有意义的。
而其他的类型(array、error、date、regexp)实际上是object类型。
所以,严格来说,目前为止ECMAScript定义的类型是:undefined、null、boolean、number、string、symbol、bigint和object。
在讨论 TypeScript 的类型时,有些书籍或资料可能将
void
, 枚举类型(enum