TyprScript
TyprScript基础知识
一、TyprScript安装与编译
TyprScript介绍
TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
TyprScript
TypeScript可以编译出纯净、 简洁的JavaScript代码,并且可以运行在任何浏览器上、Node.js环境中和任何支持ECMAScript 3(或更高版本)的JavaScript引擎中。
安装环境
npm i typescript -g //全局安装typescript
npm init -y //进入文件夹,初始化项目,生成package.json文件
tsc --init //创建tsconfig.json文件
npm i @types/node -S //这个主要是解决模块的声明文件问题
构建你的第一个TypeScript文件
在编辑器,将下面的代码输入到greeter.ts文件里:
function greeter(person) {
return "Hello, " + person;
}
let user = "Jane User";
document.body.innerHTML = greeter(user);
编译代码
在命令行上,运行TypeScript编译器
tsc greeter.ts
二、原始数据类型
js中数据类型分两种,原始数据类型和对象类型,原始类型包括:布尔值、数字、字符串、null、undefined以及Symbol。
布尔值
在TypeScript中,使用boolean定义布尔值类型
let isStatus:boolean=true;
数字
使用number定义数值类型
let isNumber:number=1;
//16进制(编译后显示10进制数字)
let isNumber16:number:=0b1010;
字符串
使用string定义字符串类型:
let isString:string='hello word';
let year:number=2020;
let add:string=`${isString},${year}!`;
空值
JavaScript 没有空值(Void)的概念,在TypeScript中,可以用void表示没有任何返回值的函数.
function alertName():void{
alert("my name is tom")
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null。
let unusable: void = undefined;
Null 和 Undefined
在TypeScript中可以使用null和undefined来定义这两个原始数据类型
let u:undefined=undefined;
let n:null=null;
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量,而void类型的变量不能赋值给number类型的变量:
let num:number=undefined;//不会报错
let u:undefined;
let num: number = u;//也不会报错
let u:void;
let num:number=u;// Type 'void' is not assignable to type 'number'.
任意值
任意值(Any)用来表示允许赋值为任意类型。
什么是任意值类型
如果是一个普通类型,在赋值过程中改变类型是不被允许的:
let renyiString:string='string';
renyiString=8;
//Type 'number' is not assignable to type 'string'.
但如果类型是Any,类型,则允许被赋值为任意类型:
let renyiString:any='string';
renyiString=8;
在任意值上访问任何属性都是允许的:
let anyThis:any='hello';
console.log(anyThis.myName)
console.log(anyThis.myName.firstName)
也允许调用任何方法:
let anyThing: any = 'Tom';
anyThing.setName('Jerry');
anyThing.setName('Jerry').sayHello();
anyThing.myName.setFirstName('Cat');
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
未声明类型的变量
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
等于
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
三、数组的类型
基础表示
「类型 + 方括号」表示法
let numbers:number[]=[1,2,3,4,5]
此时不允许出现其他类型,而且如果使用数组中push等方法,添加元素也得符合相应类型。
数组泛型
我们也可以使用数组泛型(Array Generic) Array 来表示数组
let fibonacci:Array<number>=[1,2,3,4]
如果数组中又有number类型又有string类型,则可以用|符号来区别定义:
let arr:(number|string)[]=['tom',1];
数组中对象类型的定义
项目中经常遇到数组中有对象的存在,对于这种就比较麻烦了,比如:
let arr:{name:string,age:number}[]=[
{name:'tom',age:18}
]
这样就比较麻烦了,我们可以使用ts中的类型别名来解决这个问题:
type PeopleType={name:String,age:Number};
let arr:PeopleType[]=[
{name:'bob',age:19}
]
也可以用类型定义也可以解决:
class PeopleType{
name:string;
age:number
}
let arr:PeopleType[]=[
{name:'bob',age:19}
]
数组的使用和类型约束
在数组中如果里面又有string和number,可以使用|来进行定义,但一定程度上并不严格。比如改成下面这种格式:
let arr:(number|string)[]=[111,'222',111];
ts并没有报错,如果想要严格限制,则可以这样进行约束:
let arr:[number,string,number]=[111,'222',111];
四、接口和类
Interface接口
比如我们要做一个筛选,吧不符合条件的过滤出去,我们可能会这样写:
const types=(name:string,age:number,height:number)=>{
age>=20 && height>=180 && console.log('符合条件');
age<=20 && height <180 &&console.log('不符合')
};
types('tom',20,180) //符合条件
但如果又修改了一些需求,可能还会去大量变更代码,在开发中,代码能复用肯定是最好的,所以可以把一些重复的代码抽离出来:
interface People{
name:string;
age:number;
height:number;
}
const types=(people:People)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height <180 &&console.log('不符合')
};
const choose=(people:People)=>{
console.log(people.name+'----'+people.age+'---'+people.height)
}
const people={
name:'tom',
age:18,
height:178
}
types(people);
choose(people);
接口与类型别名的区别
看起来两者没有什么区别,但有个小细节:类型别名可以直接给类型,接口必须代表对象
type People=string;
interface People{
name:string;
age:number;
height:number;
}
如果传入的参数中有不确定项,我们可以使用 ?: 来进行处理:
interface People{
name:string;
age:number;
height:number;
say?:string
};
const types=(people:People)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height <180 &&console.log('不符合')
};
const people={
name:'tom',
age:18,
height:178
}
const people2={
name:'tom',
age:18,
height:178,
say:'hello'
}
types(people);
types(people2);
这时候又有新需求了,如何在后面加入任意多的字段?这时候我们就可以这么写:
interface People{
name:string;
age:number;
height:number;
say?:string;
[propname:string]:any;
}
const types=(people:People)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height lt;180 &&console.log('不符合')
};
const people={
name:'tom',
age:18,
height:178,
say:'hello',
add:'new add',
addNumber:123
}
types(people);
接口里的方法
接口不仅仅可以存属性,也可以存方法:
interface People{
name:string;
age:number;
height:number;
goto():string;
}
const types=(people:People)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height <180 &&console.log('不符合')
};
const people={
name:'tom',
age:18,
height:178,
goto(){
return 'hello'
}
}
types(people)
接口和类的约束
在ES6中是有类的概念,类可以和接口相结合:
interfacePeople{
name:string;
age:number;
height:number;
goto():string;
}
const types=(people:People)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height <180 &&console.log('不符合')
};
const people={
name:'tom',
age:18,
height:178,
say:'hello',
add:'new add',
addNumber:123,
goto(){
return 'hello'
}
};
class newPeople implementsPeople{
name='bob';
age=19;
height:190;
say:'hello';
add:'new add2';
addNumber:123;
goto(){
return 'hi'
}
};
let a=new newPeople();
console.log(a.goto())
types(people)
接口的继承
接口与接口也是可以继承的:
interfacePeople{
name:string;
age:number;
height:number;
say?:string;
[propname:string]:any;
goto():string;
}
const types=(people:newPeople)=>{
people.age>=20 && people.height>=180 && console.log('符合条件');
people.age<=20 && people.height <180 &&console.log('不符合')
};
const people={
name:'tom',
age:18,
height:178,
say:'hello',
add:'new add',
addNumber:123,
goto(){
return 'hello'
},
back(){
console.log(1)
}
}
interface newPeople extendsPeople{
back():void
}
types(people)
五、类的基本使用
首先,我们先创建一个类:
class Test{
name='say'
say(){
return this.name
}
};
这就是平时所写的类,在ts中的继承和ES6的继承是一样的,关键字也是extends,比如我们这里新建个类,继承Text;
classTest{
name='say'
say(){
return this.name
}
};
class NewTest extendsTest{
back(){
return 'hello'
}
}
const tom=new NewTest();
console.log(tom.say());
console.log(tom.back());
super关键字的使用
如果想在say方法中后面加点东西,可以这么操作:
class Test{ name='say' say(){ return this.name } }; class NewTest extends Test{ back(){ return 'hello' } say(){ return super.say()+'----hello' } } const tom=new NewTest(); console.log(tom.say()); console.log(tom.back());
ts中类的访问类型
ts中的访问类型就是基于三个关键字:private、protected以及pubilc这三种访问类型。看例子,先定义一个类,然后用这个类的对象,进行赋值:
class Person{
name:string
}
const person=new Person();
person.name='tom';
console.log(person.name)
pubilc
运行可以看到正常的输出内容,这是因为如果不对name的访问属性进行定义,那么他的默认属性就是pubilc,从字面意思来看,它的意思就是公用的,允许在类的内部和外部被调用。
classPerson{
public name:string
}
const person=new Person();
person.name='tom';
console.log(person.name)
private
private的属性意思就是只允许在类的内部调用,不能在外部调用
class Person{
private name:string;
public say(){
console.log(this.name+'-----hello');
}
}
const person=new Person();
person.name='tom';//报错
console.log(person.name)//报错
protected
protected允许在类内以及继承的子类中使用,把刚刚的name改成protected属性,这时候在外部就会报错,这时候再写一个继承,代码如下:
classPerson{
protected name:string;
public say(){
console.log(this.name+'-----hello');
}
}
const person=new Person();
person.name='tom';//报错
console.log(person.name)//报错
class NewPerson extendsPerson{
public back(){
console.log(this.name)
}
}
const newperson=new NewPerson();
newperson.back()//正常
类的构造函数
首先新建一个类:Person,定义一个name,并在new的时候进行参数传递,然后打印出来,这时候我们就可以使用构造函数constructor :
class Person{
pubilc name:string;
constructor(name){
this.name=name
}
};
const person=new Person('tom');
console.log(person.name);
可以看到可以打印出来,但是上面写法有点麻烦,还可以再进行简化:
classPerson{
constructor(pubilc name:string){}
};
const person=new Person('tom');
console.log(person.name);
类继承中的构造器写法
普通的书写方法上面已经演示了,在子类中使用构造函数需要用super()调用父类的构造函数,直接看代码:
classPerson{
constructor(pubilc name:string){}
};
class Teacher extendsPerson{
constructor(pubilc age:number){
super()
}
};
const teacher=new Teacher(18);
console.log(teacher.age+'---'+teacher.name);
ts中类的Getter、Setter、static以及readonly
在上面中提到了访问类型private,它的最大用处就是封装一个书写,然后通过Getter和Setter去访问和修改:
classPerson{
constructor(private _age:number){
}
}
如果想让别人知道,就可以使用Getter来实现,他并不是一个方法,只是一个属性:
classPerson{
constructor(private _age:number){
};
get age(){
return this._age
}
};
const person=new Person(30);
console.log(person.age)
这时候你可能会觉得这不是多此一举吗?但在Getter中可以对 _age进行处理:
classPerson{
constructor(private _age:number){
};
get age(){
return this._age-10
}
};
const person=new Person(30);
console.log(person.age)
既然 _age是私有的,我们无法进行改变,这时候就可以用Setter进行改变:
classPerson{
constructor(private _age:number){
};
get age(){
return this._age
}
set age(age:number){
this._age=age
}
};
const person=new Person(30);
person.age=20;
console.log(person.age)
在类中,如果想用这个类的实例,就必须先进行new操作,但有没有一种方法不需要new就可以?
//常规方法
classPerson{
say(){
return 'say hello'
}
};
const person=new Person();
console.log(person.say());
在ts中,我们不想new的话可以这么写:
class Person{
static say(){
return 'say hello'
}
};
console.log(Person.say())
readonly在初始化后赋值,以后就不能进行修改
class Person{
constructor(readonly name:string){}
}
let p=new Person('tom');
p.name='bob'//报错
console.log(p.name);
类的抽象类
abstract 用于定义抽象类和其中的抽象方法。它有几个特点:
抽象类是不允许实例化的
//错误演示
abstract class Person {
public abstract say()
}
const tom=new Person()//无法创建抽象类的实例
抽象类中的抽象方法必须被子类实现:
//错误演示
abstract classPerson {
abstract say(){
console.log('hello')
}
}
class Tom extendsPerson{
say(){
console.log('say hello')
}
};
//正确方法
abstract classPerson {
abstract say()
}
class Tom extendsPerson{
say(){
console.log('say hello')
}
};
tsconfig.json配置
生成
我们可以通过tsc --init去生成ts配置文件:
tsc --init //终端执行
编译
会生成一个tsconfig.json文件,我们在ts文件中可以随便写点什么:
//测试tsconfig.json
const test:string='tsconfig.json';
然后打开tsconfig.json文件,找到complilerOptions属性下的removeComments:true(这个配置是编译后不输出注释),取消掉注释,然后执行命令:
tsc
这时候打开生成的js文件,发现没有注释,说明成功。
include 、exclude 和 files
如果有多个ts文件,只想编译一个可以在ts配置项中加入include:
"include":["text.ts"],
如果想除了某个文件不编译,剩下的都编译,可以使用exclude:
"exclude":["text.ts"],
files和include没有什么区别:
"files":["text.ts"],
compilerOptions配置
removeComments
这个配置意思就是编译后不输出注释
strict
这个设置为true,就代表严格执行ts语法,要严格按照ts语法来编写。
noImplicitAny
允许你的注解类型 any 不用特意表明,如果此时设置了true,看例子:
//这时候编译会报错
function test(name) {
return name;
}
//正确
function test(name:any) {
return name;
}
strictNullChecks
意思就是,不强制检查null类型,此时如果配置为true看例子:
//此时就不会报错
const test: string = null;
outDir和rootDir
此项配置是来指定文件目录和打包后存放目录,rootDir为文件目录,outDir为打包后保存的目录
{
"outDir": "./build" ,
"rootDir": "./src" ,
}
编译ES6语法
可以使用target和allowJs,target默认为true
"target":'es5' , // 这一项默认是开启的,你必须要保证它的开启,才能转换成功
"allowJs":true, // 这个配置项的意思是联通
sourceMap
sourceMap 简单说,Source map 就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。
noUnusedLocals
设置noUnusedLocals为true,编译代码:
const name:tring='111';
export const age = "text";
这时候就会报错,因为有name变量没有使用。
六、联合类型和类型保护
联合类型
联合类型的意思就是允许一个类型有两种或者两种以上的类型:
interfaceTest{
name:string;
say:()=>{
}
}
interfaceTest2{
name:string;
call:()=>{};
}
function Tom(fun: Test | Test2){
console.log(fun.name)
}
如果此时修改一下方法:
function Tom(fun: Test | Test2){
fun.say()
}
//报错
这是因为只能访问两个类型的共有方法。
类型保护-类型断言
上面的方法,如果修改完报错,这时候我们可以用as来判断:
interfaceTest {
text: boolean;
say(): void
}
interfaceTest2 {
text: boolean;
skill():string
}
function judgeWho(val: Waiter | Teacher) {
if (val.text) {
(val as Teacher).skill();
}else{
(val as Waiter).say();
}
}
const a={
text:true,
skill:function():void{
console.log(1)
},
say:function():void{
console.log(2)
}
}
judgeWho(a) //1
类型保护-in语法
in方法与断言比较类似,使用方法如下:
interfacePerson{
name:string;
say:()=>{
}
}
interfacePerson2{
name:string;
todo:()=>{
}
};
function tom(val:Person|Person2){
if('todo' in val){
val.todo()
}else{
val.say()
}
}
类型保护-typeof语法
可以用typeof方法来判断:
function add(name:string|number,name2:string|number){
if(typeof name==='string'||typeof name2=='string'){
return name+'---'+name2
}else{
return name+name2
}
}
add(1,2)
类型保护-instanecof语法
如果要保护类型是一个对象,就可以使用instanceof:
classNumObject{
num:number
}
function numbers(num1:object|NumObject,num2:object|NumObject){
if(num1 instanceof NumObject && num2 instanceof NumObject){
return num1.num+num2.num
}
}
Enum枚举类型
我们平时会有这种写法:
function getName(status:any){
if(status===0){
return 'one'
}else if(status===1){
return 'two'
}else{
return 'three'
}
}
console.log(getName(0))
这么写可能有点麻烦,阅读起来还是有点麻烦,这时候可以这么写:
const Status={
ONE:0,
TWO:1,
THREE:3
}
function getName(status:any){
if(status===Status.ONE){
return 'one'
}else if(status===Status.TWO){
return 'two'
}else{
return 'three'
}
}
console.log(getName(0));
这时候我们的枚举就要上场了:
enum Status{
ONE,
TWO,
THREE
}
function getName(status:any){
if(status===Status.ONE){
return 'one'
}else if(status===Status.TWO){
return 'two'
}else{
return 'three'
}
}
console.log(getName(1));
一样也可以输出,因为枚举是有对应数字值的,默认从0开始,当然也可以改变:
enum Status{
ONE=1,
TWO,//2
THREE//3
}
也可以进行返查操作:
enum Status{
ONE,
TWO,
THREE
}
function getName(status:any){
if(status===Status.ONE){
return 'one'
}else if(status===Status.TWO){
return 'two'
}else{
return 'three'
}
}
console.log(Status.ONE,Status[1]);
七、泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
在函数中使用
我们先编写一个函数:
function add(one:string,two:string){
return `${one}${two}`
}
console.log(add('a','b'))
这时候我们希望参数更加灵活一些,两个参数为number或者string:
function add(one:string|number,two:string|number){
return `${one}${two}`
}
console.log(add('a','b'))
这么书写有些麻烦,这时候就可以使用泛型:
function add<T>(one:T,two:T){
return `${one}${two}`
}
console.log(add<string>('a','b'))
在使用中,泛型通常用 <T> 来进行表示。泛型可以有多个吗?当然可以:
function add<T,P>(one:T,two:P){
return `${one}${two}`
}
console.log(add<string,number>('a',2))
同时泛型也支持类型推断:
function add<T,P>(one:T,two:P){
return `${one}${two}`
}
console.log(add<string,number>('a',2))
在类中使用
首先先写一个类,且接受参数为一个数组,数组里面存放string类型的数据:
classList {
constructor(private list:string[]) {};
getItem(index:number):string{
return this.list[index]
}
};
const list=new List(['girl','boy','wom']);
console.log(list.getItem(1))
这时候如果我们传递数组时候里面又想放数字怎么办:
classList {
constructor(private list:string[]|number[]) {};
getItem(index:number):string|number{
return this.list[index]
}
};
const list=new List(['girl','boy','wom']);
console.log(list.getItem(1))
这么写就比较复杂了,这时候就可以使用泛型来简化我们的代码:
class List<T>{
constructor(private list:T[]) {};
getItem(index:number):T{
return this.list[index]
}
};
const list=new List(['girl','boy','wom']);
console.log(list.getItem(1))
发现上面的代码没有报错?因为类型推论,所以不会报错,严格意义上应该new的时候加上类型:
const list=new List<string>(['girl','boy','wom']);
还有一种场景,传递过来是一个数组对象,这时候可以通过继承来解决:
interfacePeople{
name:string
}
class List<T extendsPeople>{
constructor(private list:T[]) {};
getItem(index:number):string{
return this.list[index].name
}
};
const list=new List(
[
{name:'boy'},
{name:'girl'}
]
);
console.log(list.getItem(1))
泛型约束
上面例子中,泛型可以为任意值,但有时候我们希望还是能稍微约束一下:
function list<T extends number|string>(name:T){
return `${name}`
}
console.log(list<number>(1))
class List<T extends number|string> {
constructor(private list:T[]) {
};
getItem(index:number):T{
return this.list[index]
}
}
const list=new List([1,2])
console.log(list.getItem(0))
八、Namespace命名空间
新建一个ts项目
- 首先,我们建立一个项目文件,然后npm init -y生成package.json文件,然后再tsc -init生成ts配置文件。
- 在根目录下新建index.html文件,再建立一个src和bulid目录,在src目录下新建一个index.js。
- 配置tsconfig.json文件,设置入口和输出目录(outDir和rootDir)。
打开index.html,引入js文件:
<script src="./build/index.js"></script>
在新建的ts文件中随便写点什么:
console.log('hello');
然后tsc编译一下,打开控制台,我们就可以看到了
编写一个小组件
在我们刚刚建立的index.ts文件,写一个header、content和footer组件:
classHeader {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Header";
document.body.appendChild(elem);
}
}
classContent {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Content";
document.body.appendChild(elem);
}
}
classFooter {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Footer";
document.body.appendChild(elem);
}
}
classPage {
constructor() {
new Header();
new Content();
new Footer();
}
}
然后在index.html中加一行js代码:
<script>new Page();</script>
这时候我们可以看到内容正常输出,但有一个问题,我们的Header、content和footer都暴露了出来,并不是只暴露一个page,这时候我们的命名空间就派上了用场:
命名空间
命名空间声明的关键词是namespace 比如声明一个namespace Home,需要暴露出去的类,可以使用export关键词,这样只有暴漏出去的类是全局的,其他的不会再生成全局污染了。
namespace Home {
classHeader {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Header";
document.body.appendChild(elem);
}
}
classContent {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Content";
document.body.appendChild(elem);
}
}
classFooter {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Footer";
document.body.appendChild(elem);
}
}
export classPage {
constructor() {
new Header();
new Content();
new Footer();
}
}
}
这么写也是比较麻烦,因为我们需要引入两个文件,我们可以通过配置来让他成为一个文件,打开tsconfig.json,找到这一行:
"module":"commonjs"
//修改为:
"module":"amd
然后找到这一行:
{
"outFile": "./build/index.js"
}
子命名空间
如果在刚刚的组件中再写一个会怎么样?
namespace Components {
export namespace SubComponents {
export class Test {}
}
//someting ...
}
//读取
// Components.SubComponents.Test
ts中使用import
首先建立一个文件compontent.ts,随便写点东西,然后使用export导出:
export classHeader {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Header";
document.body.appendChild(elem);
}
}
export classContent {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Content";
document.body.appendChild(elem);
}
}
export classFooter {
constructor() {
const elem = document.createElement("div");
elem.innerText = "This is Footer";
document.body.appendChild(elem);
}
}
然后在index.ts中导入一下:
import { Header, Content, Footer } from "./compontent";
export classPage{
constructor(){
new Header();
new Content();
new Footer();
}
}
运行tsc编译,打开build中的index.js,可以看到代码是define开头的,这是 amd 规范的代码,不能直接在浏览器中运行,可以在 Node 中直接运行,所以我们还需要借助require.js的支持:
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.js"></script>
然后在index.html中使用require.js写法:
<script>
require(["page"], function (page) {
new page.default();
});
</script>
本文参考:TypeScript基础知识整理