目录
js与ts
微软创建的typescript
- ts是以js为基础构建的语言
- ts完全兼容js,只是在js的基础上又增添了一些功能
- ts不可以直接在浏览器运行,需要先将ts编译为js再在浏览器运行~
ts开发系统搭建
ts不能直接在浏览器运行,要想运行ts文件需要先将ts文件编译为js文件再在浏览器运行。
因此需要先安装一个ts编译器
安装ts编译器
- 安装ts编译器
npm i -g typescript
- 检验是否安装成功
出现下述命令而不是出现你所说的命令找不到字样即安装成功tsc // 编译文件
常用命令
- 将ts文件编译为js文件
// 每次更改ts文件都需要重新编译 tsc 文件名
编译完成之后会在同级目录下出现同名的js文件,如下//热更新 编译之后ts文件更新之后会重新编译ts文件 tsc 文件名 -w
tips:默认会编译为es3版本的js文件
ts类型声明
变量声明赋值
javascript是弱语言类型,变量的数据类型是动态的(也可以说变量没有数据类型)
let a; // 当前a的数据类型为undefined
a = 100 // 当前a的数据类型为number
a = '100' // 当前a的数据类型为string
并且javascript允许变量进行隐士转化与强制转换,这在运行过程中会产生一系列的问题,如下:
// 定义变量a, 希望a是一个number类型 后续用于加减运算
let a
...
// 在某个情况下给a赋值为字符串了。。
a = 'hello word'
// 进行运算时会得到NaN或字符串-与期望不符
typescript可以更好的处理上述问题,ts语法如下:
-
声明变量不赋值
let 变量名 let 变量名:类型 let a // a的类型为any let num: number // num的类型为number
-
声明变量且赋值:在声明变量并且赋值的情况下,就不需要显示声明变量的类型了,如下:
let num = 10 // 隐士将num赋值为number类型了 num = 'string' // 此时有报错:不能将类型“string”分配给类型“number”
tips: ts文件即使有报错,在编译时也能正常编译为js文件
函数参数赋值
在javascript中形参与实参的数量可以不一致
// 在接收参数时,若是形参数量多于实参数量,多于的参数值为undefined
function addNum(a, b, c){
// 1+2+undefined=NaN
return a + b + c
}
console.log(addNum(1,2)) // NaN
// 在接收参数时,若是形参数量大于实参数量,则多余参数不接收(和没有一样)
function addNum(a, b){
return a + b
}
console.log(addNum(1,2,3)) // 3
在jsvascript中函数的参数可以是任意数据类型。
以上越是在大型项目就会越导致一些数据类型、传参的混乱。
在typescript中形参的数量与实参的数量要 数量一致,如下:
function fun(n1,n2){
return n1+n2
}
fun(1) // 此处有报错:应有 2 个参数,但获得 1 个
若是明确接收参数的类型,可以作出规范,如下:
function fun(n1:number,n2:number){
return n1+n2
}
fun(1,'string') // 此处有报错: 类型“string”的参数不能赋给类型“number”的参数
若是想规定返回值的类型,则可以进行如下操作
funciton 函数名():返回值类型{
}
类型分类
在上面进行类型声明时,仅仅是以number类型进行说明,那么在ts中一共有几种类型呢,如下
总结
类型 | 描述 |
---|---|
any | 任意类型 |
unknown | 类型安全的any |
void | 空值-表示没有值或者undefined |
never | 没有值-不能是任何值 |
字面量 | 固定值 |
number | 任意数字 |
string | 任意字符串 |
boolean | 布尔值为true或false |
object | 任意的js对象 |
Function | 任意的函数 |
array | 任意的js数组 |
tuple | 固定长度数组(元组) |
enum | 枚举 |
any 与unknown
-
any: 任意类型
在声明变量时 若是变量没有赋值且没有声明类型时。隐式将变量声明为any类型,此后该变量可以赋值为任意类型let a; // 类型为any a = 10 a = 'string' ...
any类型同样可以显示声明
let a:any; a = 10 a = 'string' ...
tips: 若是一个变量设置为any类型相当于关闭了ts对该变量的类型检测
-
unknown:unknown是类型安全的any。
let a:unknown // 可以赋值为任意数据类型 a = 10 a = 'string'
如上:在类型赋值方面 any类型与unknown类型都是一样的,那么为什么说unknown类型比any类型更加安全呢?
let a; a = 'string'; let b = 100; // b是number类型 b = a // 不报错 let c:unknown; c = 'string'; let d = 100; // d是number类型 d = c //此处报错 不能将类型“unknown”分配给类型“string”
通过上述例子可以看出,
any类型的变量可以赋值给任意数据类型,相当于破坏了其他变量的类型检测; unknown类型的变量不能赋值给其他类型的变量,保证其他变量的类型检测不受当前数据的干扰;
tips: any类型霍霍自己也霍霍别人,unknown类型仅能霍霍自己~
但是此时存在一个问题,比如此时恰好存在一个unknown类型的变量,此时赋值的值为stirng类型,但是此时改值也不能复制给string类型的变量,如下
let c:unknown; c = 'stirng1'; let d = 'string2'; // d是string类型 d = c //此处报错 不能将类型“unknown”分配给类型“string”
那么此处应该如何操作进行赋值呢?
- 方法1: 类型判断
// 因为c是unknown(未知)类型,对类型加以判断,符合之后再赋值即可 if(typeof c === 'string'){ d = c }
- 方法2:类型断言
但是类型断言就是告诉浏览器错误信息,同样有效,如下:d = c as string; // 类型断言-> 告诉编译器c是stirng类型
let c:unknown; c = 'stirng1'; let e = 100 e = c as number; // 告诉编译器c是number类型(但是实际c是string类型) console.log(c,e) // stirng1 stirng1
- 方法4:泛形,和类型断言存在相同问题
let c:unknown; c = 'stirng1'; let d = 'string2'; // d是string类型 let e = 100 d = <string>c; // 告诉编译器c是string类型 e = <number>c; // 告诉编译器c是number类型(实际是stirng类型) console.log(c,d,e) // stirng1 stirng1 stirng1
- 方法1: 类型判断
void 与 never
-
void:表示没有值 或 值为undefined
若是没有显示声明函数的返回值类型,则ts会根据函数的返回值类型隐士声明函数的返回值类型
// fun1的返回值类型为void类型 function fun1(){ return;// 没有返回值 } // fun2的返回值类型为number类型 function fun2(){ return 123; } ...
函数也可显示声明函数的返回值类型
若是返回值设置为never类型,则不能有任何返回值包括undefined
function fun2(): never{ }. // 此处报错:返回“从不”的函数不能具有可访问的终结点
若是类似这种专门报错的函数,可以设置返回值为never
function fun2(): never{ throw new Error('错误') }
字面量
字面量类似于常量,如下:
let a:10
a = 100 //不能将类型“100”分配给类型“10” 相当于a为常量10了
let a:10 | 20 // 此时a仅能赋值10或者20
let a:string | number // 此时a能赋值数字或者字符串类型的值
对象
一切皆对象,在实际开发中我们一般不会去约定一个变量为一个对象,而是去约定更细微的点如:对象的属性、函数传参、数组的元素等。
object
-
object 表示可以是任意js对象,但是这样做类型声明意义不大,因为在js中对象、数组、函数都是对象类型
let n1:object n1 = {} n1 = function(){} n1 = []
上述赋值都不会报错~
但是我们可通过下述方式来规范对象的属性
- 变量的类型为1个对象且该对象没有任何属性
// 本质就是字面量 变量名:{}
- 变量的类型为1个对象且该对象有且仅有name一个属性,属性值为字符串类型
变量名:{name: string}
- 变量的类型为1个对象且该对象可能存在name属性
// ?表示可以有可以没有 变量名:{name?: string}
- 此时变量必须满足既有name属性又有age属性
变量名:{name:string} & {age: number}
- 此变量一定存在name属性但是也可能存在其他属性
// [] 表示可省的 // [属性名:属性名类型]:属性值类型 // [propsname: string]: any 表示属性名为string类型而属性值为任意类型 变量名:{name: string,[propsname: string]: any}
举例说明
let n1:{name: string} n1 = {} // 类型 "{}" 中缺少属性 "name",但类型 "{ name: string; }" 中需要该属性 n1 = {name: 'chaochao'} n1 = {name:'chaochao', sex:'nv'} // 不能将类型“{ name: string; sex: string; }”分配给类型“{ name: string; }”。对象字面量只能指定已知属性,并且“sex”不在类型“{ name: string; }”中。
let a:{ name: string, [propsname: string]:any } a = {} // 类型 "{}" 中缺少属性 "name",但类型 "{ [propsname: string]: any; name: string; }" 中需要该属性 a = {name: 'chaochao'} a = {name: 'chaochao', sex:'nv'}
- 变量的类型为1个对象且该对象没有任何属性
Function
Function与object是一样的
let fun:Function // 表示变量的值为一个函数,比较宽泛
let fun:(形参: 参数类型...)=>函数返回值类型
举例说明
let fun:(a:number, b: number)=>number
// 参数类型均正确,没有问题
fun = function(a,b){
return a+b
}
// error=>不能将类型“number”分配给类型“string”
fun = function(a:string, b){
return a+b
}
// error=>不能将类型“void”分配给类型“number”
fun = function(a,b){
}
array
约定数组中元素的数据类型有两种写法
- 语法1
let arr: string[] // 表示该数组的元素为字符串类型 let arr:number[] // 表示该数组的元素为数字类型
- 语法2
let arr:Array<string> // 表示该数组的元素为字符串类型 let arr:Array<number> // 表示该数组的元素为数字类型
举例说明
let arr:Array<number>
arr = [1,2,3,4]
arr = [1,2,4,'5'] // error:不能将类型“string”分配给类型“number”。
tuple
元祖即固定长度的数组,语法如下
let arr:[数据类型,...]
举例说明
let arr:[string, number] // 表示arr数组中只有两个元素,第一个元素为字符串类型,第二个元素的类型为数字类型
arr = [1,2] // 不能将类型“number”分配给类型“string”
arr = ['1', 2] // 正确
arr = ['1'] // 源具有 1 个元素,但目标需要 2 个
类型别名
有时候设置的一些类型需要经常使用,此时可以给类型设置一个别名。
type someNumber = 1 | 2 | 3 | 4 | 5
// 此时a1与a2的数据类型都是1 | 2 | 3 | 4 | 5
let a1:someNumber
let a2:someNumber
类
ts中的类仅是在js中的类-class的基础上添加了一些功能。
[1] 实例化对象属性赋值之前需要先声明
- js
class Animal{ constructor(name, age){ this.name = name this.age = age } sayHello(){ console.log('Hello') } }
- ts
class Animal{ // 属性赋值之前需要先声明 name: string age: number constructor(name, age){ this.name = name this.age = age } sayHello(){ console.log('Hello') } }
[2] 只读属性
ts新增关键字readonly
用于表示某属性为只读属性不可修改
- 语法
readonly 属性名: 数据类型
- 举例说明
class Animal{ readonly name: string age: number constructor(name, age){ this.name = name this.age = age } sayHello(){ console.log('Hello') } } const animal = new Animal('旺财',2) console.log(animal.age) // 2 animal.age = 3 console.log(animal.age) // 3 animal.name = '大聪明' // error: 无法为“name”赋值,因为它是只读属性
[3]抽象类
在类中子类可以继承父类的属性和方法,如下示例:
先定义一个动物类Animal, 每个animal都有字的名字和年龄。子类Dot、Cat…都继承Animal这个父类另外再添加自己的属性和方法。
class Animal{
readonly name: string
age: number
constructor(name, age){
this.name = name
this.age = age
}
}
class Dog extends Animal{
breed: string
constructor(name, age, breed){
super(name, age)
this.breed = breed
}
}
class Cat extends Animal{
run(){
console.log(`${this.name}在奔跑`)
}
sayHello(){
console.log('喵喵喵')
}
}
const dog = new Dog('旺财', 3, '萨摩耶')
const cat = new Cat('大聪明', 2)
console.log(dog)
console.log(cat)
想法:把所有动物的共同属性抽取出来放在Animal类中并将Animal类作为父类,所有动物类都继承于Animal类。
实际:Animal类也可以通过实例化对象
在ts中若是该类仅仅是用于继承而不实例化对象则该类为抽象类
,使用abstract
关键字声明。
举例说明
abstract class Animal{
readonly name: string
age: number
constructor(name, age){
this.name = name
this.age = age
}
}
const animal = new Animal('旺财', 18) // error:无法创建抽象类的实例
在抽象类中还存在一种方法抽象方法
- 抽象方法只能定义在抽象类中
- 抽象方法以
abstract
开头,没有方法体 - 子类必须对抽象方法进行重写
举例说明
abstract class Animal{
readonly name: string
age: number
constructor(name, age){
this.name = name
this.age = age
}
abstract sayHello(): string
}
class Dog extends Animal{
breed: string
constructor(name, age, breed){
super(name, age)
this.breed = breed
}
// 若是没有重新会报错:非抽象类“Dog”不会实现继承自“Animal”类的抽象成员“sayHello”
sayHello(): string {
const str = '汪汪汪'
console.log(str)
return str
}
}
[4]接口
接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法。
接口使用interface
关键字定义,接口中的所有属性都不能有实际的值,接口中的所有方法都是抽象方法。
定义类时,若是使用类通过implements
关键字去实现一个接口。
使用类去实现一个接口时,该类必须包含接口中声明的所有人属性和方法,也可以声明别的属性和方法!
举例说明
// 定义一个接口
interface myclass{
name: string,
age: number,
sayHello():string
}
class People implements myclass{
// error 类“People”错误实现接口“myclass”。
// 类型“People”缺少类型“myclass”中的以下属性: name, age, sayHello
}
// 定义一个接口
interface myclass{
name: string,
age: number,
sayHello():string
}
class People implements myclass{
name: string
age: number
attr: string
constructor(name, age, attr){
this.name = name
this.age = age
this.attr = attr
}
sayHello(): string {
const str = 'hello'
console.log(str)
return str
}
}
const p1 = new People('chaochao', 18, '学生')
console.log('p1', p1)
接口可以重复
声明
- 若是后面的接口中的属性与之前接口相同,则类型必须相同
interface myclass{ name: string, age: number, sayHello():string } interface myclass{ // error 后续属性声明必须属于同一类型。属性“age”的类型必须为“number”,但此处却为类型“string” age: string }
- 若是的接口添加的新的属性,类在实现接口时必须将当前接口名的所有接口的属性和方法全部实现
interface myclass{ name: string, age: number, sayHello():string } interface myclass{ sex: string } // 该类必须存在name、age、sex属性与sayHello方法 class People implements myclass{ // error: 类“People”错误实现接口“myclass”。 // error:类型“People”缺少类型“myclass”中的以下属性: name, age, sayHello, sex }
还记得如何定义一个对象类型吗?
// 数据类型为对象且该对象中有且仅有name属性和age属性
type myobj = {
name: string,
age: number
}
接口也可以当作类型声明去使用
type objtype = {
name: string,
age: number,
sayHello():string
}
let obj:objtype = {
name: 'chaochao',
age: 18,
sayHello: function(){
const str = 'hello'
console.log(str)
return str
}
}
console.log('obj1', obj)
interface objtype2{
name: string,
age: number,
sayHello():string
}
let obj2:objtype2 = {
name: 'chaochao',
age: 18,
sayHello: function(){
const str = 'hello'
console.log(str)
return str
}
}
console.log('obj2', obj2)
以上两种方式效果相同
[5] 属性修饰符
- public 默认 公有属性-在任何位置都可以访问该成员
- private 私有属性-仅能在类的内部访问该成员
- protected 受保护的属性-在类的内部和子类中可以访问该成员
泛型
概念:泛型指的是不预先指定具体的类型,而是在使用的时候再指定具体类型
使用场景: 在定义函数或类时,若是遇到类型不明确的就可以使用泛型
语法
<数据类型>
<T>
、<T, K>
<T extends interface>
:表示泛型T必须是interface接口的实现类
在函数中使用
-
语法
// 函数声明 function fn<T>(){} // 函数调用 fn() // 不指定泛型类型,TS可以自动对类型进行判断 fn<类型>() // 指定泛型类型,即确定泛型类型
举例说明1
-
[1] 现在存在函数fn,存在形参a
function fn(a){ return a }
暂时不知道参数a的数据类型,但是希望返回值的数据类型能够和传入值的数据类型相同
此时可以使用泛型设置
// 泛型 <T> 表示T是一个类型(具体是暂时不确定),参数a与函数返回值都是T类型----> 表示变量a与函数返回值为同一数据类型 function fn<T>(a: T):T{ return a } fn(10) // 鼠标悬浮会显示:function fn<10>(a: 10): 10 表示T为常量10 fn<number>(10) // 鼠标悬浮会显示:function fn<number>(a: number): number
举例说明2
希望函数传递的参数的数据类型为 一个对象至少存在name,age属性;
-
方法1
type myobj = { name: string, age: number, [propsname: string]: any } function fn1(obj: myobj){ return obj } fn1() // error:应有 1 个参数,但获得 0 个 fn1({})// error: 类型“{}”缺少类型“myobj”中的以下属性: name, age fn1({name: 'chaochao', age: 18}) fn1({name: 'chaochao', age: 18, area: '地球'})
-
方法2
interface interobj{ name: string, age: number } function fn2<T extends interobj>(obj: T){ return obj } fn2() // error:应有 1 个参数,但获得 0 个 fn2({})// error: 类型“{}”缺少类型“myobj”中的以下属性: name, age fn2({name: 'chaochao', age: 18}) fn2({name: 'chaochao', age: 18, area: '地球'})
在类中使用:在类中使用和函数中相同
interface obj {
name: string,
age: number
}
class People<T extends obj>{
info: T
constructor(info: T){
this.info = info
}
}
const p1 = new People({name: 'chaochao'}) // error:类型 "{ name: string; }" 中缺少属性 "age",但类型 "obj" 中需要该属性
const p2 = new People({name: 'chaochao', age: 18})
ts思维
ts思维简单来说就是面向对象->所有东西先写成一个类再继续后续操作
ts编译配置
默认情况下ts编译器都是编译单个文件,将ts代码编译为语法为es3的js文件。
若是想要ts编译器对整个文件夹进行编译,可以通过tsconfig.json
配置文件进行配置。
tsc -init // 可以生成tsconfig.json配置文件,里面存在一些默认配置
配置项如下
include:需要编译的文件
"include": [] // 路径数组
// **指的是任意目录
// *指的是任意文件
"include": ['./src/**/*'] // 表示仅编译当前目录下的src目录下的任意文件
使用include配置项可以设置某文件下的所有文件进行编译,若是设置单个文件可以使用files配置项。
"files":["hello.ts"] // 表示编译hello.ts文件
使用files配置项进行配置需要枚举出所有需要编译的文件,因此一般不使用files配置行而是使用include配置项进行配置。
举例说明
{
"include": ["./src/**/*"]
}
error
上述配置文件报错=>“/Users/weiche/Desktop/knowledge/ts/tsconfig.json”中找不到任何输入。指定的 “include” 路径为“[“./src/**/*”]”,“exclude” 路径为“[]”。
原因
原因是vsscode会自动检测指定路径是否有ts文件,若没有则报错,提示用户需要创建一个ts文件后,再去使用typescript。
解决
只要是在src文件夹下创建一个ts文件就可以了。
exclude:不需要编译的文件
"exclude": ["node_modules", "bower_components", "jspm_packages"] // 路径数组(默认值)
"exclude": ['./src/module/**/*'] // 表示不编译当前目录下的src目录下的module目录下的任意文件
extends: 继承配置文件
若是项目比较大,配置文件不止一个的话,可以使用extends继承其他配置文件
"extends": "./config.base" //表示该配置文件中会自动包含config目录下的base.json中的所有配置信息
compilerOption编译配置项
-
target: 用来指定ts被编译为的es版本
"target": "ES3" // 默认值 "target": "ES6" // 编译为ES6版本的js文件 "target": "ESNext" // 编译为最新版本的js文件
tips: 若是target的属性值不是ES版本,则在编译时会在控制台展示所有ES版本,可根据需要选择所需版本进行编译。
其他配置项相同举例说明
- ts文件
let a = 100
- 不进行任何配置,编译后的js文件
// 默认是es3版本的js var a = 100
- 设置target属性值为es6编译后的js文件
let a = 100
- ts文件
-
module:指定要使用的模块化规范
"module": "commonjs" // 使用commonjs版本的模块化 "module": "es2015" // 使用es6版本的模块化
module属性的默认值会根据target属性的值有所变化。
举例说明
- ts文件
// app.ts import b from './b' let a = 100 console.log(a===b)
// b.ts let b = 100 export default b
- 配置项与编译后的js文件1
// 没有设置任何配置项,默认值 "target": "es3", "module":"commonjs"
// app.js "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var b_1 = require("./b"); var a = 100; console.log(a === b_1.default);
// b.js "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var b = 100; exports.default = b;
- 配置项与编译后的js文件2
"target": "es6" 若是target属性值设置为es6,则module的默认值为"es6"
// app.js import b from './b'; let a = 100; console.log(a === b);
let b = 100; export default b;
- ts文件
-
lib: 指定项目中所需要的库
默认值是动态变化的,如在浏览器运行时lib的默认值就是在浏览器环境所依赖的包(dom等)。
由于大多数js文件都是在浏览器运行的,因此lib配置项一般不做改动
若是文件在非浏览器中运行(环境中没有dom,但是在文件中使用到了dom语法),则会改动该配置项"lib": ["dom"] // 表示项目中需要dom库
-
outDir:指定编译后文件所在的目录
在默认情况下,编译生成的js文件会被放在ts文件同级目录下,若是想js文件统一在某个目录下,可以使用outDir配置项进行配置
outDir:'./dist' // 表示编译后的js文件会被放在当前目录下的dist目录中
-
outFile:默认情况下每个ts文件在编译之后都会生成一个同名的js文件,若是配置outFile配置项则所有全局作用域中的代码都会合并到同一个文件中
"outFile": "./dist/app.js" // 表示所有全局作用于下的代码都会合并在当前目录下的dist 目录下的app.js文件中
注意:若是想设置配置项outFile只能将module配置项设置为amd或者system,否则在编译时会报如下错误。
Cannot compile modules using option 'outFile' unless the '--module' flag is 'amd' or 'system'.
-
allowJs: 是否对js文件进行编译,默认false
在ts项目中,可能存在js文件,此时需不需要编译该js文件并打包到指定路径下就是由allowJs配置项决定的。
-
checkJs:是否对js文件进行检查,默认false
若是allowJs属性为true,则会对js文件进行编译,但是编译过程中需不需要对js文件内的语法进行检验需要通过checkJs配置项进行配置。
-
removeComments: 编译后的js文件是否需要移除注释,默认false
-
noEmit: 是否不生成编译后的文件, 默认false
-
noEmitOnError: 在有错误时不生成编译后的文件,默认false
-
语法检查配置
- alwaysStrict: 编译的js文件是否为严格模式,默认为false
tips: 若是代码中带有模块化(Es6)语法,如 import、export时默认进入到严格模式下
- noImplicitAny: 是否禁止隐士any,默认为false
- noImplicitThis是否禁止隐士类型的this,默认false
- strictNullChecks:是否严格检查空值,默认false
- …
- strict: 是否开启严格模式是上面所有语法检查配置的开关,默认false; 若是开启严格模式则上面进行语法检查配置的配置项的值全部为true
- alwaysStrict: 编译的js文件是否为严格模式,默认为false
webpack打包
若是不熟悉,可以先看webpack打包
- 项目创建
-
[1]创建项目
-
[2] 对项目进行初始化:
npm init -y
该命令会在当前目录下生成package.json配置文件,该文件用于管理项目、对项目进行配置
-
[3] 安装webpack包
npm i -D webpack webpack-cli // webpack: webpack的核心包 // webpack-cli:webpack的命令行工具
npm i -D typescript ts-loader // typescript: typescript的核心包 // loader为webpack的加载器 // ts-loader:通过ts-loader可以将webpack与typescript进行整合---> 将typescript在webpack中使用
-
webpack的配置文件
webpack.config.js
-