MobX詳解(二):ES7 裝飾器 decorator

在學習ES7裝飾器語法以前,須要先溫習一下ES5的一些基礎知識。javascript

假設有對象以下:(便於理解)java

var person = {
    name: 'TOM'
}

在ES5中,對象中的每一個屬性都有一個特性值來描述這個屬性的特色,他們分別是:git

  • 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(操做多個)來修改這些特性值。github

// 三個參數分別爲  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緩存

使用時也很簡單,以下: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就是被裝飾的具體方法getNameapp

不能使用裝飾器對構造函數進行更改,若是要修改構造函數,則能夠經過以下的方式來完成函數

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

相關文章
相關標籤/搜索