资讯专栏INFORMATION COLUMN

MobX详解(二):ES7 装饰器 decorator

Keagan / 459人阅读

摘要:在学习装饰器语法之前,需要先温习一下的一些基础知识。函数最后必须返回。使用时也很简单,如下在方法前面加上,就是装饰器语法。装备了,攻击更强了。职业的基本攻击穿上了,移动速度更快了。

在学习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
})

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

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...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107064.html

相关文章

  • 【用故事解读 MobX 源码(四)】装饰 和 Enhancer

    摘要:所以这是一篇插队的文章,用于去理解中的装饰器和概念。因此,该的作用就是根据入参返回具体的描述符。其次局部来看,装饰器具体应用表达式是,其函数签名和是一模一样。等装饰器语法,是和直接使用是等效等价的。 ================前言=================== 初衷:以系列故事的方式展现 MobX 源码逻辑,尽可能以易懂的方式讲解源码; 本系列文章: 《【用故事解...

    maybe_009 评论0 收藏0
  • 写 React 组件的最佳实践

    摘要:本文介绍了,我们团队写组件的最佳实践。这样可以避免类似之类的错误避免使用函数表达式的方式来定义组件,如下这看起来非常酷,但是在这里,通过函数表达式定义的函数却是匿名函数。匿名函数也可能会导致测试库出问题。 本文为译文,已获得原作者允许,原文地址:http://scottdomes.com/blog/ou... 当我第一次开始写 React 时,我发现多少个 React 教程,就有多少种...

    lakeside 评论0 收藏0
  • react+mobx+thrift学习demo

    摘要:安装等相关依赖。通过启动项目,进行后续操作。自定义执行状态的改变。任何不在使用状态的计算值将不会更新,直到需要它进行副作用操作时。强烈建议始终抛出错误,以便保留原始堆栈跟踪。 2018-08-14 learning about work begin:2018-08-13 step 1 熟悉react 写法 step 2 mobx 了解&使用 step 3 thrift接口调用过程 Re...

    xcc3641 评论0 收藏0
  • Mobx4.X状态管理入门

    摘要:前言原本说接下来会专注学但是最新工作又学习了一些有意思的库於是就再写下来做个简单的入门之前我写过一篇文章这个也算是作為一个补充吧这次无非就是类似笔记把认为的一些关键点记下来有些地方还没用到就衹是描述一下代码有些自己写的有些文档写的很好就搬下 前言 原本说接下来会专注学nodejs,但是最新工作又学习了一些有意思的库,於是就再写下来做个简单的入门,之前我写过一篇文章,这个也算是作為一个补...

    CKJOKER 评论0 收藏0
  • 【用故事解读 MobX 源码(五)】 Observable

    摘要:前言初衷以系列故事的方式展现源码逻辑,尽可能以易懂的方式讲解源码本系列文章用故事解读源码一用故事解读源码二用故事解读源码三用故事解读源码四装饰器和用故事解读源码五文章编排每篇文章分成两大段,第一大段以简单的侦探系列故事的形式讲解所涉及人物场 ================前言=================== 初衷:以系列故事的方式展现 MobX 源码逻辑,尽可能以易懂的方式...

    leeon 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<