⑥TypeScript 命名空间,装饰器(类、属性、方法、参数装饰器,装饰器执行顺序)


写下博客主要用来分享知识内容,并便于自我复习和总结。
如有错误之处,请各位大佬指出。


命名空间

在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。同Java的包一样,TS的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。命名空间内的对象通过export来暴露出来。

namespace A{
    interface Animal{
        name:string;
        eat():void;
    }
    export class Dog implements Animal{
        name:string;
        constructor(name:string) {
            this.name = name;
        }
        eat():void{
            console.log(this.name + "吃肉");
        }
    }
}
let d = new A.Dog('旺财');
d.eat();

模块,我们就可以把命名空间这部分内容抽出来,放在一个文件中,并且一个模块里可能会有多个命名空间,我们这里模拟一下这个功能:
在这里插入图片描述
a.ts:

export namespace A{
    interface Animal{
        name:string;
        eat():void;
    }
    export class Dog implements Animal{
        name:string;
        constructor(name:string) {
            this.name = name;
        }
        eat():void{
            console.log(this.name + "吃肉");
        }
    }
}

export namespace B{
    interface Animal{
        name:string;
        eat():void;
    }
    export class Dog implements Animal{
        name:string;
        constructor(name:string) {
            this.name = name;
        }
        eat():void{
            console.log(this.name + "吃骨头");
        }
    }
}

index.ts:

import {A,B} from './modules/a';
let d = new A.Dog('旺财');
d.eat();
let c = new B.Dog('小黑');
c.eat();

此时,如果直接运行在浏览器中会报错:
在这里插入图片描述
浏览器不认识exports。

解决方法一:使用cmd
在这里插入图片描述
解决方法二:使用webpack

webpack解决ts问题

命名空间和模块的区别:
命名空间:内部模块,主要用于组织代码,避免命名冲突。
模块:TS的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间。


装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。通俗的说,装饰器就是一个方法,可以注入到类、方法、属性、参数上来扩展类、属性、方法、参数的功能。装饰器是过去几年中js的成就之一,已是ES7的标准特性之一。


类装饰器

类装饰器在类声明之前被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。

先看一下最基本用法:

// 装饰器
function logClass(params:any){
    // params就是当前类
    console.log(params);
}
@logClass
class HttpClient{}

此时它会输出:
在这里插入图片描述
(注意@logClass后不能加分号)

在这段代码中,可能会报错,
在这里插入图片描述
它是说,在当前ts版本中,装饰器还在测试使用,它在未来的版本才能正式使用。所以,我们只要去tsconfig文件中,设置一个experimentalDecorators属性就可以了。如果没有tsconfig就去建一个。

在这里插入图片描述

{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "sourceMap": true,
    "experimentalDecorators": true
  },
  "exclude": [
    "node_modules"
  ]
}

然后报错就消失了。

接下来我们看一下,怎么扩展相关功能:

function logClass(params:any){
    console.log(params);
    params.prototype.apiUrl = '动态扩展的属性';
    params.prototype.run = function(){
        console.log('我是run方法');
    }
}
@logClass
class HttpClient{}
// 一定要给一个any数据类型,否则会报错
let http:any = new HttpClient();
console.log(http.apiUrl);

但我们发现,这里我们是不能传参的,它自动就去获取当前这个类,这种装饰器属于普通装饰器。当然,还是有可以传参的装饰器的,它叫做:装饰器工厂。

function logClass(params:string){
    return function(target:any){
        target.prototype.apiUrl = params;
    }
}
// 这样一来,这里传参是params,而target是这个类
@logClass('https://www.baidu.com')
class HttpClient{}
// 一定要给一个any数据类型,否则会报错
let http:any = new HttpClient();
console.log(http.apiUrl);

下面是一个重载构造函数的例子。
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。

function logClass(target:any){
    return class extends target{
        apiUrl:any = '修改后的Url'
    }
}
@logClass
class HttpClient{
    public apiUrl:string;
    constructor() {
        this.apiUrl = '构造函数里的Url';
    }
    getData(){
        console.log(this.apiUrl);
    }
}
let http = new HttpClient();
http.getData();

输出:修改后的Url。

在这里会有个报错,也就是说,这种方式下,我们一定要对原始class里的所有函数重载。
在这里插入图片描述
最后完善一下:

function logClass(target:any){
    return class extends target{
        apiUrl:any = '修改后的Url';
        getData(){
            this.apiUrl = this.apiUrl + '----';
            console.log(this.apiUrl)
        }
    }
}
@logClass
class HttpClient{
    public apiUrl:string;
    constructor() {
        this.apiUrl = '构造函数里的Url';
    }
    getData(){
        console.log(this.apiUrl);
    }
}
let http = new HttpClient();
http.getData();

到这里已经可以发现装饰器的好处,我们只需要在装饰器做一些修改,所有使用这个装饰器的类都会被修改。那么它相比于抽象类、接口、多态,我们就不需要去相应类中按照规范定义了,现在直接在装饰器中扩展,方便管理。


属性装饰器

属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
1、 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2、 属性的名字。

// 类装饰器
function logClass(params:string){
    return function(target:any){

    }
}
// 属性装饰器
function logProperty(params:any){
    return function(target:any,attr:any){
        //静态成员输出构造函数,实例成员输出原型对象
        console.log(target);
        console.log(attr);
        target[attr] = params;
    }
}
@logClass('xxxx')
class HttpClient{
    @logProperty('https://www.baidu.com')
    public apiUrl:string;
    getData(){
        console.log(this.apiUrl);
    }
}
let http = new HttpClient();
http.getData();

通过输出结果,我们可以看到target在这里是原型对象,attr就是被设置装饰器的属性的名字,那我们想通过传参修改它的值,用键值对去设置即可:target[attr] = params。
在这里插入图片描述


方法装饰器

方法装饰器会被应用到方法的属性描述符上,可以用来监视、修改或者替换方法定义。它在运行时传入下列3个参数:
1、 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2、 方法的名字。
3、 方法的属性描述符。
我们来看一下传入的几个参数,并对方法进行扩展:

// 方法装饰器
function get(params:any){
    return function(target:any,methodName:any,desc:any){
        console.log(target);
        console.log(methodName);
        console.log(desc);
        // 扩展方法
        target.apiUrl = 'xxxx';
        target.run = function(){
            console.log('run');
        }
    }
}

class HttpClient{
    url:string;
    @get('https://www.baidu.com')
    getData(){
        console.log(this.url);
    }
}
let http:any = new HttpClient();
console.log(http.apiUrl);
http.run();

在这里插入图片描述
现在虽然扩展了方法,但还不知道如何修改当前方法。这个当前的方法就存储在desc.value中。

function get(params:any){
    return function(target:any,methodName:any,desc:any){
        console.log(desc.value);
    }
}

在这里插入图片描述
那现在我们就去替换它:

// 方法装饰器
function get(params:any){
    return function(target:any,methodName:any,desc:any){
        // 替换装饰器的方法
        desc.value = function(...args:any[]){
            args = args.map((value)=>{
                return String(value);
            })
            console.log('修改后:',args);
        }
    }
}
class HttpClient{
    url:string;
    @get('https://www.baidu.com')
    getData(){
        console.log('getData修改前方法');
    }
}
let http:any = new HttpClient();
http.getData(123,'xxx');

在这里插入图片描述
但是现在这么做是直接替换当前的方法,而不是对其修改,之前方法的内容都被替换了。那如果只想修改,我们可以这么做:

function get(params:any){
    return function(target:any,methodName:any,desc:any){
        // 1、保存当前方法
        let oMethod = desc.value;
        // 2、接受传递过来的参数
        desc.value = function(...args:any[]){
            args = args.map((value)=>{
                return String(value);
            })
            console.log('修改后:',args);
            oMethod.apply(this,args);
        }
    }
}
class HttpClient{
    url:string;
    @get('https://www.baidu.com')
    getData(){
        console.log('getData修改前方法');
    }
}
let http:any = new HttpClient();
http.getData(123,'xxx');

在这里插入图片描述


参数装饰器

参数装饰器表达式会在运行时当作函数被调用,我们可以用它来为类的原型增加一些元素数据。它传入3个参数:
1、 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
2、 方法的名字。
3、 参数在函数参数列表中的索引。
我们先来看一下传入的几个参数:

function logParams(params:any){
    return function(target:any, methodName:any,paramsIndex:any){
        console.log(params);
        console.log(target);
        console.log(methodName);
        console.log(paramsIndex);
    }
}
class HttpClient{
    url:string;
    getData(@logParams('xxxx') uuid:any){
        console.log(uuid);
    }
}
let http:any = new HttpClient();
http.getData(123)

在这里插入图片描述
但从目前来看,参数装饰器有些鸡肋,其它装饰器可以完成这些功能,所以参数装饰器用的很少。


装饰器执行的顺序

所有装饰器放在一起,我们来看一下执行顺序。

function logClass1(params:string){
    return function(target:any){
        console.log('类装饰器1',params)
    }
}
function logClass2(params:string){
    return function(target:any){
        console.log('类装饰器2',params)
    }
}
function logAttribute(params?:string){
    return function(target:any,attrName:any){
        console.log('属性装饰器',attrName)
    }
}
function logMethod(params?:string){
    return function(target:any,methodName:any,desc:any){
        console.log('方法装饰器',methodName)
    }
}
function logParams1(params?:string){
    return function(target:any,methodName:any,paramsIndex:any){
        console.log('参数装饰器1',methodName)
    }
}
function logParams2(params?:string){
    return function(target:any,methodName:any,paramsIndex:any){
        console.log('参数装饰器2',methodName)
    }
}
@logClass1('xxxx')
@logClass2('yyyy')
class HttpClient{
    @logAttribute()
    apiUrl:string;
    @logMethod()
    getData(){}
    setData(@logParams1() attr1:any,@logParams2() attr2:any){ }
}
let http:any = new HttpClient();

在这里插入图片描述
所以执行顺序就是:属性 ,方法, 参数, 类
如果有多个同样装饰器,它会先执行后面的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

只爭朝夕不負韶華

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值