TypeScript是什么?
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
TypeScript增加了什么?
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
- Await
以下功能是从 ECMA 2015 反向移植而来:
- 类
- 模块
- lambda 函数的箭头语法
- 可选参数以及默认参数
JavaScript 与 TypeScript 的区别
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
TypeScript开发环境搭建
- 下载安装Node.js
- 使用npm全局安装TypeScript
npm install -g typescrit - 创建一个ts文件
- 使用tsc对ts文件进行编译
tsc xxx.ts
基本类型
- 类型声明
- 类型声明是TS非常重要的一个特点
- 通过类型声明可以指定TS中变量(参数、形参)的类型
- 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
- 语法:
let 变量:类型;
let 变量:类型 = 值
function fn(参数:类型, 参数:类型):类型{...}
- 自动类型判断
- TS拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
- 所以如果你的变量声明和赋值是同时进行的,可以省略类型声明
- 类型
类型 | 例子 | 描述 |
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolen | true, false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值,定义赋值之后就不能再改变 |
any | * | 任意类型,相当于对该变量关闭了TS的类型检测,可以直接赋值给其他变量 |
unknown | * | 类型安全的any,不能直接赋值给其他变量 |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {key:value} | 任意的js对象 |
array | [1, 2, 3] | 任意的js数组 |
tuple元组 | [4, 5] | 元素,TS新增的类型,固定长度的数组 |
enum | enum{A, B} | 枚举,TS中新增的类型 |
额外知识点:
类型断言——可以用来告诉解析器变量的实际类型
语法
变量 as 类型
<类型> 变量
TypeScript编译
编译监听
TS目前不能在浏览器上直接运行,需要转化为响应的js文件。则通过 tsc xxx.ts 命令进行编译,但是我们不可能每改一次代码执行一次这个命令,所以需要对文件进行监听。
//命令行代码:
tsc xxx.ts -w // w watch的缩写
但是前面这种写法只能监听一个TS文件,监听多个文件只能开启多个终端,这不符合我们的日常开发需求。所以我们真正开发时采用以下的方法。
- 首先在根目录创建tsconfig.json配置文件,手动创建或通过 tsc - - init自动创建
- 运行tsc 编译所有在目录下的ts文件
- 运行tsc -m 监听所在目录下的所有ts文件。
tsconfig.json 文件配置
- include
用来定义需要排除在外的元素。
tsconfig.json
{
//用来指定哪些ts文件要被编译
"include":[
'./scr/**/*', //**表示任意目录, *表示任意文件
]
}
- exclude
用来指定哪些文件不需要被编译。 如果没有特殊指定, "exclude"默认情况下会排除node_modules,bower_components,jspm_packages和目录。
tsconfig.json
{
"exclude":[
"./src/banner/**/*"
]
}
- extends
继承。这个有点类似js 的引入。extends是tsconfig.json文件里的顶级属性(与compilerOptions,files,include,和exclude一样)。 extends的值是一个字符串,包含指向另一个要继承文件的路径。
tsconfig.json
{
"extends":’./config/base‘ //继承config/base.json文件
}
- files
指定一个包含相对或绝对文件路径的列表。
官网案例
{
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}
- compilerOptions (重点)
编译选项是配置文件重非常重要也比较复杂的配置选项。
在compilerOptions中包含多个子选项,用来完成对编译的配置。
东西太多 无法一一列举 详情需访问官网 或者初始化配置 tsc --init 来查看
'compilerOptions':{
"target" :"ES5", //用来设置编译的ECMAscript版本+
"module":"es6", //指定要使用的模块化规范
"lib":[ ], //lib用来指定项目中要使用的库
"outDir":"./dist:", //用来指定编译后文件所在的目录
"outFile":"./dist/app.js" //将编译的代码合并到一个文件上
"allowJs":true, //是否对JS文件进行编译
"removeComments":true //是否移除注释
"noEmit":false //不生成编译后的文件
"noEmitOnError":true //当有错误时不编译文件
"strict":true //所有严格检查的总开关 这个开启 底下的四个默认为true 推荐为true
"alwaysStrict":false //设置是否为编译后的文件开启严格模式
"noImpLicitAny":true, //不允许隐式的any类型(就是必须声明)
"noImplicitThis":true, //不允许不明确类型的this 方法中的this需指定在参数中 this:any
"strictNullChecks":true //严格检查空值 如果有可能存在null则报错
}
Webpack打包TypeScript
- 在项目目录下 通过命令行生成package.json
npm init -y //生成package.json文件
npm install -D webpack webpack-cli typescript ts-loader
- 在package.json文件的scripts字段中添加运行webpack
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
- 在项目目录下创建webpack.config.js配置文件
//引入node 路径包
const path = require('path')
//导出所有webpack配置
module.exports = {
//指定路口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//指定打包文件的目录
path: path.resolve(__dirname, 'dist'),
//打包的文件名
filename: "bundle.js",
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: "ts-loader",
//设置不需要打包的文件
exclude: /node_modules/
}
]
}
}
- 项目根目录创建tsconfig.json配置文件
{
//这是自己配置
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true
}
}
- 打包编译只需运行 npm run bulid
- html-webpack-plugin插件
打包完成我们需要在html页面显示时,需要安装这个插件,可以动态生成html页面。(以前是手动创建html文件,用script标签手动引入)
npm i -D html-webpack-plugin
之后需在webpack.config.js中配置
const htmlWebpackPlugin = require('html-webpack-plugin')
//导出所有webpack配置
module.exports = {
//配置webpack插件
plugins: [
new htmlWebpackPlugin()
]
}
- webpack配置模块格式
有的时候我们不同模块之间需要互相引入某些值,例如模块A需要引入模块B的值,但是如果两者都是TS文件,则无法执行(webpack默认不支持TS模块引入),但是可以通过相关的配置,设置允许TS文件格式进行模块导入。
需要在webpack.config.js文件中添加字段:
module.exports = {
//用来设置引用模块格式
resolve: {
extensions: ['.ts', '.js'] //表示允许.ts 和.js文件进行模块化操作(例如模块的引入 导出)
}
}
- 安装webpack的babel插件
npm i -D @babel/core @babel/preset-env babel-loader core.js
安装完之后需要在webpack.config.js中进行babel的配置:
module.exports = {
//告诉webpack不使用箭头函数
//因为webpack打包时会将代码块打包到一个自调用函数中,该函数内部使用箭头函数
//而IE不支持
evironment:{
arrowFunction:false
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: [
//配置babel
{
//指定加载器
loader: "label-loader",
//设置babel
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
{
//要兼容浏览器版本
targets: {
"ie": "11"
},
//指定corejs版本
"corejs": "4",
// 使用corejs的方式
"useBuiltIns": "usage" //按需加载
}
]
]
}
},
"ts-loader",
],
//设置不需要打包的文件
exclude: /node_modules/
}
]
},
}
Typescript进阶
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
什么是接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
例子:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。 接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。 定义的变量比接口少了一些属性是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom'
}
// Property 'age' is missing in type '{ name: string }' but required in type 'Person'.
多一些属性也是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见, 赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom'
}
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
可选属性的含义是该属性可以存在,可以不存在。
任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}
使用 [propName: string] 定义了任意属性取 string 类型的值。 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string
age?: number
[propName: string]: string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Property 'age' is incompatible with index signature.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: ‘Tom’, age: 25, gender: ‘male’ } 的类型被推断成了 { [x: string]: string | number name: string age: number gender: string },这是联合类型和接口的结合。
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
}
tom.id = 9527
// Cannot assign to 'id' because it is a read-only property.
上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。
类
传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class。
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
类的概念
这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过 new 生成
- 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自
Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat
方法,程序会自动判断出来应该如何执行 eat 存取器(getter & setter):用以改变属性的读取和赋值行为 - 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
- 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
属性和方法
class Animal {
constructor(public name) {
this.name = name
}
sayHi() {
return `My name is ${this.name}`
}
}
let a = new Animal('Jack')
console.log(a.sayHi()) // My name is Jack
类的继承
class Cat extends Animal {
constructor(name) {
super(name) // 调用父类的 constructor(name)
console.log(this.name)
}
sayHi() {
return 'Meow, ' + super.sayHi() // 调用父类的 sayHi()
}
}
let c = new Cat('Tom') // Tom
console.log(c.sayHi()) // Meow, My name is Tom
存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name
}
get name() {
return 'Jack'
}
set name(value) {
console.log('setter: ' + value)
}
}
let a = new Animal('Kitty') // setter: Kitty
a.name = 'Tom' // setter: Tom
console.log(a.name) // Jack
静态方法
使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
static isAnimal(a) {
return a instanceof Animal
}
}
let a = new Animal('Jack')
Animal.isAnimal(a) // true
a.isAnimal(a) // TypeError: a.isAnimal is not a function
实例属性
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
class Animal {
name = 'Jack'
constructor() {
// ...
}
}
let a = new Animal()
console.log(a.name) // Jack
静态属性
ES7 提案中,可以使用 static 定义一个静态属性:
class Animal {
static num = 42
constructor() {
// ...
}
}
console.log(Animal.num) // 42
TypeScript 中类的用法
public private 和 protected
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
抽象类
abstract 用于定义抽象类和其中的抽象方法。
什么是抽象类?
首先,抽象类是不允许被实例化的;其次,抽象类中的抽象方法必须被子类实现:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
let a = new Animal('Jack')
// Cannot create an instance of an abstract class.
上面的例子中,我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。
下面是一个正确使用抽象类的例子:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`)
}
}
let cat = new Cat('Tom')
泛型
泛型定义
泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持
泛型函数
// 只能返回string类型的数据
function getData(value:string):string{
return value
}
// 可返回string和number类型的数据 (代码冗余)
function getData1(value:string):string{
return value
}
function getData2(value:number):number{
return value
}
// any 相当于放弃了类型检测
function getData3(value:any):any{
return value
}
// 实现传入什么类型返回什么类型
// T 表示泛型,具体什么类型是调用这个方法的时候绝定的(也可以是其它字母)
function getData4<T>(value:T):T{ // 指定了返回的类型
// 传入什么 返回什么
return value
}
getData4<number>(123) //使用
getData4<string>("xxx")
// 返回任意类型
function getData5<T>(value:T):any{
return "dd"
}
getData5<number>(123) //使用
getData5<string>("dd")
泛型类
比如有个最小堆算法, 需要同时支持返回数字和字符串2种类型
只支持 number类型
class MinClass{
public list:number[] = [];
add(num:number){
this.list.push(num)
}
min():number{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass()
m.add(22)
m.add(2)
m.add(66)
console.log(m.min()) //2
可支持多类型 对不特定数据类型的支持
class MinClass<T>{
public list:T[] = [];
add(num:T):void{
this.list.push(num)
}
min():T{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass<string>() //实例化类 并且制定了类的 T代表类型
m.add("1")
m.add("12")
m.add("23")
console.log(m.min()) //1
把类当做参数的泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb{
add(user:user):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb()
sql.add(u)
泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb<User>()
sql.add(u)
参数传对象
class User{
name: string | undefined;
pwd: string | undefined;
constructor(params:{
name: string | undefined,
pwd: string | undefined
}){
this.name = params.name
this.pwd = params.pwd
}
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User({
name:"u-name",
pwd:"u-123"
})
var sql = new MysqlDb<User>()
sql.add(u)
泛型接口
函数接口
interface ConFigFu{ // 函数类型接口
(value1:string,value2:string):string
}
var setData:ConFigFu = function(value1:string,value2:string):string{
return value1 + value2
}
setData("xx","yy")
泛型接口
第一种写法
interface ConFigFu{
<T>(value1:T,value2:T):T
}
var setData:ConFigFu = function<T>(value1:T,value2:T):T{
return value1
}
setData<string>("xx","yy")
第二种写法
interface ConFigFu<T>{
(value1:T,value2:T):T
}
var setData = function<T>(value1:T,value2:T):T{
return value1
}
var getdata:ConFigFu<string> = setData
getdata("xx","yy")
TypeScript是什么?
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
TypeScript增加了什么?
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
- Await
以下功能是从 ECMA 2015 反向移植而来:
- 类
- 模块
- lambda 函数的箭头语法
- 可选参数以及默认参数
JavaScript 与 TypeScript 的区别
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
TypeScript开发环境搭建
- 下载安装Node.js
- 使用npm全局安装TypeScript
npm install -g typescrit - 创建一个ts文件
- 使用tsc对ts文件进行编译
tsc xxx.ts
基本类型
- 类型声明
- 类型声明是TS非常重要的一个特点
- 通过类型声明可以指定TS中变量(参数、形参)的类型
- 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
- 语法:
let 变量:类型;
let 变量:类型 = 值
function fn(参数:类型, 参数:类型):类型{...}
- 自动类型判断
- TS拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
- 所以如果你的变量声明和赋值是同时进行的,可以省略类型声明
- 类型
类型 | 例子 | 描述 |
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolen | true, false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值,定义赋值之后就不能再改变 |
any | * | 任意类型,相当于对该变量关闭了TS的类型检测,可以直接赋值给其他变量 |
unknown | * | 类型安全的any,不能直接赋值给其他变量 |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {key:value} | 任意的js对象 |
array | [1, 2, 3] | 任意的js数组 |
tuple元组 | [4, 5] | 元素,TS新增的类型,固定长度的数组 |
enum | enum{A, B} | 枚举,TS中新增的类型 |
额外知识点:
类型断言——可以用来告诉解析器变量的实际类型
语法
变量 as 类型
<类型> 变量
TypeScript编译
编译监听
TS目前不能在浏览器上直接运行,需要转化为响应的js文件。则通过 tsc xxx.ts 命令进行编译,但是我们不可能每改一次代码执行一次这个命令,所以需要对文件进行监听。
//命令行代码:
tsc xxx.ts -w // w watch的缩写
但是前面这种写法只能监听一个TS文件,监听多个文件只能开启多个终端,这不符合我们的日常开发需求。所以我们真正开发时采用以下的方法。
- 首先在根目录创建tsconfig.json配置文件,手动创建或通过 tsc - - init自动创建
- 运行tsc 编译所有在目录下的ts文件
- 运行tsc -m 监听所在目录下的所有ts文件。
tsconfig.json 文件配置
- include
用来定义需要排除在外的元素。
tsconfig.json
{
//用来指定哪些ts文件要被编译
"include":[
'./scr/**/*', //**表示任意目录, *表示任意文件
]
}
- exclude
用来指定哪些文件不需要被编译。 如果没有特殊指定, "exclude"默认情况下会排除node_modules,bower_components,jspm_packages和目录。
tsconfig.json
{
"exclude":[
"./src/banner/**/*"
]
}
- extends
继承。这个有点类似js 的引入。extends是tsconfig.json文件里的顶级属性(与compilerOptions,files,include,和exclude一样)。 extends的值是一个字符串,包含指向另一个要继承文件的路径。
tsconfig.json
{
"extends":’./config/base‘ //继承config/base.json文件
}
- files
指定一个包含相对或绝对文件路径的列表。
官网案例
{
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}
- compilerOptions (重点)
编译选项是配置文件重非常重要也比较复杂的配置选项。
在compilerOptions中包含多个子选项,用来完成对编译的配置。
东西太多 无法一一列举 详情需访问官网 或者初始化配置 tsc --init 来查看
'compilerOptions':{
"target" :"ES5", //用来设置编译的ECMAscript版本+
"module":"es6", //指定要使用的模块化规范
"lib":[ ], //lib用来指定项目中要使用的库
"outDir":"./dist:", //用来指定编译后文件所在的目录
"outFile":"./dist/app.js" //将编译的代码合并到一个文件上
"allowJs":true, //是否对JS文件进行编译
"removeComments":true //是否移除注释
"noEmit":false //不生成编译后的文件
"noEmitOnError":true //当有错误时不编译文件
"strict":true //所有严格检查的总开关 这个开启 底下的四个默认为true 推荐为true
"alwaysStrict":false //设置是否为编译后的文件开启严格模式
"noImpLicitAny":true, //不允许隐式的any类型(就是必须声明)
"noImplicitThis":true, //不允许不明确类型的this 方法中的this需指定在参数中 this:any
"strictNullChecks":true //严格检查空值 如果有可能存在null则报错
}
Webpack打包TypeScript
- 在项目目录下 通过命令行生成package.json
npm init -y //生成package.json文件
npm install -D webpack webpack-cli typescript ts-loader
- 在package.json文件的scripts字段中添加运行webpack
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
- 在项目目录下创建webpack.config.js配置文件
//引入node 路径包
const path = require('path')
//导出所有webpack配置
module.exports = {
//指定路口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//指定打包文件的目录
path: path.resolve(__dirname, 'dist'),
//打包的文件名
filename: "bundle.js",
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: "ts-loader",
//设置不需要打包的文件
exclude: /node_modules/
}
]
}
}
- 项目根目录创建tsconfig.json配置文件
{
//这是自己配置
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true
}
}
- 打包编译只需运行 npm run bulid
- html-webpack-plugin插件
打包完成我们需要在html页面显示时,需要安装这个插件,可以动态生成html页面。(以前是手动创建html文件,用script标签手动引入)
npm i -D html-webpack-plugin
之后需在webpack.config.js中配置
const htmlWebpackPlugin = require('html-webpack-plugin')
//导出所有webpack配置
module.exports = {
//配置webpack插件
plugins: [
new htmlWebpackPlugin()
]
}
- webpack配置模块格式
有的时候我们不同模块之间需要互相引入某些值,例如模块A需要引入模块B的值,但是如果两者都是TS文件,则无法执行(webpack默认不支持TS模块引入),但是可以通过相关的配置,设置允许TS文件格式进行模块导入。
需要在webpack.config.js文件中添加字段:
module.exports = {
//用来设置引用模块格式
resolve: {
extensions: ['.ts', '.js'] //表示允许.ts 和.js文件进行模块化操作(例如模块的引入 导出)
}
}
- 安装webpack的babel插件
npm i -D @babel/core @babel/preset-env babel-loader core.js
安装完之后需要在webpack.config.js中进行babel的配置:
module.exports = {
//告诉webpack不使用箭头函数
//因为webpack打包时会将代码块打包到一个自调用函数中,该函数内部使用箭头函数
//而IE不支持
evironment:{
arrowFunction:false
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: [
//配置babel
{
//指定加载器
loader: "label-loader",
//设置babel
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
{
//要兼容浏览器版本
targets: {
"ie": "11"
},
//指定corejs版本
"corejs": "4",
// 使用corejs的方式
"useBuiltIns": "usage" //按需加载
}
]
]
}
},
"ts-loader",
],
//设置不需要打包的文件
exclude: /node_modules/
}
]
},
}
Typescript进阶
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
什么是接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
例子:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。 接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。 定义的变量比接口少了一些属性是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom'
}
// Property 'age' is missing in type '{ name: string }' but required in type 'Person'.
多一些属性也是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见, 赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom'
}
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
可选属性的含义是该属性可以存在,可以不存在。
任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}
使用 [propName: string] 定义了任意属性取 string 类型的值。 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string
age?: number
[propName: string]: string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Property 'age' is incompatible with index signature.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: ‘Tom’, age: 25, gender: ‘male’ } 的类型被推断成了 { [x: string]: string | number name: string age: number gender: string },这是联合类型和接口的结合。
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
}
tom.id = 9527
// Cannot assign to 'id' because it is a read-only property.
上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。
类
传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class。
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
类的概念
这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过 new 生成
- 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自
Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat
方法,程序会自动判断出来应该如何执行 eat 存取器(getter & setter):用以改变属性的读取和赋值行为 - 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
- 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
属性和方法
class Animal {
constructor(public name) {
this.name = name
}
sayHi() {
return `My name is ${this.name}`
}
}
let a = new Animal('Jack')
console.log(a.sayHi()) // My name is Jack
类的继承
class Cat extends Animal {
constructor(name) {
super(name) // 调用父类的 constructor(name)
console.log(this.name)
}
sayHi() {
return 'Meow, ' + super.sayHi() // 调用父类的 sayHi()
}
}
let c = new Cat('Tom') // Tom
console.log(c.sayHi()) // Meow, My name is Tom
存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name
}
get name() {
return 'Jack'
}
set name(value) {
console.log('setter: ' + value)
}
}
let a = new Animal('Kitty') // setter: Kitty
a.name = 'Tom' // setter: Tom
console.log(a.name) // Jack
静态方法
使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
static isAnimal(a) {
return a instanceof Animal
}
}
let a = new Animal('Jack')
Animal.isAnimal(a) // true
a.isAnimal(a) // TypeError: a.isAnimal is not a function
实例属性
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
class Animal {
name = 'Jack'
constructor() {
// ...
}
}
let a = new Animal()
console.log(a.name) // Jack
静态属性
ES7 提案中,可以使用 static 定义一个静态属性:
class Animal {
static num = 42
constructor() {
// ...
}
}
console.log(Animal.num) // 42
TypeScript 中类的用法
public private 和 protected
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
抽象类
abstract 用于定义抽象类和其中的抽象方法。
什么是抽象类?
首先,抽象类是不允许被实例化的;其次,抽象类中的抽象方法必须被子类实现:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
let a = new Animal('Jack')
// Cannot create an instance of an abstract class.
上面的例子中,我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。
下面是一个正确使用抽象类的例子:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`)
}
}
let cat = new Cat('Tom')
泛型
泛型定义
泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持
泛型函数
// 只能返回string类型的数据
function getData(value:string):string{
return value
}
// 可返回string和number类型的数据 (代码冗余)
function getData1(value:string):string{
return value
}
function getData2(value:number):number{
return value
}
// any 相当于放弃了类型检测
function getData3(value:any):any{
return value
}
// 实现传入什么类型返回什么类型
// T 表示泛型,具体什么类型是调用这个方法的时候绝定的(也可以是其它字母)
function getData4<T>(value:T):T{ // 指定了返回的类型
// 传入什么 返回什么
return value
}
getData4<number>(123) //使用
getData4<string>("xxx")
// 返回任意类型
function getData5<T>(value:T):any{
return "dd"
}
getData5<number>(123) //使用
getData5<string>("dd")
泛型类
比如有个最小堆算法, 需要同时支持返回数字和字符串2种类型
只支持 number类型
class MinClass{
public list:number[] = [];
add(num:number){
this.list.push(num)
}
min():number{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass()
m.add(22)
m.add(2)
m.add(66)
console.log(m.min()) //2
可支持多类型 对不特定数据类型的支持
class MinClass<T>{
public list:T[] = [];
add(num:T):void{
this.list.push(num)
}
min():T{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass<string>() //实例化类 并且制定了类的 T代表类型
m.add("1")
m.add("12")
m.add("23")
console.log(m.min()) //1
把类当做参数的泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb{
add(user:user):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb()
sql.add(u)
泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb<User>()
sql.add(u)
参数传对象
class User{
name: string | undefined;
pwd: string | undefined;
constructor(params:{
name: string | undefined,
pwd: string | undefined
}){
this.name = params.name
this.pwd = params.pwd
}
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User({
name:"u-name",
pwd:"u-123"
})
var sql = new MysqlDb<User>()
sql.add(u)
泛型接口
函数接口
interface ConFigFu{ // 函数类型接口
(value1:string,value2:string):string
}
var setData:ConFigFu = function(value1:string,value2:string):string{
return value1 + value2
}
setData("xx","yy")
泛型接口
第一种写法
interface ConFigFu{
<T>(value1:T,value2:T):T
}
var setData:ConFigFu = function<T>(value1:T,value2:T):T{
return value1
}
setData<string>("xx","yy")
第二种写法
interface ConFigFu<T>{
(value1:T,value2:T):T
}
var setData = function<T>(value1:T,value2:T):T{
return value1
}
var getdata:ConFigFu<string> = setData
getdata("xx","yy")
TypeScript是什么?
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
TypeScript增加了什么?
- 类型批注和编译时类型检查
- 类型推断
- 类型擦除
- 接口
- 枚举
- Mixin
- 泛型编程
- 名字空间
- 元组
- Await
以下功能是从 ECMA 2015 反向移植而来:
- 类
- 模块
- lambda 函数的箭头语法
- 可选参数以及默认参数
JavaScript 与 TypeScript 的区别
TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。
TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
TypeScript开发环境搭建
- 下载安装Node.js
- 使用npm全局安装TypeScript
npm install -g typescrit - 创建一个ts文件
- 使用tsc对ts文件进行编译
tsc xxx.ts
基本类型
- 类型声明
- 类型声明是TS非常重要的一个特点
- 通过类型声明可以指定TS中变量(参数、形参)的类型
- 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错
- 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值
- 语法:
let 变量:类型;
let 变量:类型 = 值
function fn(参数:类型, 参数:类型):类型{...}
- 自动类型判断
- TS拥有自动的类型判断机制
- 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
- 所以如果你的变量声明和赋值是同时进行的,可以省略类型声明
- 类型
类型 | 例子 | 描述 |
number | 1, -33, 2.5 | 任意数字 |
string | ‘hi’, “hi”, hi | 任意字符串 |
boolen | true, false | 布尔值true或false |
字面量 | 其本身 | 限制变量的值就是该字面量的值,定义赋值之后就不能再改变 |
any | * | 任意类型,相当于对该变量关闭了TS的类型检测,可以直接赋值给其他变量 |
unknown | * | 类型安全的any,不能直接赋值给其他变量 |
void | 空值(undefined) | 没有值(或undefined) |
never | 没有值 | 不能是任何值 |
object | {key:value} | 任意的js对象 |
array | [1, 2, 3] | 任意的js数组 |
tuple元组 | [4, 5] | 元素,TS新增的类型,固定长度的数组 |
enum | enum{A, B} | 枚举,TS中新增的类型 |
额外知识点:
类型断言——可以用来告诉解析器变量的实际类型
语法
变量 as 类型
<类型> 变量
TypeScript编译
编译监听
TS目前不能在浏览器上直接运行,需要转化为响应的js文件。则通过 tsc xxx.ts 命令进行编译,但是我们不可能每改一次代码执行一次这个命令,所以需要对文件进行监听。
//命令行代码:
tsc xxx.ts -w // w watch的缩写
但是前面这种写法只能监听一个TS文件,监听多个文件只能开启多个终端,这不符合我们的日常开发需求。所以我们真正开发时采用以下的方法。
- 首先在根目录创建tsconfig.json配置文件,手动创建或通过 tsc - - init自动创建
- 运行tsc 编译所有在目录下的ts文件
- 运行tsc -m 监听所在目录下的所有ts文件。
tsconfig.json 文件配置
- include
用来定义需要排除在外的元素。
tsconfig.json
{
//用来指定哪些ts文件要被编译
"include":[
'./scr/**/*', //**表示任意目录, *表示任意文件
]
}
- exclude
用来指定哪些文件不需要被编译。 如果没有特殊指定, "exclude"默认情况下会排除node_modules,bower_components,jspm_packages和目录。
tsconfig.json
{
"exclude":[
"./src/banner/**/*"
]
}
- extends
继承。这个有点类似js 的引入。extends是tsconfig.json文件里的顶级属性(与compilerOptions,files,include,和exclude一样)。 extends的值是一个字符串,包含指向另一个要继承文件的路径。
tsconfig.json
{
"extends":’./config/base‘ //继承config/base.json文件
}
- files
指定一个包含相对或绝对文件路径的列表。
官网案例
{
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}
- compilerOptions (重点)
编译选项是配置文件重非常重要也比较复杂的配置选项。
在compilerOptions中包含多个子选项,用来完成对编译的配置。
东西太多 无法一一列举 详情需访问官网 或者初始化配置 tsc --init 来查看
'compilerOptions':{
"target" :"ES5", //用来设置编译的ECMAscript版本+
"module":"es6", //指定要使用的模块化规范
"lib":[ ], //lib用来指定项目中要使用的库
"outDir":"./dist:", //用来指定编译后文件所在的目录
"outFile":"./dist/app.js" //将编译的代码合并到一个文件上
"allowJs":true, //是否对JS文件进行编译
"removeComments":true //是否移除注释
"noEmit":false //不生成编译后的文件
"noEmitOnError":true //当有错误时不编译文件
"strict":true //所有严格检查的总开关 这个开启 底下的四个默认为true 推荐为true
"alwaysStrict":false //设置是否为编译后的文件开启严格模式
"noImpLicitAny":true, //不允许隐式的any类型(就是必须声明)
"noImplicitThis":true, //不允许不明确类型的this 方法中的this需指定在参数中 this:any
"strictNullChecks":true //严格检查空值 如果有可能存在null则报错
}
Webpack打包TypeScript
- 在项目目录下 通过命令行生成package.json
npm init -y //生成package.json文件
npm install -D webpack webpack-cli typescript ts-loader
- 在package.json文件的scripts字段中添加运行webpack
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
- 在项目目录下创建webpack.config.js配置文件
//引入node 路径包
const path = require('path')
//导出所有webpack配置
module.exports = {
//指定路口文件
entry: "./src/index.ts",
//指定打包文件所在目录
output: {
//指定打包文件的目录
path: path.resolve(__dirname, 'dist'),
//打包的文件名
filename: "bundle.js",
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: "ts-loader",
//设置不需要打包的文件
exclude: /node_modules/
}
]
}
}
- 项目根目录创建tsconfig.json配置文件
{
//这是自己配置
"compilerOptions": {
"module": "ES2015",
"target": "ES2015",
"strict": true
}
}
- 打包编译只需运行 npm run bulid
- html-webpack-plugin插件
打包完成我们需要在html页面显示时,需要安装这个插件,可以动态生成html页面。(以前是手动创建html文件,用script标签手动引入)
npm i -D html-webpack-plugin
之后需在webpack.config.js中配置
const htmlWebpackPlugin = require('html-webpack-plugin')
//导出所有webpack配置
module.exports = {
//配置webpack插件
plugins: [
new htmlWebpackPlugin()
]
}
- webpack配置模块格式
有的时候我们不同模块之间需要互相引入某些值,例如模块A需要引入模块B的值,但是如果两者都是TS文件,则无法执行(webpack默认不支持TS模块引入),但是可以通过相关的配置,设置允许TS文件格式进行模块导入。
需要在webpack.config.js文件中添加字段:
module.exports = {
//用来设置引用模块格式
resolve: {
extensions: ['.ts', '.js'] //表示允许.ts 和.js文件进行模块化操作(例如模块的引入 导出)
}
}
- 安装webpack的babel插件
npm i -D @babel/core @babel/preset-env babel-loader core.js
安装完之后需要在webpack.config.js中进行babel的配置:
module.exports = {
//告诉webpack不使用箭头函数
//因为webpack打包时会将代码块打包到一个自调用函数中,该函数内部使用箭头函数
//而IE不支持
evironment:{
arrowFunction:false
},
//指定webpack打包时使用的模块
module: {
//指定要加载的规则
rules: [
{ //指定规则生效的文件
test: /\.ts$/,
//使用ts-loader打包ts文件
use: [
//配置babel
{
//指定加载器
loader: "label-loader",
//设置babel
options: {
//设置预定义的环境
presets: [
[
//指定环境的插件
"@babel/preset-env",
{
//要兼容浏览器版本
targets: {
"ie": "11"
},
//指定corejs版本
"corejs": "4",
// 使用corejs的方式
"useBuiltIns": "usage" //按需加载
}
]
]
}
},
"ts-loader",
],
//设置不需要打包的文件
exclude: /node_modules/
}
]
},
}
Typescript进阶
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
什么是接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
例子:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。 接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。 定义的变量比接口少了一些属性是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom'
}
// Property 'age' is missing in type '{ name: string }' but required in type 'Person'.
多一些属性也是不允许的:
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见, 赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom'
}
interface Person {
name: string
age?: number
}
let tom: Person = {
name: 'Tom',
age: 25
}
可选属性的含义是该属性可以存在,可以不存在。
任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
name: 'Tom',
gender: 'male'
}
使用 [propName: string] 定义了任意属性取 string 类型的值。 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string
age?: number
[propName: string]: string
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
}
// Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.
// Type '{ name: string age: number gender: string }' is not assignable to type 'Person'.
// Property 'age' is incompatible with index signature.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: ‘Tom’, age: 25, gender: ‘male’ } 的类型被推断成了 { [x: string]: string | number name: string age: number gender: string },这是联合类型和接口的结合。
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
interface Person {
readonly id: number
name: string
age?: number
[propName: string]: any
}
let tom: Person = {
id: 89757,
name: 'Tom',
gender: 'male'
}
tom.id = 9527
// Cannot assign to 'id' because it is a read-only property.
上例中,使用 readonly 定义的属性 id 初始化后,又被赋值了,所以报错了。
类
传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。而在 ES6 中,我们终于迎来了 class。
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法。
类的概念
这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过 new 生成
- 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 Cat 和 Dog 都继承自
Animal,但是分别实现了自己的 eat 方法。此时针对某一个实例,我们无需了解它是 Cat 还是 Dog,就可以直接调用 eat
方法,程序会自动判断出来应该如何执行 eat 存取器(getter & setter):用以改变属性的读取和赋值行为 - 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 public 表示公有属性或方法
- 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
属性和方法
class Animal {
constructor(public name) {
this.name = name
}
sayHi() {
return `My name is ${this.name}`
}
}
let a = new Animal('Jack')
console.log(a.sayHi()) // My name is Jack
类的继承
class Cat extends Animal {
constructor(name) {
super(name) // 调用父类的 constructor(name)
console.log(this.name)
}
sayHi() {
return 'Meow, ' + super.sayHi() // 调用父类的 sayHi()
}
}
let c = new Cat('Tom') // Tom
console.log(c.sayHi()) // Meow, My name is Tom
存取器
使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name
}
get name() {
return 'Jack'
}
set name(value) {
console.log('setter: ' + value)
}
}
let a = new Animal('Kitty') // setter: Kitty
a.name = 'Tom' // setter: Tom
console.log(a.name) // Jack
静态方法
使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
static isAnimal(a) {
return a instanceof Animal
}
}
let a = new Animal('Jack')
Animal.isAnimal(a) // true
a.isAnimal(a) // TypeError: a.isAnimal is not a function
实例属性
ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
class Animal {
name = 'Jack'
constructor() {
// ...
}
}
let a = new Animal()
console.log(a.name) // Jack
静态属性
ES7 提案中,可以使用 static 定义一个静态属性:
class Animal {
static num = 42
constructor() {
// ...
}
}
console.log(Animal.num) // 42
TypeScript 中类的用法
public private 和 protected
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
抽象类
abstract 用于定义抽象类和其中的抽象方法。
什么是抽象类?
首先,抽象类是不允许被实例化的;其次,抽象类中的抽象方法必须被子类实现:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
let a = new Animal('Jack')
// Cannot create an instance of an abstract class.
上面的例子中,我们定义了一个类 Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,所以编译报错了。
下面是一个正确使用抽象类的例子:
abstract class Animal {
public name
public constructor(name) {
this.name = name
}
public abstract sayHi()
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`)
}
}
let cat = new Cat('Tom')
泛型
泛型定义
泛型就是解决 类 接口 方法的复用性、以及对不特定数据类型的支持
泛型函数
// 只能返回string类型的数据
function getData(value:string):string{
return value
}
// 可返回string和number类型的数据 (代码冗余)
function getData1(value:string):string{
return value
}
function getData2(value:number):number{
return value
}
// any 相当于放弃了类型检测
function getData3(value:any):any{
return value
}
// 实现传入什么类型返回什么类型
// T 表示泛型,具体什么类型是调用这个方法的时候绝定的(也可以是其它字母)
function getData4<T>(value:T):T{ // 指定了返回的类型
// 传入什么 返回什么
return value
}
getData4<number>(123) //使用
getData4<string>("xxx")
// 返回任意类型
function getData5<T>(value:T):any{
return "dd"
}
getData5<number>(123) //使用
getData5<string>("dd")
泛型类
比如有个最小堆算法, 需要同时支持返回数字和字符串2种类型
只支持 number类型
class MinClass{
public list:number[] = [];
add(num:number){
this.list.push(num)
}
min():number{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass()
m.add(22)
m.add(2)
m.add(66)
console.log(m.min()) //2
可支持多类型 对不特定数据类型的支持
class MinClass<T>{
public list:T[] = [];
add(num:T):void{
this.list.push(num)
}
min():T{ // 求最小数
var minNum = this.list[0]
for(var i = 0; i < this.list.length; i++){
if(minNum > this.list[i]){
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass<string>() //实例化类 并且制定了类的 T代表类型
m.add("1")
m.add("12")
m.add("23")
console.log(m.min()) //1
把类当做参数的泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb{
add(user:user):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb()
sql.add(u)
泛型类
class User{
name: string | undefined;
pwd: string | undefined
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User()
u.name = "u-name"
u.pwd = "u-123"
var sql = new MysqlDb<User>()
sql.add(u)
参数传对象
class User{
name: string | undefined;
pwd: string | undefined;
constructor(params:{
name: string | undefined,
pwd: string | undefined
}){
this.name = params.name
this.pwd = params.pwd
}
}
class MysqlDb<T>{
add(user:T):boolean{
return true
}
}
var u = new User({
name:"u-name",
pwd:"u-123"
})
var sql = new MysqlDb<User>()
sql.add(u)
泛型接口
函数接口
interface ConFigFu{ // 函数类型接口
(value1:string,value2:string):string
}
var setData:ConFigFu = function(value1:string,value2:string):string{
return value1 + value2
}
setData("xx","yy")
泛型接口
第一种写法
interface ConFigFu{
<T>(value1:T,value2:T):T
}
var setData:ConFigFu = function<T>(value1:T,value2:T):T{
return value1
}
setData<string>("xx","yy")
第二种写法
interface ConFigFu<T>{
(value1:T,value2:T):T
}
var setData = function<T>(value1:T,value2:T):T{
return value1
}
var getdata:ConFigFu<string> = setData
getdata("xx","yy")