本文共 8109 字,大约阅读时间需要 27 分钟。
写下博客主要用来分享知识内容,并便于自我复习和总结。 如有错误之处,请各位大佬指出。
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。同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/