首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >浅谈JS中的装饰器模式

浅谈JS中的装饰器模式

作者头像
IMWeb前端团队
发布2019-12-03 16:36:05
发布2019-12-03 16:36:05
1.5K0
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb Jianglinyuan 原文出处:IMWeb社区 未经同意,禁止转载

浅谈JS中的装饰器模式

什么是装饰器?

装饰器设计模式

装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。 我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。

JS中的装饰器

装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写法与Java的注解(Annotation)非常相似,但是功能还是有很大区别。

JS中的Decorator在原理和功能上简单明了,简而言之就是对对象进行包装,返回一个新的对象描述(descriptor)。这个概念其实和React中的高阶组件也类似,大家可以用高阶组件的方式来理解它。

举个非常简单的例子:

假设我们现在要对一个函数log,打印出它的执行记录。

不使用Decorator:

代码语言:javascript
复制
const log = (srcFun) => {
  if(typeof(srcFun) !== 'function') {
    throw new Error(`the param must be a function`);
  }
  return (...arguments) => {
    console.info(`${srcFun.name} invoke with ${arguments.join(',')}`);
    srcFun(...arguments);
  }
}

const plus = (a, b) => a + b;

const logPlus = log(plus);

logPlus(1,2); // this will log : plus invoke with 1,2

使用Decorator

代码语言:javascript
复制
const log = (target, name, descriptor) => {
  var oldValue = descriptor.value;
  descriptor.value = function() {
    console.log(`Calling ${name} with`, arguments);
    return oldValue.apply(this, arguments);
  };
  return descriptor;
}

class Math {
  @log  // Decorator
  plus(a, b) {
    return a + b;
  }
}
const math = new Math();

math.add(1, 2); // this will log: Calling plus with 1,2

从上面的代码可以看出,如果有的时候我们并不需要关心函数的内部实现,仅仅是想调用它的话,装饰器能够带来比较好的可读性,使用起来也是非常的方便。

JS中的原理

JS中的装饰器本质也是一个函数,利用的是JS中object的descriptor,这个函数会接收三个参数:

代码语言:javascript
复制
/**
 * 装饰器函数
 * @param {Object} target 被装饰器的类的原型
 * @param {string} name 被装饰的类、属性、方法的名字
 * @param {Object} descriptor 被装饰的类、属性、方法的descriptor
 */
function Decorator(target, name, descriptor) {
  // 以此可以获取实例化的时候此属性的默认值
  let v = descriptor.initializer && descriptor.initializer.call(this);

  // 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
  return {
    enumerable: true,
    configurable: true,
    get() {
      return v;
    },
    set(c) {
      v = c;
    },
  };
}

 // USE
 class Fudao{
    @Decorator
    title = "企鹅辅导“
 }

当然装饰器也可以接受参数:

代码语言:javascript
复制
// decorator 外部可以包装一个函数,函数可以带参数
function Decorator(type) {
  /**
* 装饰器函数
* @param {Object} target 被装饰器的类的原型
* @param {string} name 被装饰的类、属性、方法的名字
* @param {Object} descriptor 被装饰的类、属性、方法的descriptor
*/
  return (target, name, descriptor) => {
    // 以此可以获取实例化的时候此属性的默认值
    let v = descriptor.initializer && descriptor.initializer.call(this);

    // 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
    return {
      enumerable: true,
      configurable: true,
      get() {
        return v + type;
      },
      set(c) {
        v = c;
      },
    };
  }
}

// USE
 class Fudao{
    @Decorator('string') 
    title = "企鹅辅导“
 }

常见的装饰器

autobind

autobind修饰器使得方法中的this对象,绑定原始对象,使得this始终指向绑定的对象。

代码语言:javascript
复制
import { autobind } from 'core-decorators'; // a NPM lib

class Person {
  @autobind
  getPerson() {
    return this;
  }
}

let person = new Person();
let getPerson = person.getPerson;

getPerson() === person;
// true
readonly

readonly修饰器使得属性或方法不可写。

代码语言:javascript
复制
import { readonly } from 'core-decorators';

class Fudao {
  @readonly
  title = '企鹅辅导';
}

var fudao = new Fudao();
fudao.title = '腾讯课堂'; // This will log error & doesn't work
deprecate

deprecate可以用来装饰那些已经废弃的函数方法或者属性,这样用户在调用这个函数的时候就会收到相关的告警。

代码语言:javascript
复制
import { deprecate } from 'core-decorators';

class Person {
  @deprecate
  facepalm() {}

  @deprecate('We stopped facepalming')
  facepalmHard() {}

  @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' })
  facepalmHarder() {}
}

let person = new Person();

person.facepalm();
// DEPRECATION Person#facepalm: This function will be removed in future versions.

person.facepalmHard();
// DEPRECATION Person#facepalmHard: We stopped facepalming

person.facepalmHarder();
// DEPRECATION Person#facepalmHarder: We stopped facepalming
//
//     See http://knowyourmeme.com/memes/facepalm for more details.
//

React中的装饰器

在React中我们可以使用装饰器来干我们想干的任何事情,这得益于React天生需要打包环境(虽然也可以不打包☺)。

transferProps

如果我们想把propTypes中没有声明的props提取出来,放在ohters这个key下面,实现类似下面的功能:

代码语言:javascript
复制
@transferProps
class Foo extends React.Component {

  static propTypes = {
    foo: PropTypes.string,
  }

  // 下面props中只会有foo和others两种
  render() {
    return (
      <div {...this.props.others}>
        {this.props.foo}
      </div>
    );
  }
}

怎么实现的呢,很简单:

代码语言:javascript
复制
import React from 'react';

export default function transferProps(Target) {
  return (props) => {
    const others = {};
    const newProps = Object.assign({}, props);
    Object.keys(newProps).forEach((key) => {
      if (!(Target.propTypes && Target.propTypes[key])) {
        others[key] = newProps[key];
        delete newProps[key];
      }
    });
    return <Target others={others} {...newProps} />;
  };
}
renameProps

改变props的name:

代码语言:javascript
复制
@renameProps({
  foo: 'bar',
})
class Foo extends React.Component {

  static propTypes = {
    bar: PropTypes.string,
  }

  render() {
    return (
      <div>
        {this.props.bar}
      </div>
    );
  }
}

// Rendering the following
// <Foo foo="example" />
//
// produces these props:
// props = {
//   bar: 'example',
// }

代码实现,也非常简单:

代码语言:javascript
复制
import React from 'react';

export default function renameProps(newNames) {
  return (Target) => {
    return (props) => {
      const newProps = Object.assign({}, props);
      const names = Object.keys(newNames);
      Object.keys(newProps).forEach((key) => {
        const nameIndex = names.indexOf(key);
        if (names && key && nameIndex !== -1) {
          newProps[newNames[names[nameIndex]]] = newProps[key];
          delete newProps[key];
        }
      });
      return <Target {...newProps} />;
    };
  };
}
@connect

在Redux中,通常我们需要一个reducer和一个action,然后使用connect来包裹你的Component。

代码语言:javascript
复制
import React from 'react'
import {render} from 'react-dom'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import action from 'action.js'

class App extends React.Component{
  render(){
    return <div>hello</div>
  }
}
function mapStateToProps(state){
  return state.main
}
function mapDispatchToProps(dispatch){
  return bindActionCreators(action,dispatch)
}
export default connect(mapStateToProps,mapDispatchToProps)(App)

使用connect装饰器,可以让代码变得非常明了:

代码语言:javascript
复制
import React from 'react'
import {render} from 'react-dom'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import action from 'action.js'

[@connect](/user/connect)(
 state=>state.main,
 dispatch=>bindActionCreators(action,dispatch)
)
class App extends React.Component{
  render(){
    return <div>hello</div>
  }
}

总结

Decorator 虽然原理非常简单,但是的确可以实现很多实用又方便的功能,像 mobx中@observable、Angular中的大量应用以及证明了其的高可用性。个人觉得在一些开发框架中尝试加入装饰器可以提供更简洁以及高效的代码质量,下篇我们将为你介绍装饰器的实际应用场景,带你体验装饰器的魅力。

博客文章地址

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-06-03 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 浅谈JS中的装饰器模式
    • 什么是装饰器?
      • 装饰器设计模式
      • JS中的装饰器
      • JS中的原理
    • 常见的装饰器
      • autobind
      • readonly
      • deprecate
    • React中的装饰器
      • transferProps
      • renameProps
      • @connect
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档