写下博客主要用来分享知识内容,并便于自我复习和总结。
如有错误之处,请各位大佬指出。
命名空间
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。同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);
}
@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();
所以执行顺序就是:属性 ,方法, 参数, 类
如果有多个同样装饰器,它会先执行后面的。