9

在学习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,writeableget set

我们可以通过Object.defineProperty(操作单个)与Object.defineProperties(操作多个)来修改这些特性值。

// 三个参数分别为  target, key, descriptor(特性值的描述对象)
Object.defineProperty(person, 'name', {
  value: "TOM"
})

// 新增
Object.defineProperty(person, 'age', {
  value: 20
})

clipboard.png

装饰器语法与此类似,当我们想要自定义一个装饰器时,可以这样写:

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语法

https://technologyadvice.gith...

clipboard.png


这波能反杀
12.6k 声望2.7k 粉丝