在学习ES7装饰器语法以前,须要先温习一下ES5的一些基础知识。javascript
假设有对象以下:(便于理解)java
var person = { name: 'TOM' }
在ES5中,对象中的每一个属性都有一个特性值来描述这个属性的特色,他们分别是:git
configurable
: 属性是否能被delete删除,当值为false时,其余特性值也不能被改变,默认值为trueenumerable
: 属性是否能被枚举,也就是是否能被for in循环遍历。默认为truewritable
: 是否能修改属性值。默认为truevalue
:具体的属性值是多少,默认为undefinedget
:当咱们经过person.name
访问name的属性值时,get将被调用。该方法能够自定义返回的具体值是多少。get默认值为undefinedset
:当咱们经过person.name = 'Jake'
设置name属性值时,set方法将被调用,该方法能够自定义设置值的具体方式,set默认值为undefined须要注意的是,不能同时设置value,writeable
与get set
。
咱们能够经过Object.defineProperty
(操做单个)与Object.defineProperties
(操做多个)来修改这些特性值。github
// 三个参数分别为 target, key, descriptor(特性值的描述对象) Object.defineProperty(person, 'name', { value: "TOM" }) // 新增 Object.defineProperty(person, 'age', { value: 20 })
装饰器语法与此相似,当咱们想要自定义一个装饰器时,能够这样写:浏览器
function nameDecorator(target, key, descriptor) { descriptor.value = () => { return 'jake'; } return descriptor; }
函数nameDecorator
的定义会重写被他装饰的属性(getName)。方法的三个参数与Object.defineProperty
一一对应,分别指当前的对象Person
,被做用的属性getName
,以及属性特性值的描述对象descriptor
。函数最后必须返回descriptor
。缓存
使用时也很简单,以下:babel
class Person { constructor() { this.name = 'jake' } @nameDecorator getName() { return this.name; } } let p1 = new Person(); console.log(p1.getName())
在getName
方法前面加上@nameDecorator
,就是装饰器语法。闭包
自定义函数nameDecorator
的参数中,target,就是装饰的对象Person,key就是被装饰的具体方法getName
。app
不能使用装饰器对构造函数进行更改,若是要修改构造函数,则能够经过以下的方式来完成函数
function initDecorator(target, key, descriptor) { const fn = descriptor.value; // 改变传入的参数值 descriptor.value = (...args) => { args[0] = 'TOM'; return fn.apply(target, args); } return descriptor; } class Person { constructor(name, age) { this.init(name, age) } @initDecorator init(name, age) { this.name = name; this.age = age; } getName() { return this.name; } getAge() { return this.age; } } console.log(new Person('alex', 20).getName()); // TOM
如何但愿装饰器传入一个指定的参数,能够以下作。
// 注意这里的差异 function initDecorator(name) { return function(target, key, descriptor) { const fn = descriptor.value; descriptor.value = (...args) => { args[0] = name; return fn.apply(target, args); } return descriptor; } } class Person { constructor(name, age) { this.init(name, age) } @initDecorator('xiaoming') init(name, age) { this.name = name; this.age = age; } getName() { return this.name; } getAge() { return this.age; } } console.log(new Person('alex', 20).getName()); // xiaoming
这里利用了闭包的原理,将装饰器函数外包裹一层函数,以闭包的形式缓存了传入的参数。
咱们也能够对整个class添加装饰器
function personDecorator(target) { // 修改方法 target.prototype.getName = () => { return 'hahahahaha' } // 新增方法,由于内部使用了this,所以必定不能使用箭头函数 target.prototype.getAge = function() { return this.age } return target; } @personDecorator class Person { constructor(name, age) { this.init(name, age) } init(name, age) { this.name = name; this.age = age; } getName() { return this.name; } } var p = new Person('alex', 30); console.log(p.getName(), p.getAge()); // hahahahaha 30
也能够传参数
var xiaom = { name: 'xiaom', age: 22 } function stuDecorator(person) { return function(target) { // 修改方法 target.prototype.getAge = () => { return person.age; } // 添加方法 target.prototype.getOther = () => { return 'other info.' } return target; } } function initDecorator(person) { return function(target, key, descriptor) { var method = descriptor.value; descriptor.value = () => { var ret = method.call(target, person.name); return ret; } } } @stuDecorator(xiaom) class Student { constructor(name, age) { this.init(name, age); } @initDecorator(xiaom) init(name, age) { this.name = name; this.age = age; } getAge() { return this.age; } getName() { return this.name; } } var p = new Student('hu', 18); console.log(p.getAge(), p.getName(), p.getOther()); // 22 "xiaom" "other info."
那么用ES7 的decorator来实现最开始的需求,则能够这样作
import { cloth, weapon, shoes, defaultRole } from './config'; // 基础角色 class Role { constructor(role) { this.hp = role.hp; this.atk = role.atk; this.speed = role.speed; this.cloth = role.cloth; this.weapon = role.weapon; this.shoes = role.shoes; } run() {} attack() {} } function ClothDecorator(target) { target.prototype.getCloth = function(cloth) { this.hp += cloth.hp; this.cloth = cloth.name; } } function WeaponDecorator(target) { target.prototype.getWeapon = function(weapon) { this.atk += weapon.attack; this.weapon = weapon.name; } target.prototype.attack = function() { if (this.weapon) { console.log(`装备了${this.weapon},攻击更强了`); } else { console.log('战士的基础攻击'); } } } function ShoesDecorator(target) { target.prototype.getShoes = function(shoes) { this.speed += shoes.speed; this.shoes = shoes.name; } target.prototype.run = function() { if (this.shoes) { console.log(`穿上了${this.shoes},移动速度更快了`); } else { console.log('战士的奔跑动做'); } } } @ClothDecorator @WeaponDecorator @ShoesDecorator class Soldier extends Role { constructor(role) { const o = Object.assign({}, defaultRole, role); super(o); this.nickname = role.nickname; this.gender = role.gender; this.career = '战士'; if (role.hp == defaultRole.hp) { this.hp = defaultRole.hp + 20; } if (role.speed == defaultRole.speed) { this.speed = defaultRole.speed + 5; } } run() { console.log('战士的奔跑动做'); } attack() { console.log('战士的基础攻击'); } } const base = { ...defaultRole, nickname: 'alex', gender: 'man' } const s = new Soldier(base); s.getCloth(cloth); console.log(s); s.getWeapon(weapon); s.attack(); console.log(s); s.getShoes(shoes); s.run(); console.log(s);
这里须要注意的是,装饰者模式与直接使用浏览器支持的语法在实现上的一些区别。
ES7 Decorator重点在于对装饰器的封装,所以咱们能够将上栗中的装饰器单独封装为一个模块。在细节上作了一些调整,让咱们封装的装饰器模块不单单能够在建立战士对象的时候使用,在咱们建立其余职业例如法师,射手的时候也可以正常使用。
export function ClothDecorator(target) { target.prototype.getCloth = function(cloth) { this.hp += cloth.hp; this.cloth = cloth.name; } } export function WeaponDecorator(target) { target.prototype.getWeapon = function(weapon) { this.atk += weapon.attack; this.weapon = weapon.name; } target.prototype.attack = function() { if (this.weapon) { console.log(`${this.nickname}装备了${this.weapon},攻击更强了。职业:${this.career}`); } else { console.log(`${this.career}的基本攻击`); } } } export function ShoesDecorator(target) { target.prototype.getShoes = function(shoes) { this.speed += shoes.speed; this.shoes = shoes.name; } target.prototype.run = function() { if (this.shoes) { console.log(`${this.nickname}穿上了${this.shoes},移动速度更快了。职业:${this.career}`); } else { console.log(`${this.career}的奔跑动做`); } } }
能够利用该例子,感觉Decorator与继承的不一样。
整理以后,Soldier的封装代码将会变得很是简单
import { cloth, weapon, shoes, defaultRole } from './config'; import { ClothDecorator, WeaponDecorator, ShoesDecorator } from './equip'; import Role from './Role'; @ClothDecorator @WeaponDecorator @ShoesDecorator class Soldier extends Role { constructor(roleInfo) { const o = Object.assign({}, defaultRoleInfo, roleInfo); super(o); this.nickname = roleInfo.nickname; this.gender = roleInfo.gender; this.career = '战士'; if (roleInfo.hp == defaultRoleInfo.hp) { this.hp = defaultRoleInfo.hp + 20; } if (roleInfo.speed == defaultRoleInfo.speed) { this.speed = defaultRoleInfo.speed + 5; } } run() { console.log('战士的奔跑动做'); } attack() { console.log('战士的基础攻击'); } }
那么继续上一篇文章的思考题,利用装饰器能够怎么作呢?
补充:如何在构建环境中支持ES7 Decorator语法
https://technologyadvice.gith...