TypeScript学习指南
- TS的安装以及初步使用
- typescript的安装命令
- 运行ts文件
- 生成Typescript配置文件。
- TS中的强类型基本变量
- ts中的布尔型变量
- ts中的数字型变量
- ts中的字符型变量
- ts声明数组
- ts声明元组
- ts声明枚举型变量
- ts声明任意型变量
- null变量和undefined变量
- void变量类型
- never类型
- TS中的函数
- 函数声明
- 函数的可选参数。
- 函数的默认值
- 函数的省略参数
- 函数的重载
- ES5类的创建与继承
- ES5中类的新建
- 类的静态方法
- ES5中类的继承
- TS中类的定义,继承以及修饰符
- TS中类的定义
- TS中类的继承
- TS中类的修饰符
- TS中的静态属性,静态方法,多态以及抽象类
- 静态属性以及静态方法
- 多态
- TS中的抽象类
- TS中的接口
- 接口的定义以及使用
- 函数类型接口
- 可索引接口
- 类类型接口
- 接口的扩展以及继承
- 泛型,泛型函数以及泛型类
- 泛型函数
- 泛型类
- 泛型接口
- 把类作为参数类型的泛型类
- TS中的模块
- 外部模块
- 内部模块
- TS中的装饰器
- 普通装饰器
- 装饰器工厂
- 使用装饰器修改构造函数
- 属性装饰器
- 方法装饰器
- 方法参数修饰器
- 装饰器优先级
TS的安装以及初步使用
typescript的安装命令
npm install -g typescript
我们新建一个TS文件夹,拖入vscode中打开后在终端输入以上命令以全局安装TS。
之后可以用以下命令来检测TS是否安装成功
tsc v
如果成功出现版本号则说明安装成功
运行ts文件
首先我们可以新建一个TS后缀的文件。文件中可以先随便写一些简单的JS代码。此时vs中的目录结构如下所示
在命令行中手动输入以下命令来编译TS文件。
tsc index.ts
这里有一个坑,有一个设置会导致即使安装成功在运行TS文件时也会出现因为在此系统上禁止运行脚本报错
解决方案
win+Q键盘搜索powerShell,右键以管理员方式打开。输入
set-ExecutionPolicy RemoteSigned
最后选择Y或者A。
这样设置之后就可以成功编译TS文件了。
此时我们在终端输入tsc index.ts来编译ts文件,可以看到编译成功之后vscode的目录结构发生了变化。
可以看到。ts文件编译后的最终归宿其实还是js。
生成Typescript配置文件。
我们不想每次写完或者保存完ts文件后都需要重新输入一次编译TS文件命令。此时可以通过修改TS配置文件来达到目的。
在终端中输入
tsc --init
完成之后注意到我们的目录结构中新出现了一个
tsconfig.json文件。它包含了ts全部的配置。当然也包括了我们想要的自动编译功能。
打开此json文件,找到outDir配置项。将注释放开。这里作者想将每一次的js文件都编译到特定的JS文件夹中。tscongfig的配置如下。
配置好之后依次点击vscode上方菜单栏中的终端—>运行任务—>选择tsc监视
此时我们任意修改index.ts文件内容。当我们保存编辑之后。vs就可以自动的将我们编写的ts代码转化成对应的js代码了。为了更清晰的看出编译出js文件的输出。我们可以新建一个html文件来导入这个js文件。之后就可以在控制台来看到js文件的输出了。
demo目录结构如下
html代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./js/index.js"> </script>
</body>
</html>
TS中的强类型基本变量
与JavaScript不同,而与传统的计算机语言C,java相似的是TS在声明变量时需要同时指定数据类型;
TS数据类型主要有如下几种
ts中的布尔型变量
声明布尔型变量
let a:boolean = true;
可以看到和JS代码还是有很大的相似性。只不过是多了指定的变量类型。之所以是强类型基本变量。就说明了变量a声明为布尔型变量后就不可以指定为其他类型的变量了。如果强行赋值,TS就会报错。
由此可以看出TS相比较JS在语言特性上而言还是更加的规范。
ts中的数字型变量
TS声明数字型变量和声明布尔型方法一样。
let a:number = 111
需要注意的是,NaN意为非数字,但是在TS中是可以赋值给数字型变量的。可以理解成应该有数字型的输出。但因为计算失误计算出了NaN。
ts中的字符型变量
声明方法同上
let a:string = 'asdsa'
ts声明数组
在TS中声明数组有两种方式。
第一种:
let arr:number[] = [1,2]
第二种:
let a:Array<number> = [1,23]
需要注意的是,这两种方法声明数组都需要指定数组中元素的类型。如果数组中元素的类型和声明的不一致。TS一样会报错。
ts声明元组
解释一下,其实元组就是数组。当我们明确知道我们想要声明的数组的长度和每一个元素的数据类型时就可以声明一个元组。
let arr:[number,string] = [1,"sss"]
tips:当声明的数组长度和元素类型和声明的不一致时。TS会报错。
ts声明枚举型变量
现实生活中有很多场景是可以枚举出来的。如动物的性别,手机的颜色,支付的状态等等。在实际开发中也常常有这样的情况。我们开发过程中用0表示未支付,1表示支付中,2表示支付完成。但隔了很久我们再次来维护这段代码的时候往往会很困惑。
此时在TS中我们可以声明一个枚举型变量来很好的解决此类问题。枚举型变量的声明方法如下
enum orderStatus{
noStart,
paying,
down
}
需要注意的是声明这个变量我们用的是JS中没有的关键字enum
此时我们可以在控制台打印出这个变量看看它具体的数据结构。
可以看到编译之后实际上是生成了一个元素值和下表索引一一对应的对象。不给枚举对象赋值的话下标是默认从0开始。当然我们也可以手动赋值。
enum orderStatus{
noStart = 1,
paying = 3,
down = 5
}
ts声明任意型变量
当我们想声明的变量不知道具体类型或者是类型会变化的时候可以将此变量声明成任意类型的。(常常用于对象)
let obj:any = {};
声明了任意型的变量在赋值时不会报错。相当于不会进行类型校验。
null变量和undefined变量
let a:null = null
let b:undefined = undefined
将这两个类型的变量赋值除此两个值之外的任何变量都会报错。
void变量类型
常常用于一个没有返回值的函数
function say():void {
console.log(1);
}
never类型
据说是因为使用场景不大,省略。。。
TS中的函数
函数声明
函数声明有两种方式,和JS声明函数一样。
(1)函数表达式
function saySomething():void{
console.log(111)
}
(2)函数声明
let saySomething = function():void{
console.log(22112);
}
差别就是给函数的返回值加上了类型,当然不加也不会报错,但是为了TS语言的规范性还是推荐加上。
函数的可选参数。
与js不同,在js中在函数中声明了形参,但是在调用时可以不传。在js中依然不会报错。但是在TS中不同,如果在函数声明时指定了参数,那么在调用函数时不论是参数类型或者数量没对应上的话都会引起TS报错。看以下栗子
我们声明了一个有一个形参name的函数,但是在调用时并没有传递这个参数,此时TS就编译不通过并报错。
如果我们确实有需求想要定义形参,但是调用的时候不一定会传递的话应该采用如下写法。
function saySomething(name?:string){
console.log(11111)
}
函数的默认值
TS中函数的默认值用法同ES中函数的默认值一样。故不作过多介绍。
function saySomething(name:string='巴啦啦'){
console.log(11111)
}
tips:需要注意的时,函数的默认值和可选参数不能同时使用。当然也是很好理解的。指定了默认值的参数是必然不会缺少的,也就没有必要成为函数的可选参数了。
函数的省略参数
主要用于不知道函数到底要传递几个参数的情况下。打个栗子:
function printUserFriends(name:string,friend:string):void{
console.log(`我的名字是${name},我的朋友是${friend}`);
}
printUserFriends('wxs','小明');
在此我声明了一个打印用户朋友的函数。很简单的看出输出:我的名字是wxs,我的朋友是小明。
当我要对这个函数进行改写,我的朋友可以输入很多个,但不确定到底会输入几个时可以用到函数的省略参数。(其实用法也类似于ES的省略运算符)
用法如下:
function printUserFriends(name:string,...friends:Array<string>):void{
let printMsg:string = `我叫${name}`
if(friends.length){
printMsg+='我的朋友是'
friends.forEach((item:string) => {
printMsg += item
})
}
console.log(printMsg);
}
printUserFriends('wxs','张三','李四','王五');
函数的重载
函数重载:对相同函数传入不同的参数会产生不同的行为称为函数重载
不论是以前的JS还是最新的ES6标准中都没有实现传统意义上的重载,我们只能动态的判断传进函数的参数来间接的实现函数重载。
TS对函数重载的实现稍微严格了那么一点。
function saySomething(name:string):string
function saySomething(name:number):number
function saySomething(name:any):any{
if (typeof name === 'string') return '123132';
else if (typeof name === 'number') return 22222;
}
saySomething('12132');
saySomething(11231231);
saySomething(true);
tips:需要重载的函数类型需要在TS中提前声明,没有实现但直接调用的将会报错。如上例中的第三次调用将会报错。报错信息如下
ES5类的创建与继承
tips:这一章是对js中类的新建和继承方法的介绍,可以选择性跳过
ES5中类的新建
在ES6之前的版本中,JavaScript是没有类这个概念的,只能用函数来简介实现。
实例:
function Animal(name,age){
= name,
this.age = age
}
let dog = new Animal('小狗',18)
上例中我们就新建了一个Animal类(本质是构造函数)以及以及dog实例。
tips:构造函数其实也就是一个函数,函数名约定俗成为大写。
类的静态方法
静态方法:由类直接调用的方法,不由其子类继承。
知道静态方法的定义之后写一个静态方法就很简单了。由类直接调用,我们就把方法直接定义在类上就可以了。
function Animal(name,age){
= name,
this.age = age
}
Animal.dark = function(){
console.log('动物都会叫');
}
let dog = new Animal('小狗',18)
Animal.dark(); // 动物都会叫
dog.dark(); // dag.dark is not a function
由原型链可以知道,dog实例先会在他的构造函数中寻找dark方法,然后沿着构造函数的原型,也就是Animal.prototype,一直找到原型链的顶部都没有找到dark方法之后便会报错。
ES5中类的继承
ES5中类的继承主要有以下四种方法。
(1)、构造继承
所谓构造继承就是利用父类的构造函数进行继承
function Animal(name,age){
= name,
this.age = age
}
Animal.prototype.dark = function(){
console.log('动物都会叫');
}
function Dog(name,age){
Animal.call(this,name,age)
}
let dog = new Dog('小狗',22);
console.log(,dog.age); //小狗 22
dog.dark(); // 报错
构造函数用法很简单,就是在子类中调用父类的构造函数(本质上就是把父类的this改为指向了子类),但是它的缺点也显而易见。构造继承不能继承父类原型上的属性和方法。
(2)、原型继承
原型继承顾名思义就是利用原型链进行继承
function Animal(name,age){
= name,
this.age = age
}
Animal.prototype.dark = function(){
console.log('动物都会叫');
}
function Dog(name,age){
}
Dog.prototype = new Animal();
let dog = new Dog('小狗',22);
console.log(,dog.age); // undefined undefined
dog.dark(); // 动物都会叫
原型继承原理就是子类的原型等于父类的一个实例,这样父类的属性和方法子类都会继承到。缺点也很显而易见。原型继承不能传参
(3)、组合继承
如果熟悉并掌握了以上两种继承的原理,那么组合继承就变得异常简单 了。组合继承 = 构造继承 + 原型继承。既然说构造继承不能继承父类原型的方法,原型继承不能传参。那么就把这两个方法结合起来劣势互补。
function Animal(name,age){
= name,
this.age = age
}
Animal.prototype.dark = function(){
console.log('动物都会叫');
}
function Dog(name,age){
Animal.call(this,name,age)
}
Dog.prototype = new Animal();
let dog = new Dog('小狗',22);
console.log(,dog.age); // 小狗 22
dog.dark(); // 动物都会叫
(4)、寄生组合继承
寄生组合继承是对组合继承的一个优化,组合继承把两种继承结合起来,完美的实现了继承。但是可以看到Animal.call(this)调用了一次父类的构造方法,Dog.prototype = new Animal()又调用了父类的构造函数。实际上是生成了两个父类的实例的。造成了不必要的性能开销。寄生组合继承相对于组合继承实际上只改写了一行代码。将
Dog.prototype = new Animal();
改写成
Dog.prototype = Animal.prototype;
在JavaScript高级程序设计中指出,可以将父类原型赋值给子类原型达到重写子类原型的目的。如果子类原型还需要其他方法。
可以在之后继续添加新方法达到增强子类原型对象的目的。
TS中类的定义,继承以及修饰符
TS中类的定义
class Animal{
name:string
age:number
constructor(name:string,age:number){
= name
this.age = age
}
}
TS中类的继承
TS中类的继承主要用到两个关键字extends和super
class Animal{
name:string
age:number
constructor(name:string,age:number){
= name
this.age = age
}
}
class Dog extends Animal{
constructor(name:string,age:number){
super(name,age)
}
}
tips:在TS继承中子类必须使用super调用父类的构造函数。在ES6中解释道class子类没有自己的this,需要调用super将父类的this传递给子类。
TS中类的修饰符
TS给在类中定义的属性提供了三种修饰符:public,protected,private。
特性介绍
public:定义为public的变量在本类和子类和外部均可使用。
protected:定义为protected的变量在本类和子类中可以使用
private:定义为private的变量仅可以在本类中使用。
class Animal{
public name:string
public age:number
constructor(name:string,age:number){
= name
this.age = age
}
dark(){
console.log(`${}都会叫`)
}
}
class Dog extends Animal{
constructor(name:string,age:number){
super(name,age)
}
run(){
console.log(`${}都会跑`)
}
}
let dog = new Dog('小狗',8);
let animal = new Animal('动物',10);
console.log(animal.name,animal.age); // 动物 10
此时是可以正常使用的。
如果把父类两个public关键词改成protected,改为仅限本类和子类使用,TS就会报错。
如果改成private,那么子类中使用到这两个属性的地方也会报错。
TS中的静态属性,静态方法,多态以及抽象类
静态属性以及静态方法
TS中声明静态属性及方法需要用到一个关键词:static。此关键词修饰的属性或者方法只能由类直接调用不能由实例调用
class Animal{
public name:string
public age:number
static color:string = 'red'
constructor(name:string,age:number){
= name
this.age = age
}
static dark(){
console.log(`小动物是${this.color}的`);
}
}
let animal = new Animal('小动物',2);
animal.dark(); // 报错
Animal.dark(); // 小动物是red的
tips:静态方法只能调用静态属性。
多态
多态:父类定义一种方法不去实现,而由继承的子类对此有不同的实现的一种行为
class Animal{
public name:string
public age:number
constructor(name:string,age:number){
= name
this.age = age
}
dark(){}
}
class Dog extends Animal{
constructor(name:string,age:number){
super(name,age)
}
dark():number{
return 13
}
}
class Cat extends Animal{
constructor(name:string,age:number){
super(name,age)
}
dark():String{
return '1121'
}
}
这就是一个简单的多态,父类Anima定义了一个dark函数没有实现其具体行为,而继承他的Dog和Cat类对此分别有不同的实现。
TS中的抽象类
抽象类:提供其他类继承的基类,不能被直接实例化,用abstract来定义抽象类和抽象方法,抽象方法只能放在抽象类中,抽象方法不包含具体实现,只能在其派生类中实现。抽象类和抽象方法用来定义标准。
一个简单的抽象类:
abstract class Animal{
name:string
age:number
constructor(name:string,age:number) {
= name
this.age = age
}
abstract dark():void;
}
如果直接将抽象类实例化则会报错。抽象类只能由继承自它的子类来实现其具体行为。
tips:继承自抽象类的类必须实现抽象类定义的抽象方法,不然也会报错。
以上面那个Animal抽象类为例,由于它内部有个dark抽象方法,所以它的子类必须实现此方法,不然也会报错
abstract class Animal{
name:string
age:number
constructor(name:string,age:number) {
= name
this.age = age
}
abstract dark():void;
}
class Dog extends Animal{
constructor(name:string,age:number){
super(name,age)
}
// dark():void{
// console.log(111);
// } 不实现dark方法会报错
}
TS中的接口
接口的定义以及使用
typescript中的接口:行为和动作的规范,对批量方法进行约束
定义看起来似乎比较抽象。我们来看这样一个例子。
如果我们需要定义一个函数,他的参数可以是以下三种类型的string,Boolean,number。很自然的我们可以想到可以这样实现。
function someMethods(name:string|number|boolean){
console.log(name)
}
是的,如果只有一个这样的函数是可以这样写。但是如果有成百上千的这样的函数参数都需要这样定义。那么如果每一个函数都是这样定义就会显得十分冗余。
function someMethods(name:string|number|boolean,age:number){
console.log(name,age)
}
function someMethods1(name:string|number|boolean,age:number){
console.log(name,age)
}
function someMethods2(name:string|number|boolean,age:number){
console.log(name,age)
}
...
...
这时候就可以用上接口来定义一个规范。
interface paramsRules{
name:string|number|boolean
age:number
}
function someMethods(paramsObj:paramsRules){
console.log(paramsObj.name,paramsObj.age)
}
function someMethods1(paramsObj:paramsRules){
console.log(paramsObj.name,paramsObj.age)
}
function someMethods2(paramsObj:paramsRules){
console.log(paramsObj.name,paramsObj.age)
}
这样定义的规范就可以在方法中批量使用啦!
函数类型接口
同样的,函数类型接口就是对函数进行规范。无非就是参数和返回值进行规范。
如果你想完成一些函数,这些函数有固定类型的参数和返回值,你又担心由于自己的粗心大意而写错其中的某些函数,那么就可以定义一个函数类型接口,TS就会使用接口来规范函数的写法。
interface methodRules{
(firstNum:number,secondNum:number):number
}
let fn:methodRules = function(a:number,b:number){
return a+b
}
tips:使用函数类型接口规范函数行为之后,函数的实现必须符合接口定义的规范
可索引接口
规范通过索引得到值的数据类型的接口。
通过索引得到值,其实就是指数组、对象这些数据结构了。(用的不多,了解下即可)
(1)、规范数组
interface arrRules{
[index:number]:string
}
let arr:arrRules = ['1111']
上述规则规范了一个索引必须为数字,值必须为字符的数组
tips:使用接口规范数组时,index必须为number。
(2)、规范对象
interface objRules{
[index:string]:boolean
}
let obj:objRules = {
name:true
}
上述接口规范了一个属性名必须为字符串,属性值必须为布尔值的对象。
类类型接口
顾名思义,用来规范类格式的接口。可以看成是属性接口和函数接口的结合。因为一个类可以看成是属性和函数的结合。
interface classRules{
name:string,
age:number,
dark(name:string):void
}
class Animal implements classRules{
name:string
age:number
constructor(name:string,age:number){
= name
this.age = age
}
dark(name:string):void{
console.log(1)
}
}
注意到实现类类型接口的关键词implements
接口的扩展以及继承
如果此时我们接手其他的一个项目,亦或者有个第三方库已经定义了一个现成的Animal接口。
原animal接口
interface AnimalRules{
name:string,
age:number
run():void
dark():void
}
这个animal接口定义了实现类必须要有name,age属性,必须实现run和dark方法。
此时我们想在此基础上再实现一个human接口。扩展了work属性(人可以有工作),和useTool方法。可以使用接口的继承来实现此目的。
interface AnimalRules{
name:string,
age:number
run():void
dark():void
}
interface humanRules extends AnimalRules{
work?:string,
useTools():void
}
class Human implements humanRules{
name:string
age:number
work?:string
constructor(name:string,age:number,work?:string){
= name
this.age = age
this.work = work
}
run():void{
console.log("动物都会跑")
}
dark():void{
console.log('动物都会叫')
}
useTools():void{
console.log('人会使用工具')
}
}
tips:接口的继承其实很简单。实现继承接口的类不仅需要实现其子接口的规范,同时需要实现其父接口的规范。
泛型,泛型函数以及泛型类
如果有这样一个需求,我们需要实现一个echo(回音)函数,它的作用是输入什么参数,返回值就是什么参数(同样类型的)。我们很容易就可以想到这样的写法。
function echo(key:string):string{
return key
}
然后再写一个number型echo,布尔型echo。。。
此时如果想解决代码冗余问题。有人可能会想到这样的解法。
function echo(key:any):any{
return key
}
但是如此写来就失去了类型校验的功能。比如你传入一个number,返回一个string同样可以编译通过(当然在此例中不可能会这样)
泛型就可以很好的解决此类问题。
泛型函数
function echo<T>(key:T):T{
return key
}
用泛型函数解决此类问题就是这么简单。首先使用将函数声明成一个泛型函数,这个T可以换成任何JS合法变量名,之后就可以在函数的任意地方使用这个T。它可以指代TS中的任意性变量。同时使用T的变量类型需要保持一致。
既然在函数定义时声明了T,那么在调用时同样需要指定T的类型。
泛型函数的调用:
echo<string>('ddd');
tips:调用时的入参需要和指定的类型保持一致!
泛型类
泛型类和泛型函数的使用方法大同小异了。
class Animal<T>{
name:T
age:T
constructor(name:T, age:T){
= name
this.age = age
}
}
let dog = new Animal<number>(111,222);
简直一毛一样,声明时使用泛型,调用时指定泛型。
泛型接口
直接改写之前写过的函数接口的栗子
interface fnRules{
(name:string):string
}
let fn:fnRules = function(name:string):string{
return name
}
一个普通的函数型接口,此时我们需要改写成泛型类接口。很简单,直接像改写泛型函数一样改写此接口即可
interface fnRules{
<T>(name:T):T
}
let fn:fnRules = function<T>(name:T):T{
return name
}
把类作为参数类型的泛型类
既然说泛型可以指代任意类型,也同样的可以指代我们自定义的类型。
比如我们手写一个mangoDB类
class MangoDB<T>{
dataBase:Array<any> = []
add(dataType:T){
this.dataBase.push(dataType)
}
}
我们在此类中定义了一个add方法,意为向数据库中push数据。同时我们对push的数据有格式要求。要有姓名,年龄,可选属性工作。
此时定义一个类来规范格式
class MangoDB<T>{
dataBase:Array<any> = []
add(dataType:T){
this.dataBase.push(dataType)
}
}
class DataRules{
name:string
age:number
work?:string
constructor(name:string,age:number,work:string){
= name
this.age = age
this.work = work
}
}
let data = new DataRules('wxs',12,'前端开发')
let data_2 = {
name:123123
}
let mangoDB = new MangoDB<DataRules>()
mangoDB.add(data) //添加成功
mangoDB.add(data_2) // 添加失败,因为不符合泛型指定的数据格式
看到这里,博主想到,规范数据格式之前提到的好像可以使用接口。于是乎写了一个接口来作为参数类型的泛型测试之后也可以用。(意外收获)
class MangoDB<T>{
dataBase:Array<any> = []
add(dataType:T){
this.dataBase.push(dataType)
}
}
interface DataRules{
name:string
age:number
work?:string
}
let mangoDB = new MangoDB<DataRules>()
TS中的模块
外部模块
外部模块简称为模块。
我们可以把一些常用的函数写到一个文件里面作为模块。通过export暴露给其他模块。其他模块可以用import按需导入。
新建一个module.ts文件放在index.ts的同级目录下。
如果需要导出,直接在变量或者函数声明的语句前加上export即可。
此时module.ts就是一个模块,它暴露了一个变量a。我们尝试在index.js中引入它。
index.ts文件编写代码如下
import { a } from './module'
console.log(a);
注意这段代码是不能在浏览器直接打印的,似乎是浏览器还没有支持export,import的写法。我们可以用node来编译此代码。同样也很简单。终端切换到index.js目录下(注意是JS文件哦)。使用node来编译index.js代码。
由此见得模块导入成功。
导出函数也是同样的写法。修改module.ts代码如下
export function printUserName(name:string){
console.log(name)
}
在index.ts中调用此函数
import { printUserName } from './module'
printUserName('wxs')
在node中一样可以调用成功
如果我们不喜欢函数暴露出来的变量名,想改成我们自己熟悉的变量名可以按照如下写法修改
import { printUserName as myFn } from './module'
myFn('wxs')
还有一种暴露变量的方式是export default
使用方法
function printUserName(name:string){
console.log(name)
}
export default printUserName
使用方法
import printUserName from './module'
printUserName('wxs')
注意,这里导入不需要加中括号了。
export可以在文件中多次使用以达到多次导出的目的。export default只能导出一次。
内部模块
TS中的内部模块又叫命名空间。
如果我们编写一个功能模块需要和其他人协同完成。那么就有可能出现变量名重复的情况。使用命名空间就可以很好的解决此类问题。使用方法很简单。直接上代码展示
namespace programer_1{
export let aaa:number = 1
export function dark():void{
console.log('汪汪汪')
}
}
namespace programer_2{
export let aaa:number = 2
export function dark():void{
console.log('喵喵喵')
}
}
let a_1 = programer_1.aaa;
console.log(a_1)
programer_1.dark()
let a_2 = programer_2.aaa;
console.log(a_2)
programer_2.dark()
TS中的装饰器
简单的了解完Angular之后发现在Angular中修饰器的使用还是相当广泛的。因此需要返过头来好好梳理一遍装饰器这一节。
什么是装饰器?
装饰器其实就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
普通装饰器
首先来看一个简单的装饰器栗子。
首先定义一个animal类
class Animal{
name:string
constructor(name:string){
= name
}
dark(name:string){
console.log(`${}都会叫`)
}
}
有name属性和dark方法。此时使用修饰器给这个类加上一个run方法
function decorate(classInstance:any):void{
classInstance.prototype.run = function(){
console.log(`${}都会跑`)
}
}
@decorate
class Animal{
name:string
constructor(name:string){
= name
}
dark(){
console.log(`${}都会叫`)
}
}
let dog = new Animal('小狗');
dog.dark();
dog.run();
此时Animal类上就被装饰上了run方法。装饰器传入的参数就是Animal类。
以上就是装饰器中的普通装饰器。它只有简单的修饰功能,而不能传递参数。
装饰器工厂
装饰器工厂是应用的更为广泛的一种装饰器,它是可以传递参数的,在使用上更加灵活。装饰器工厂简单来说实际上就是在普通装饰器上使用了一个闭包。
function decorate(params:any){
return function(classInstance:any){
classInstance.prototype.run = function(){
console.log(`${}都会跑,外部传递的参数是${params}`)
}
}
}
@decorate(123123123)
class Animal{
name:string
constructor(name:string){
= name
}
dark(){
console.log(`${}都会叫`)
}
}
let dog = new Animal('小狗');
dog.dark();
dog.run(); // index.js:11 小狗都会跑,外部传递的参数是123123123
使用装饰器修改构造函数
装饰器也可以直接对构造函数进行修改。(tips:自我理解,虽说是叫修改构造函数,本质上似乎是返回了一个原类的子类来覆盖原类的属性)
function decorate(classInstance:any){
return class extends classInstance{
name = '我修改了构造函数中的name值'
}
}
@decorate
class Animal{
name:string|undefined
constructor(name:string){
= name
}
dark():void{
console.log(`${}都会叫`)
}
}
let dog:any = new Animal('小狗');
dog.run();
属性装饰器
事实上,装饰器也可以直接修饰类中的属性。
function decorate(params:any){
return function(target:any,keyName:string){
console.log(params); // 属性装饰器传参
console.log(target); // 被修饰类的构造函数
console.log(keyName); // 被修饰属性的属性名
}
}
class Animal{
@decorate(1123123)
public name:String
constructor(name:string){
= name
}
dark(){
console.log(111111);
}
}
let dog = new Animal('小狗')
经打印后,target是被修饰类的构造函数,keyName是被修饰的属性名。
方法装饰器
既然有类装饰器,类属性装饰器,相信类方法装饰器也是必不可少的了
用法同属性装饰器。
function decorate(params:any){
return function(target:any,methodsName:string,fnDescribetion:any){
console.log(params); // 装饰器传参
console.log(target); // 被修饰方法所属的类
console.log(methodsName); // 方法名
console.log(fnDescribetion); // 方法描述信息
}
}
class Animal{
public name:String
constructor(name:string){
= name
}
@decorate(1123123)
dark(name:string){
console.log(`${}都会跑`);
}
}
let dog = new Animal('小狗')
方法参数修饰器
function decorate(params:any){
return function(target:any,methodsName:string,fnDescribetion:any){
console.log(params); //装饰器参数
console.log(target); // 类实例
console.log(methodsName); // 被修饰参数所属方法名
console.log(fnDescribetion); // 参数索引
}
}
class Animal{
public name:String
constructor(name:string){
= name
}
dark(@decorate(1123123) name:string){
console.log(`${}都会跑`);
}
}
let dog = new Animal('小狗')
tips: 以上装饰器装饰的如果是实例成员,那么target是类的原型对象。如果装饰的是静态成员,那么target就是类的构造函数
装饰器优先级
属性> 方法 > 方法参数装饰器 > 类装饰器