博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
⑥TypeScript 命名空间,装饰器(类、属性、方法、参数装饰器,装饰器执行顺序)
阅读量:3964 次
发布时间:2019-05-24

本文共 8109 字,大约阅读时间需要 27 分钟。

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

命名空间和模块的区别:

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


装饰器

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


类装饰器

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

先看一下最基本用法:

// 装饰器function logClass(params:any){
// params就是当前类 console.log(params);}@logClassclass 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方法'); }}@logClassclass 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' }}@logClassclass 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) } }}@logClassclass 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();

在这里插入图片描述

所以执行顺序就是:属性 ,方法, 参数, 类
如果有多个同样装饰器,它会先执行后面的。


转载地址:http://jmyki.baihongyu.com/

你可能感兴趣的文章
docker上传镜像至Registry时https报错解决方法
查看>>
docker下删除none的images
查看>>
Linux提权获取敏感信息方法
查看>>
Ubuntu 16.04开机A start job is running for Raise network interface(5min 4s)解决方法
查看>>
Ubuntu 16.04开机隐藏菜单缩短时间
查看>>
《Linux内核设计与实现》- Linux的进程
查看>>
用户态切换到内核态的3种方式
查看>>
内核库函数
查看>>
Linux 系统内核空间与用户空间通信的实现与分析
查看>>
64位int类型用printf输出问题
查看>>
进程的状态转换
查看>>
如何查看进程的信息(线程数)
查看>>
Linux中的chage命令
查看>>
linux-详细解析密码文件passwd与shadow
查看>>
su- 与su的区别
查看>>
linux下发邮件mail
查看>>
echo如何手动输出换行
查看>>
身份证的正确使用方法——非常重要的知识
查看>>
ExtJS & Ajax
查看>>
Tomcat在Windows下的免安装配置
查看>>