微软创建的typescript
ts不能直接在浏览器运行,要想运行ts文件需要先将ts文件编译为js文件再在浏览器运行。
因此需要先安装一个ts编译器
// 每次更改ts文件都需要重新编译
tsc 文件名
//热更新 编译之后ts文件更新之后会重新编译ts文件
tsc 文件名 -w
编译完成之后会在同级目录下出现同名的js文件,如下tips:默认会编译为es3版本的js文件
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: 任意类型
在声明变量时 若是变量没有赋值且没有声明类型时。隐式将变量声明为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”
那么此处应该如何操作进行赋值呢?
// 因为c是unknown(未知)类型,对类型加以判断,符合之后再赋值即可
if(typeof c === 'string'){
d = c
}
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
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
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 表示可以是任意js对象,但是这样做类型声明意义不大,因为在js中对象、数组、函数都是对象类型
let n1:object
n1 = {}
n1 = function(){}
n1 = []
上述赋值都不会报错~
但是我们可通过下述方式来规范对象的属性
// 本质就是字面量
变量名:{}
变量名:{name: string}
// ?表示可以有可以没有
变量名:{name?: string}
变量名:{name:string} & {age: number}
// [] 表示可省的
// [属性名:属性名类型]:属性值类型
// [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'}
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){
}
约定数组中元素的数据类型有两种写法
let arr: string[] // 表示该数组的元素为字符串类型
let arr:number[] // 表示该数组的元素为数字类型
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”。
元祖即固定长度的数组,语法如下
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的基础上添加了一些功能。
class Animal{
constructor(name, age){
this.name = name
this.age = age
}
sayHello(){
console.log('Hello')
}
}
class Animal{
// 属性赋值之前需要先声明
name: string
age: number
constructor(name, age){
this.name = name
this.age = age
}
sayHello(){
console.log('Hello')
}
}
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”赋值,因为它是只读属性
在类中子类可以继承父类的属性和方法,如下示例:
先定义一个动物类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
}
}
接口用来定义一个类结构,用来定义一个类中应该包含哪些属性和方法。
接口使用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)
概念:泛型指的是不预先指定具体的类型,而是在使用的时候再指定具体类型
使用场景: 在定义函数或类时,若是遇到类型不明确的就可以使用泛型
语法
<数据类型>
、
:表示泛型T必须是interface接口的实现类在函数中使用
语法
// 函数声明
function fn<T>(){}
// 函数调用
fn() // 不指定泛型类型,TS可以自动对类型进行判断
fn<类型>() // 指定泛型类型,即确定泛型类型
举例说明1
[1] 现在存在函数fn,存在形参a
function fn(a){
return a
}
暂时不知道参数a的数据类型,但是希望返回值的数据类型能够和传入值的数据类型相同
此时可以使用泛型设置
// 泛型 表示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(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代码编译为语法为es3的js文件。
若是想要ts编译器对整个文件夹进行编译,可以通过tsconfig.json
配置文件进行配置。
tsc -init // 可以生成tsconfig.json配置文件,里面存在一些默认配置
配置项如下
"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": ["node_modules", "bower_components", "jspm_packages"] // 路径数组(默认值)
"exclude": ['./src/module/**/*'] // 表示不编译当前目录下的src目录下的module目录下的任意文件
若是项目比较大,配置文件不止一个的话,可以使用extends继承其他配置文件
"extends": "./config.base" //表示该配置文件中会自动包含config目录下的base.json中的所有配置信息
target: 用来指定ts被编译为的es版本
"target": "ES3" // 默认值
"target": "ES6" // 编译为ES6版本的js文件
"target": "ESNext" // 编译为最新版本的js文件
tips: 若是target的属性值不是ES版本,则在编译时会在控制台展示所有ES版本,可根据需要选择所需版本进行编译。
其他配置项相同
举例说明
let a = 100
// 默认是es3版本的js
var a = 100
let a = 100
module:指定要使用的模块化规范
"module": "commonjs" // 使用commonjs版本的模块化
"module": "es2015" // 使用es6版本的模块化
module属性的默认值会根据target属性的值有所变化。
举例说明
// app.ts
import b from './b'
let a = 100
console.log(a===b)
// b.ts
let b = 100
export default b
// 没有设置任何配置项,默认值
"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;
"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;
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
语法检查配置
tips: 若是代码中带有模块化(Es6)语法,如 import、export时默认进入到严格模式下
若是不熟悉,可以先看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