装饰器简介

Decorator 是 ES7 的一个新语法,正如其“装饰器”的叫法所表达的,他可以对一些对象进行装饰包装然后返回一个被包装过的对象,可以装饰的对象包括:类,属性,方法等。Decorator 的写法与 Java 里的注解(Annotation)非常类似,但是一定不要把 JS 中的装饰器叫做是“注解”,因为这两者的原理和实现的功能还是有所区别的,在 Java 中,注解主要是对某个对象进行标注,然后在运行时或者编译时,可以通过例如反射这样的机制拿到被标注的对象,对其进行一些逻辑包装。而 Decorator 的原理和作用则更为简单,就是包装对象,然后返回一个新的对象描述(descriptor),其作用也非常单一简单,基本上就是获取包装对象的宿主、键值几个有限的信息。

关于 Decorator 的详细介绍参见文章:zhuanlan.zhihu.com/FrontendMag…

简单来说,JS 的装饰器可以用来“装饰”三种类型的对象:类的属性/方法、访问器、类本身,简单看几个例子吧。

针对属性/方法的装饰器

// decorator 外部可以包装一个函数,函数可以带参数
function Decorator(type){
    /**
     * 这里是真正的 decorator
     * @target 装饰的属性所述的类的原型,注意,不是实例后的类。如果装饰的是 Car 的某个属性,这个 target 的值就是 Car.prototype
     * @name 装饰的属性的 key
     * @descriptor 装饰的对象的描述对象
     */
    return function (target, name, descriptor){
        // 以此可以获取实例化的时候此属性的默认值
        let v = descriptor.initializer && descriptor.initializer.call(this);
        // 返回一个新的描述对象,或者直接修改 descriptor 也可以
        return {
            enumerable: true,
            configurable: true,
            get: function() {
                return v;
            },
            set: function(c) {
                v = c;
            }
        }
    }
}复制代码

注意这里的 target 对应的是被装饰的属性所属类的原型,如果是装饰一个 A 类的属性,并且 A 类是继承自 B 类的,这时候你打印 target,获取到的是 A.prototype,它的结构是这样的,这里一定要注意:

[image:A944761A-E0FA-4C04-BD90-BE179C46B641-35651-00001223828250C5/187FCC2A-8CC4-46C4-B8A3-A7FD5E0376F6.png]
如果需要操作 target,可能需要搞清楚这个问题。

针对 访问操作符的装饰

与属性方法类似,就不详述了。

class Person {
  @nonenumerable
  get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
  descriptor.enumerable = false;
  return descriptor;
}复制代码

针对类的装饰

// 例如 mobx 中 @observer 的用法
/**
 * 包装 react 组件
 * @param target
 */
function observer(target) {
    target.prototype.componentWillMount = function() {
        targetCWM && targetCWM.call(this);
        ReactMixin.componentWillMount.call(this);
    };
}复制代码

其中的 target 就是类本身(而不是其 prototype)


真实场景应用

今天,我们要介绍的主要是,如何将 Decorator 这个特性应用于数据定义层,实现一些类似于类型检查、字段映射等功能。

关于数据定义层(Model),其实就是应用内出现的各种实体数据的定义,也就是 MVVM 中的 M 层,注意,和 VM 层做好区分,Model 本身不提供数据的管理和流通,只负责定义某个实体本身的属性和方法,例如页面里有一辆车的模块,我们就定义一个 CarModel,它用来描述车辆的颜色、价格、品牌等信息。

关于为什么要在前端应用内定义明确的 Model,这个我之前在知乎上也早有论述,核心几点:

  • 提高可维护性。将数据源头的实体做一个固定而准确的描述,这个对于串联理解整个应用非常重要,特别是在重构或者接手别人的代码的时候,你需要准确的知道一个页面(或者是一个模块)它会包含哪些数据,这些数据分别有哪些字段,这样更便于理解整个应用的数据逻辑。
  • 提高确定性。当你要给你的界面增加几个车辆字段的时候,你不清楚之前是否已经定义过这些字段,服务端是否会返回这些字段,可能要请求一下(并且要有权限取到所有字段)才能知道,但是如果有 model 的明确定义,有什么字段就一目了然了。
  • 提高开发效率。在这一层统一做一些数据映射和类型检查等工作,这也是今天要讲的重点。

以我们团队 RN 开发框架中 Model 部分的实现为例,我们至少提供了三个基础的基于 Decorator 的功能:类型检查,单位转换,字段映射。接下来我会先简单介绍下这几个功能是做什么的,随后介绍如何实现这些 Decorator。

先来看看最终调用时候的代码

class CarModel extends BaseModel {
    /**
     * 价格
     * @type {number}
     */
    @observable
    @Check(CheckType.Number)
    @Unit(UnitType.PRICE_UNIT_WY)
    price = 0;

    /**
     * 卖家名
     * @type {string}
     */
    @observable
    @Check(CheckType.String)
    @ServerName('seller_name')
    sellerName = '';
}复制代码

可以看到我们有三个自定义的 decorator :

@Unit,         // 单位转换装饰器
@Check,        // 类型检查装饰器,
@ServerName    // 数据字段映射装饰器,当前后端定义的字段名不一致的时候用复制代码

@Unit 是一个比较特殊的装饰器,它的作用是在前后端之间自动转换单位,也就是前端和后端交换某些带单位的数据的时候,会把根据各端的注解和装饰器,把真实值转换成带单位的值传给另一端,然后另一端会在框架层自动转成它定义的单位,以此解决前后端单位不一致,交换数据时混乱导致的问题。

被 @Unit 装饰过的属性,读写的时候都是按照前端的单位读写,然后再转换成 JSON 的时候就会特殊处理成类似 12.3_$wy 这样的格式,表示这个数的单位是万元。
@Check 更为容易理解,就是用来检查字段类型,或者检查字段格式,或者一些自定义检查,例如正则表达式等。
@ServerName 则用来做映射,例如前后端对同一个界面元素的命名不同,这时候不需要完全按照服务端的命名来决定,可以在前端用另外一个属性名,然后将其装饰成服务端的字段名。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容