TypeScript学习笔记
TypeScript是JavaScript的超集,任何合法的JS程序都是合法的TypeScript程序
TypeScript通过向JavaScript增加可选的静态类型声明来把JavaScript变成强类型程序语言。
静态类型声明可约束函数、变量、属性等程序实体。
TypeScript语言内部分为三层:
1. 语言层:实现所有TypeScript语言特性
2.编译层:执行编译,类型检查,将TypeScript代码转换成JavaScript代码
3.语言服务层:生成信息以帮助编译器和其他工具来提供更好的辅助特性
语言特性
全局安装:npm install -g typescript
使用WebStrom调试
1. File - Settings - Languages & Frameworks - TypeScript - TypeScript 路径选取全局安装路径
2. 项目 - New - tscofing.json 新增转换项目
此时可以转换成js代码,然后调试添加node指向js代码即可。
使用命令行调试
tsc demo1.ts 可以将TypeScript转换为JavaScript文件
可选静态类型声明
可以声明一个变量的类型,当没有类型声明的时候,会尝试检查赋值。
1var counter; 2var counter : number; 3var counter : number = 0;
基本类型
boolean:true和false,表示是否正确
number:浮点数
string:文本
array:数组,let list : number[] = [] or let list:Array<number> = []
enum:枚举,enum Color {Red,Blue}; var c : Color = Color.Red
any:任意JS值进行最小静态化检查,var list:any[] = [1,true,'']
void:没有返回值
注:undefined 和 null,不能作为变量类型赋值
联合类型
声明可以存储多种类型值变量
let path : string|string[]
类型守护
可以在运行时使用typeof或者instanceof运算符对类型进行验证
环境声明
允许在TypeScript代码中创建一个不会被编译到JavaScript中的变量。
如果希望调用一个未被定义的对象上的方法,可以使用declare操作符创建一个环境声明。
此时就不会有编译错误
TypeScript,支持do while \ for in \ while \ for ,四种循环方式
函数通过三种方式声明
1//具名函数 2function greet(name?:string){} 3//匿名函数 4const greet = function(name?:string){} 5//箭头函数 6const greet = (name:string):string=>{}
Bower
包管理工具,只用于管理前端的依赖。因此Bower中的许多包也都针对前端做过优化。
安装:npm install -g bower
创建配置文件:bower init 与 npm init 异曲同工
安装包:bower install jquery --save-dev
所有Bower包都保存在bower_components目录下,也需要添加.gitignore中
tsd
TypeScript包含一个lib.d.ts文件,描述JS内建对象、文档对象(DOM)、浏览器对象(BOM)的API。
扩展名为.d.ts的文件是一种特殊的TypeScript文件,称为类型定义文件或描述文件。
一个类型描述文件通常对包含第三方库API的类型声明,使这些库现存的JS库与TS集成在一起。
例如:TypeScript调用jQuery会得到报错。
$.ajax({}); //cannot find name '$'
需要添加jQuery描述文件的引用,例如
///<reference path="node_modules/@types/jquery/index.d.ts" />
tsd就是包含这些引用的开源项目DefinitelyTyped
tsd用来管理TypeScript应用中描述文件的工具,和npm\bower工具一样,也有自己的配置文件tsd.json
安装:npm install tsd -g
创建配置文件:tsd init
安装描述文件:tsd install jquery --save
但是这种方法目前用不了,使用过渡方法:npm install --save @types/jquery
官网:http://definitelytyped.org/
自动化工具
自动化工具可以自动完成开发中的重复任务,例如:编译TS文件,压缩JS文件等
如今最流行的两个自动化工具就是,Grunt 和 Gulp
Grunt中使用文件作为任务的输入和输出,Gulp中是使用流。Grunt插件使用键值对配置,Gulp插件使用代码描述任务。
可读性Gulp高于Grunt.
Gulp
安装:npm install -g gulp
开发依赖:npm install gulp --save-dev
项目根目录创建gulpfile.js,用来配置gulp命令,内容如下:
1let gulp = require('gulp'); 2 3gulp.task('default',function(){ 4 console.log("Hello Gulp"); 5});
注意此时不能使用import引入,否则无法进行测试。
执行gulp命令,会检索当前目录下的gulpfile.js,找到后执行default任务。
代码质量
可以使用Gulp插件中的tslint来检查代码质量。
安装:npm install typescript --save-dev
安装:npm install tslint --save-dev
安装:npm install gulp-tslint --save-dev
添加新任务(没起作用不知道为什么)
1var gulp = require('gulp'); 2var tslint = require('gulp-tslint'); 3 4gulp.task('lint', function () { 5 return gulp.src([ 6 './source/**.ts', './test/**/**.test.ts' 7 ]).pipe(tslint()) 8 .pipe(tslint.report('verbose')); 9}); 10gulp.task('default', ['lint']);
编译TypeScript代码
添加两个编译TypeScript代码的任务,一个编译应用的逻辑代码,另一个编译单元测试的代码
安装:npm install gulp-typescript --save-dev
创建新的gulp-typescript项目对象
1var ts = require('gulp-typescript'); 2var tsProject = ts.createProject({ 3 removeComments: true, 4 noImplicitAny: true, 5 target: 'ES5', 6 module: "commonjs", 7 declarationFiles: false 8});
上面代码将TypeScript编译器作为依赖加载进来,然后创建tsProject对象。
包含TypeScript编译器在编译我们的代码时,需要带上的参数。现在编辑应用源代码
1gulp.task('tsc', function () { 2 return gulp.src('./source/**/**.ts') 3 .pipe(ts(tsProject)) 4 .js.pipe(gulp.dest('./source/js')); 5});
tsc任务会去查找./source/目录及其子目录下所有的.ts文件,然后将他们作为一个输入流传递给TypeScript编译器
编译器会使用tsProject对象的属性作为编译时的参数,并将编译后的JavaScript代码保存在./source/js目录下
针对不同地方的编译,例如单元测试编译。代码格式一样
再次修改default任务
gulp.task('default', ['tsc']);
优化TypeScript应用
编译完TypeScript代码后,会为每个TypeScript文件生成对应的JavaScript文件。
但是他们不能运行在浏览器中,因为在浏览器运行的唯一方法就是添加<script>标签。
或者还有以下两种方案
1. 使用RequireJS这样的库,通过AJAX来按需加载各个文件。这种做法称为异步模块加载。
2. 将TypeScript模块参数配置成CommonJS,然后使用Browerify这样的工具,解析应用的模块和依赖。最后生成一个包含应用里所有模块且高度优化的JS文件。
这里使用CommonJS方法,因为Browserify和Gulp是高度集成的。
安装:npm install browserify vinyl-transform gulp-uglify gulp-sourcemaps
导入这些包,然后对他们进行初始化。创建main.js输入下方代码
1var browserify = require('browserify'); 2var transform = require('vinyl-transform'); 3var uglify = require('gulp-uglify'); 4var sourcemaps = require('gulp-sourcemaps'); 5 6var browserified = transform(function (filename) { 7 var b = browserify({entries: filename, debug: true}); 8 return b.bundle(); 9});
browserified函数会将普通的Node.js流转换为Gulp流。
实现实际的任务
1gulp.task('bundle-js', function () { 2 return gulp.src('./main.js') 3 .pipe(browserified) 4 .pipe(sourcemaps.init({loadMaps: true})) 5 .pipe(uglify()) 6 .pipe(sourcemaps.write('./')) 7 .pipe(gulp.dest('./dist/source/js')); 8});
main.js 作为入口,Browserify会从这个入口出发,追踪应用里所有任务的模块和依赖,然后生成一个高度优化的JS文件的流。
接着会使用uglify插件来最小化输出,这将会减少应用的加载时间,但是会让BUG难以追踪。
我们生成一个source map文件来简化追踪BUG的过程。uglify会移出代码中的空行和空格,并缩减变量名的长度。
source map使得我们在追踪bug时能够将最小化后的代码映射到源文件中对应的地方。
我们可以轻松的追踪一个最小化后的代码的bug,在Chrome和Firefox的开发者工具里,已经內建了对source map的支持。
目前,代码还不能跑,因为现在里面的任务是并行的。需要改成按顺序执行。
管理Gulp任务的执行顺序
Gulp默认的是一步执行所有任务的,有三种方法让一个任务同步执行
1.传递回调函数
2.返回一个流
3.返回一个promise
1//传递回调函数 2gulp.task('sync',function(cb){ 3 setTimeout(function(){ 4 cb(); 5 },1000) ; 6}); 7 8//返回一个流 9gulp.task('sync',function(){ 10 return gulp.src('js/*.js') 11 .pipe(concat('script.min.js')) 12 .pipe(uglify()) 13 .pipe(gulp.dest('../dist/js')) 14});
也可以通过run-sequence的Gulp插件,可以更好的控制任务执行顺序。
安装:npm install run-sequence --save-dev
1var runSequence = require('run-sequence'); 2gulp.task('default',function(cb){ 3 runSequence( 4 'lint', // lint 5 ['tsc'], // 并行执行tsc 6 ['bundle-js'], //并行执行bundle-js 7 'karma', // test 8 'browser-sync', // callback 9 cb 10 ); 11})
自动化测试工具
自动化测试工具让我们能够自动化执行应用里面的单元测试。
通过自动化测试工具,可以自动在多个浏览器内执行应用的测试套件,而不用打开浏览器运行测试。
自动化测试工具为Karma,Karma可以和多个流行的单元测试框架兼容。这里的测试框架为Mocha,Chai(断言库),Sinon(数据模拟框架)
安装测试框架:npm install mocha chai sinon --save-dev
安装Karma作为开发依赖:npm install karma karma-mocha karma-chai karma-sinon karma-coverage karma-phantomjs-launcher gulp-karma --save-dev
1var karma = require('gulp-karma'); 2gulp.task('karma', (cb) => { 3 gulp.src('./dist/test/**/**.test.js') 4 .pipe(karma({configFile: 'karma.conf.js', action: 'run'})) 5 .on('end', cb) 6 .on('error', (err) => { 7 throw err; 8 }) 9});
获取了test及其子目录下所有.test.js文件,然后把他们连同karma.conf.js文件一同传递给Karma插件。
需要在根目录下创建一个名为karma.conf.js的文件,
1module.exports = function (config) { 2 config.set({ 3 basePath: '', 4 frameworks: ['mocha', 'chai', 'sinon'], 5 browsers: ['PhantomJS'], 6 reporters: ['progress', 'coverage'], 7 plugins: [ 8 'karma-coverage', 9 'karma-mocha', 10 'karma-chai', 11 'karma-sinon', 12 'karma-phantomjs-launcher' 13 ], 14 preprocessors: { 15 './test/*.test.js': ['coverage'] 16 }, 17 port: 9876, 18 colors: true, 19 autoWatch: false, 20 singleRun: false, 21 logLevel: config.LOG_INFO 22 }); 23};
配置文件说明了Karma应用根目录、框架、浏览器、插件需要报告的测试执行期间的信息。
PhantomJS是一个无界面Web浏览器,使用它就无须打开一个真正的Web浏览器,而执行单元测试代码。
跨设备测试同步
在浏览器中运行我们的应用,需要先安装browser-sync包
安装:npm install -g browser-sync
版本不一致,暂且搁置
脚手架工具
可以自动生成项目的文件结构、构建脚本等。
最流行的就是Yeoman,内置命令yo,同时也是包管理工具,基于模板生成项目
省城项目的模板在Yeoman中共称为生成器。生成器地址:http://yeoman.io/generators/
安装:npm install -g yo
安装生成器:npm install -g angular-typescript-gulp typings
安装完成后结合yo命令一同使用:yo angular-typescript-gulp
接着根据提示填写就可以了。
函数
参数可以设置静态类型声明,返回值也可以设置静态类型声明。比如下方代码,就会编译报错,因为需要返回number
function named(name: string): number { return "";}
具名函数和匿名函数,行为并不一样。具名函数会优先识别,匿名函数除非表达式被赋值,否则不会被执行。
当我们需要匿名函数返回本身时,可以进行如下定义,来减少冗余的类型声明。
1var greetUnnamed: (name: string) => string = function (name: string): string { 2 return "Chenxy" 3}
如果不需要返回值可以写成
(name : string) : void;
注意:函数类型和变量类型,必须相同
可选参数
可选参数可以在参数名后方添加?号来防止编译报错
1function add(foo?: number) { 2 console.log(foo); 3}
可以在类型声明后面添加默认参数
1function add(foo: number = 1) { 2 console.log(foo); 3}
rest参数
TypeScript也可以写rest参数来用一个数组接受多个参数。同样也支持扩展运算符
1function add(...foo: number[]) { 2 console.log(foo); 3} 4 5add(1, 2, 3, 4); //[ 1, 2, 3, 4 ]
函数重载
不同类型的方法可以通过声明一个函数的所有函数签名,然后将逐个签名一一实现,来达到函数重载的功效。
1function foo(age: number): string; 2function foo(name: string): string; 3function foo(value: (number | string)): string { 4 switch (typeof value) { 5 case "string": 6 return `string`; 7 case "number": 8 return `number`; 9 } 10} 11 12console.log(foo(1)); //number 13console.log(foo("chenxy")); //string
实现签名必须兼容所有的重载签名。直接执行签名函数会得到一个编译错误。
泛型
可以使用T来定义泛型参数
1///<reference path="node_modules/@types/jquery/index.d.ts" /> 2function getEntitytes<T>(name: string, entity: T): void { 3 console.log(`name=${name},entity=${entity.get()}`); 4} 5 6class User { 7 _name: string; 8 get = () => this._name; 9 constructor(name) { 10 this._name = name; 11 } 12} 13 14getEntitytes<User>("chenxy", new User("chenxy"));
类
TS中也可以使用public或private访问符。
接口
可以定义实现规则,接口的规则就是方法和属性的签名,继承接口的类必须实现他们。
接口有两点不同:
1. 接口可以扩展其他接口或者类
2. 接口可以定义数据和行为而不只是行为
继承
使用extends可以继承某个父类,会继承所有方法和属性
class Teacher extends Person
使用super.method() 可以调用父类方法,来实现重写
如果子类有构造,则必须要构造中 super() 来构造父类,否则无法使用。
使用implements可以继承多个接口,必须实现接口中定义的方法或属性签名。
1interface IUser{ 2 get():string; 3 set:string; 4} 5 6class User implements IUser{ 7 constructor(name) { 8 this._name = name; 9 } 10 set: string; 11 _name: string; 12 get = () => this._name; 13}
泛型约束
使用extends可以给泛型进行接口约束。
1function getEntitytes<T extends IUser>(name: string, entity: T): void { 2 console.log(`name=${name},entity=${entity.get()}`); 3} 4 5interface IUser{ 6 get():string; 7 set:string; 8} 9 10class User implements IUser{ 11 constructor(name) { 12 this._name = name; 13 } 14 set: string; 15 _name: string; 16 get = () => this._name; 17} 18 19getEntitytes<User>("chenxy", new User("chenxy"));
多重约束
泛型约束不能进行多重约束,但是可以使用组合接口来实现
需要定义组合接口,并且继承接口A\B,然后泛型约束组合接口即可。
1interface IA { 2 A: string; 3} 4 5interface IB { 6 B: string; 7} 8 9interface IC extends IA, IB { 10 A: string; 11 B: string; 12} 13 14class C implements IC { 15 A: string = "CA"; 16 B: string = "CB"; 17} 18 19class Person<T extends IC> { 20 Entity: T; 21 22 constructor(entity: T) { 23 this.Entity = entity; 24 } 25 26 Con(): void { 27 console.log(`A=${this.Entity.A},B=${this.Entity.B}`) 28 } 29} 30 31const P = new Person(new C()); 32P.Con();
创建新泛型
如果我们要创建新的泛型(new T()),需要使用下方代码
1Copy(): T { 2 var type: { new(): T; } 3 return new type(); 4 }
命名空间
直接嵌套在类上面即可。namespace app {}
模块
TypeScript使用模块加载器来进行模块加载。
模块加载器是在模块加载过程中为我们提供更好控制能力的工具,能优化加载任务,比如异步加载文件或轻松合并多个模块到单一的高度优化文件中。
不推荐使用<script>,因为浏览器不会异步加载此标签。
使用ES6进行模块加载时,需要使用export关键字来定义外部模块。跟ES6语法一致
1class UserModel { 2 3} 4 5export {UserModel}
引入使用 import ,跟ES6语法一致。
应用测试
软件测试术语
断言:一个条件,是必须被测试确认的一段代码的行为是否与期望项目,与要求一致。
测试规范:软件开发人员用来指代测试规范的一个术语,是一个详细的清单,包含了需要测试的场景,如何被测试等。
测试用例:决定一个程序中的功能是否按照原始期望工作的一些条件。断言是一个条件、测试用例是一组条件。
测试套件:许多测试用例的集合。一个测试套件可以包含多个测试用例来覆盖很多的场景。
测试监控:某些测试框架提供的功能,允许我们包括一个函数,并记录它的使用情况。
替身:测试执行时被传入但并没有实际用到的对象。
测试桩:允许包括一个方法然后观察它的使用情况。
模拟:为测试提供输入来决定测试是否能够通过。
测试覆盖率:指程序中有多大比例的代码通过自动化测试被测试到。
必备工具
npm init
添加Gulp运行必要任务:npm install gulp -g
添加Karma自动执行测试:npm install --save-dev karma
Karma插件更容易创建测试覆盖率报告:npm install --save-dev karma-coverage
Istanbul指出哪一行代码在自动化测试中被测试到的工具。生成覆盖率报告
Mocha测试框架库,方便的创建测试套件、测试用例和测试规范:npm install --save-dev mocha karma-mocha
Chai支持测试驱动开发和行为驱动开发的断言库:npm install --save-dev chai karma-chai
Sion.JS一个独立的框架提供API可以独立测试一个组件:npm install --save-dev sinon karma-sinon
类型定义防止ts用第三方库编译错误:
npm install --save-dev @types/mocha
npm install --save-dev @types/chai
npm install --save-dev @types/sinon
npm install --save-dev @types/jquery
PhantomJS无显示页面的浏览器:npm install --save-dev phantomjs / npm install --save-dev karma-phantomjs-launcher
Selenium测试运行期,只运行端对端(E2E)测试的特定测试。
Nightwatch.JS自动化测试框架,使用Selenium网络驱动API,完整的浏览器(E2E)测试解决方案。
npm install --save-dev gulp-nightwatch
npm install --save-dev selenium-standalone -g
selenium-standalone install (需要JAVA环境)
测试计划和方法
测试方面主要两种风格或方法可选:测试驱动开发(TDD)和行为驱动开发(BDD)
测试驱动开发
鼓励开发者在写程序代码之前写测试。使用TDD编码包含以下基本步骤:
1. 编写不通过的测试
2. 运行这个测试,保证不通过
3. 编写应用代码,让测试通过。
4.运行这个测试,保证通过
5.运行所有其他测试,保证没有被破坏
6.重复以上步骤
TDD是被推荐的,因为他会极大程度的帮助你和你的团队增加程序的测试覆盖率。
行为驱动测试
描述并阐明测试应该关注程序的需求而非测试的需求。鼓励开发者少思考测试这件事而更多的思考整个程序。
测试类型
单元测试:通过设置测试模拟和依赖注入来尽可能的让测试独立。
部分集成测试和整体集成测试:用来测试一组组件或整个程序。
回归测试:用来确认程序错误是否被修复。
性能/加载测试:用来确认程序是否达到性能预期。
端对端(E2E)测试:用来测试一组组件或整个程序。
验收测试(UAT):验收是否符合用户的所有需求
基础结构
根目录创建两个文件夹source、test
Gulp构建
npm install --save-dev gulp
npm install --save-dev browserify
npm install --save-dev vinyl-source-stream
npm install --save-dev vinyl-buffer
npm install --save-dev gulp-run
npm install --save-dev gulp-nightwatch
npm install --save-dev tslint
npm install --save-dev typescript
npm install --save-dev gulp-tslint
npm install --save-dev gulp-typescript
npm install --save-dev browser-sync
npm install --save-dev karma
npm install --save-dev gulp-uglify
npm install --save-dev gulp-docco
npm install --save-dev run-sequence
npm install --save-dev gulp-header
未完待续
装饰器
只有TypeScript1.5或更高版本才可以使用。
安装:
npm install --save-dev gulp gulp-typescript typescript
npm install --save reflect-metadata
创建gulpfile.js文件,添加编译代码的任务。
1var gulp = require('gulp'); 2var ts = require('gulp-typescript'); 3var typescript = require('typescript'); 4 5var tsProject = ts.createProject({ 6 removeComments: false, 7 noImplicitAny: false, 8 target: 'ES5', 9 module: "commonjs", 10 declarationFiles: false, 11 emitDecoratorMetadata: true, 12 typescript: typescript 13}); 14 15gulp.task("build-source", function () { 16 return gulp.src(__dirname + "/file.ts") 17 .pipe(tsc(tsProject)) 18 .js 19 .pipe(gulp.dest(__dirname + "/")); 20})
注解:一种为类声明添加元数据的方法,然后元数据就可以被诸如依赖注入容器这样的工具所使用()。
装饰器:是ECMAScript7标准的特性,用来在代码设计时注释和修改类和类的属性。
只需要关注装饰器即可,因为他是真正的语言标准。
使用下面的类,来说明如何使用装饰器。
1class Person { 2 public Name: string; 3 public Surname: string; 4 5 constructor(private name: string, private surname: string) { 6 this.Name = name; 7 this.Surname = surname 8 } 9 10 public saySomething(something: string): string { 11 return `${this.Name} ${this.Surname} says: ${something}`; 12 } 13}
装饰器一共四中,分别用来装饰:类、属性、方法、参数
类装饰器
类装饰器用来修改类的构造函数,如果返回undefined那么类仍然使用原来的构造函数。
如果装饰器有返回值,那么返回值会被用来覆盖类原来的构造函数。
创建一个logClass的类装饰器。
function logClass(target:any){}
使用装饰器需要在类上面 @logClass
如果已经声明并使用一个装饰器,经过TypeScript编译后的代码中会有一个__decorate的函数。
1@logClass 2class Person { 3 public Name: string; 4 public Surname: string; 5 6 constructor(private name: string, private surname: string) { 7 this.Name = name; 8 this.Surname = surname 9 console.log('Person'); 10 } 11 12 public saySomething(something: string): string { 13 return `${this.Name} ${this.Surname} says: ${something}`; 14 } 15} 16 17function logClass(target: any) { 18 var orginal = target; 19 20 function construct(constructor, args) { 21 var c: any = () => constructor.apply(this, args); 22 c.prototype = constructor.prototype; 23 return new c(); 24 } 25 26 var f: any = (...args) => { 27 console.log('logClass'); 28 return construct(orginal, args); 29 } 30 f.prototype = orginal.prototype; 31 return f; 32} 33 34new Person("chen", "xy"); //logClass Person
类装饰器接受一个参数,即类的构造函数。意味着target参数就是Person类的构造函数。
装饰器先复制类的原有构造,然后定义一个名为construct的工具函数来生成类的实例。
装饰器用来为元素添加一些额外的逻辑或元数据,我们想要拓展一个函数的功能时。
需要往原函数上包一个新的函数,有额外逻辑,且能执行原函数的方法。
方法装饰器
与类装饰器相似,用来覆盖类的方法。
如果返回的不是undefined,那么返回值将会覆盖方法的属性描述对象。
被调用时,带有以下参数:
包含了被装饰方法的类的原型,即Person.prototype
被装饰方法的名字,即saySomething
被装饰方法的属性描述对象,即Obejct
1class Person { 2 public Name: string; 3 public Surname: string; 4 5 constructor(public name: string, private surname: string) { 6 this.Name = name; 7 this.Surname = surname 8 console.log('Person'); 9 } 10 11 @logMethod 12 public saySomething(something: string): string { 13 return `${this.Name} ${this.Surname} says: ${something}`; 14 } 15} 16 17function logMethod(target:any,key:string,descriptor:any){ 18 var orginalMethod = descriptor.value; 19 descriptor.value = function(...args:any[]){ 20 var a = args.map(a=>JSON.stringify(a)).join(); 21 var result = orginalMethod.apply(this,args); 22 var r = JSON.stringify(result); 23 console.log('logMethod'); 24 return result; 25 } 26 return descriptor; 27} 28 29const P = new Person("chen", "xy"); 30console.log(P.saySomething("c")); //logClass Person
创建被装饰元素的副本。
创建一个新函数来替代被装饰的函数,新函数除了调用原函数之外,还包含一些额外逻辑。
属性装饰器
一个属性装饰器没有返回值且没有第三个参数
1class Person { 2 @logProperty 3 public Name: string; 4 public Surname: string; 5 6 constructor(public name: string, private surname: string) { 7 this.Name = name; 8 this.Surname = surname 9 console.log('Person'); 10 } 11 12 public saySomething(something: string): string { 13 return `${this.Name} ${this.Surname} says: ${something}`; 14 } 15} 16 17function logProperty(target: any, key: string) { 18 var _val = this[key]; 19 var getter = function () { 20 console.log(`Get:${key} => ${_val}`); 21 return _val; 22 } 23 var setter = function (newValue: any) { 24 console.log(`Set:${key} => ${_val}`); 25 _val = newValue; 26 } 27 if (delete this[key]) { 28 Object.defineProperty(target, key, { 29 get: getter, 30 set: setter, 31 enumerable: true, 32 configurable: true 33 }); 34 } 35} 36 37const P = new Person("chen", "xy"); 38console.log(P.saySomething("c")); //logClass Person
创建原属性副本,声明两个函数:getter\settet
手动删除原属性,并使用Object.defineProperty来创建新属性
参数装饰器
接受三个参数
包含被装饰参数的方法的对象
方法的名字
参数在参数列表中的索引
参数属性没有返回值,意味着不能覆盖包含修饰参数的方法。
1class Person { 2 public Name: string; 3 public Surname: string; 4 5 constructor(public name: string, private surname: string) { 6 this.Name = name; 7 this.Surname = surname 8 console.log('Person'); 9 } 10 11 public saySomething(@addMetadata something: string): string { 12 return `${this.Name} ${this.Surname} says: ${something}`; 13 } 14} 15 16function addMetadata(target: any, key: string, index: number) { 17 var metadataKey = `Log_${key}`; 18 if (Array.isArray(target[metadataKey])) { 19 target[metadataKey].push(index); 20 } else { 21 target[metadataKey] = [index]; 22 } 23} 24 25const P = new Person("chen", "xy"); 26console.log(P.saySomething("c")); //logClass Person
#