在學習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...