在学习ES7装饰器语法之前,需要先温习一下ES5的一些基础知识。
假设有对象如下:(便于理解)
var person = {
name: 'TOM'
}
在ES5中,对象中的每个属性都有一个特性值来描述这个属性的特点,他们分别是:
-
configurable
: 属性是否能被delete删除,当值为false时,其他特性值也不能被改变,默认值为true -
enumerable
: 属性是否能被枚举,也就是是否能被for in循环遍历。默认为true -
writable
: 是否能修改属性值。默认为true -
value
:具体的属性值是多少,默认为undefined -
get
:当我们通过person.name
访问name的属性值时,get将被调用。该方法可以自定义返回的具体值是多少。get默认值为undefined -
set
:当我们通过person.name = 'Jake'
设置name属性值时,set方法将被调用,该方法可以自定义设置值的具体方式,set默认值为undefined
需要注意的是,不能同时设置value,writeable
与get set
。
我们可以通过Object.defineProperty
(操作单个)与Object.defineProperties
(操作多个)来修改这些特性值。
// 三个参数分别为 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
。
使用时也很简单,如下:
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
。
不能使用装饰器对构造函数进行更改,如果要修改构造函数,则可以通过如下的方式来完成
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语法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。