您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

浅谈ES6中的装饰器

2025/10/17 23:30:56发布29次查看
本篇文章给大家带来的内容是关于浅谈es6中的装饰器,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。
decorator
装饰器主要用于:
装饰类
装饰方法或属性
装饰类@annotationclass myclass { }function annotation(target) {   target.annotated = true;}

装饰方法或属性class myclass {  @readonly  method() { }}function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}
babel安装编译我们可以在 babel 官网的 try it out,查看 babel 编译后的代码。
不过我们也可以选择本地编译:
npm initnpm install --save-dev @babel/core @babel/clinpm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
新建 .babelrc 文件
{  plugins: [    [@babel/plugin-proposal-decorators, { legacy: true }],    [@babel/plugin-proposal-class-properties, {loose: true}]  ]}
再编译指定的文件
babel decorator.js --out-file decorator-compiled.js
装饰类的编译编译前:
@annotationclass myclass { }function annotation(target) {   target.annotated = true;}

编译后:
var _class;let myclass = annotation(_class = class myclass {}) || _class;function annotation(target) {  target.annotated = true;}
我们可以看到对于类的装饰,其原理就是:
@decoratorclass a {}// 等同于class a {}a = decorator(a) || a;
装饰方法的编译编译前:
class myclass {  @unenumerable  @readonly  method() { }}function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}function unenumerable(target, name, descriptor) {  descriptor.enumerable = false;  return descriptor;}
编译后:
var _class;function _applydecorateddescriptor(target, property, decorators, descriptor, context ) {    /**     * 第一部分     * 拷贝属性     */    var desc = {};    object[ke + ys](descriptor).foreach(function(key) {        desc[key] = descriptor[key];    });    desc.enumerable = !!desc.enumerable;    desc.configurable = !!desc.configurable;    if (value in desc || desc.initializer) {        desc.writable = true;    }    /**     * 第二部分     * 应用多个 decorators     */    desc = decorators        .slice()        .reverse()        .reduce(function(desc, decorator) {            return decorator(target, property, desc) || desc;        }, desc);    /**     * 第三部分     * 设置要 decorators 的属性     */    if (context && desc.initializer !== void 0) {        desc.value = desc.initializer ? desc.initializer.call(context) : void 0;        desc.initializer = undefined;    }    if (desc.initializer === void 0) {        object[define + property](target, property, desc);        desc = null;    }    return desc;}let myclass = ((_class = class myclass {    method() {}}),_applydecorateddescriptor(    _class.prototype,    method,    [readonly],    object.getownpropertydescriptor(_class.prototype, method),    _class.prototype),_class);function readonly(target, name, descriptor) {    descriptor.writable = false;    return descriptor;}
装饰方法的编译源码解析我们可以看到 babel 构建了一个 _applydecorateddescriptor 函数,用于给方法装饰。
object.getownpropertydescriptor()在传入参数的时候,我们使用了一个 object.getownpropertydescriptor() 方法,我们来看下这个方法:
object.getownpropertydescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)顺便注意这是一个 es5 的方法。
举个例子:
const foo = { value: 1 };const bar = object.getownpropertydescriptor(foo, value);// bar {//   value: 1,//   writable: true//   enumerable: true,//   configurable: true,// }const foo = { get value() { return 1; } };const bar = object.getownpropertydescriptor(foo, value);// bar {//   get: /*the getter function*/,//   set: undefined//   enumerable: true,//   configurable: true,// }
第一部分源码解析在 _applydecorateddescriptor 函数内部,我们首先将 object.getownpropertydescriptor() 返回的属性描述符对象做了一份拷贝:
// 拷贝一份 descriptorvar desc = {};object[ke + ys](descriptor).foreach(function(key) {    desc[key] = descriptor[key];});desc.enumerable = !!desc.enumerable;desc.configurable = !!desc.configurable;// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setterif (value in desc || desc.initializer) {    desc.writable = true;}
那么 initializer 属性是什么呢?object.getownpropertydescriptor() 返回的对象并不具有这个属性呀,确实,这是 babel 的 class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:
class myclass {  @readonly  born = date.now();}function readonly(target, name, descriptor) {  descriptor.writable = false;  return descriptor;}var foo = new myclass();console.log(foo.born);
babel 就会编译为:
// ...(_descriptor = _applydecorateddescriptor(_class.prototype, born, [readonly], {    configurable: true,    enumerable: true,    writable: true,    initializer: function() {        return date.now();    }}))// ...
此时传入 _applydecorateddescriptor 函数的 descriptor 就具有 initializer 属性。
第二部分源码解析接下是应用多个 decorators:
/** * 第二部分 * @type {[type]} */desc = decorators    .slice()    .reverse()    .reduce(function(desc, decorator) {        return decorator(target, property, desc) || desc;    }, desc);
对于一个方法应用了多个 decorator,比如:
class myclass {  @unenumerable  @readonly  method() { }}
babel 会编译为:
_applydecorateddescriptor(    _class.prototype,    method,    [unenumerable, readonly],    object.getownpropertydescriptor(_class.prototype, method),    _class.prototype)
在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。
第三部分源码解析/** * 第三部分 * 设置要 decorators 的属性 */if (context && desc.initializer !== void 0) {    desc.value = desc.initializer ? desc.initializer.call(context) : void 0;    desc.initializer = undefined;}if (desc.initializer === void 0) {    object[define + property](target, property, desc);    desc = null;}return desc;
如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:
desc.initializer.call(context)
而 context 的值为 _class.prototype,之所以要 call(context),这也很好理解,因为有可能
class myclass {  @readonly  value = this.getnum() + 1;  getnum() {    return 1;  }}
最后无论是装饰方法还是属性,都会执行:
object[define + property](target, property, desc);
由此可见,装饰方法本质上还是使用 object.defineproperty() 来实现的。
应用1.log为一个方法添加 log 函数,检查输入的参数:
class math {  @log  add(a, b) {    return a + b;  }}function log(target, name, descriptor) {  var oldvalue = descriptor.value;  descriptor.value = function(...args) {    console.log(`calling ${name} with`, args);    return oldvalue.apply(this, args);  };  return descriptor;}const math = new math();// calling add with [2, 4]math.add(2, 4);
再完善点:
let log = (type) => {  return (target, name, descriptor) => {    const method = descriptor.value;    descriptor.value =  (...args) => {      console.info(`(${type}) 正在执行: ${name}(${args}) = ?`);      let ret;      try {        ret = method.apply(target, args);        console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);      } catch (error) {        console.error(`(${type}) 失败: ${name}(${args}) => ${error}`);      }      return ret;    }  }};
2.autobindclass person {  @autobind  getperson() {      return this;  }}let person = new person();let { getperson } = person;getperson() === person;// true
我们很容易想到的一个场景是 react 绑定事件的时候:
class toggle extends react.component {  @autobind  handleclick() {      console.log(this)  }  render() {    return (      <button onclick={this.handleclick}>        button      </button>    );  }}
我们来写这样一个 autobind 函数:
const { defineproperty, getprototypeof} = object;function bind(fn, context) {  if (fn.bind) {    return fn.bind(context);  } else {    return function __autobind__() {      return fn.apply(context, arguments);    };  }}function createdefaultsetter(key) {  return function set(newvalue) {    object.defineproperty(this, key, {      configurable: true,      writable: true,      enumerable: true,      value: newvalue    });    return newvalue;  };}function autobind(target, key, { value: fn, configurable, enumerable }) {  if (typeof fn !== 'function') {    throw new syntaxerror(`@autobind can only be used on functions, not: ${fn}`);  }  const { constructor } = target;  return {    configurable,    enumerable,    get() {      /**       * 使用这种方式相当于替换了这个函数,所以当比如       * class.prototype.hasownproperty(key) 的时候,为了正确返回       * 所以这里做了 this 的判断       */      if (this === target) {        return fn;      }      const boundfn = bind(fn, this);      defineproperty(this, key, {        configurable: true,        writable: true,        enumerable: false,        value: boundfn      });      return boundfn;    },    set: createdefaultsetter(key)  };}
3.debounce有的时候,我们需要对执行的方法进行防抖处理:
class toggle extends react.component {  @debounce(500, true)  handleclick() {    console.log('toggle')  }  render() {    return (      <button onclick={this.handleclick}>        button      </button>    );  }}
我们来实现一下:
function _debounce(func, wait, immediate) {    var timeout;    return function () {        var context = this;        var args = arguments;        if (timeout) cleartimeout(timeout);        if (immediate) {            var callnow = !timeout;            timeout = settimeout(function(){                timeout = null;            }, wait)            if (callnow) func.apply(context, args)        }        else {            timeout = settimeout(function(){                func.apply(context, args)            }, wait);        }    }}function debounce(wait, immediate) {  return function handledescriptor(target, key, descriptor) {    const callback = descriptor.value;    if (typeof callback !== 'function') {      throw new syntaxerror('only functions can be debounced');    }    var fn = _debounce(callback, wait, immediate)    return {      ...descriptor,      value() {        fn()      }    };  }}
4.time用于统计方法执行的时间:
function time(prefix) {  let count = 0;  return function handledescriptor(target, key, descriptor) {    const fn = descriptor.value;    if (prefix == null) {      prefix = `${target.constructor.name}.${key}`;    }    if (typeof fn !== 'function') {      throw new syntaxerror(`@time can only be used on functions, not: ${fn}`);    }    return {      ...descriptor,      value() {        const label = `${prefix}-${count}`;        count++;        console.time(label);        try {          return fn.apply(this, arguments);        } finally {          console.timeend(label);        }      }    }  }}
5.mixin用于将对象的方法混入 class 中:
const singermixin = {  sing(sound) {    alert(sound);  }};const flymixin = {  // all types of property descriptors are supported  get speed() {},  fly() {},  land() {}};@mixin(singermixin, flymixin)class bird {  singmatingcall() {    this.sing('tweet tweet');  }}var bird = new bird();bird.singmatingcall();// alerts tweet tweet
mixin 的一个简单实现如下:
function mixin(...mixins) {  return target => {    if (!mixins.length) {      throw new syntaxerror(`@mixin() class ${target.name} requires at least one mixin as an argument`);    }    for (let i = 0, l = mixins.length; i < l; i++) {      const descs = object.getownpropertydescriptors(mixins[i]);      const keys = object.getownpropertynames(descs);      for (let j = 0, k = keys.length; j < k; j++) {        const key = keys[j];        if (!target.prototype.hasownproperty(key)) {          object.defineproperty(target.prototype, key, descs[key]);        }      }    }  };}
6.redux实际开发中,react 与 redux 库结合使用时,常常需要写成下面这样。
class myreactcomponent extends react.component {}export default connect(mapstatetoprops, mapdispatchtoprops)(myreactcomponent);
有了装饰器,就可以改写上面的代码。
@connect(mapstatetoprops, mapdispatchtoprops)export default class myreactcomponent extends react.component {};
相对来说,后一种写法看上去更容易理解。
7.注意以上我们都是用于修饰类方法,我们获取值的方式为:
const method = descriptor.value;
但是如果我们修饰的是类的实例属性,因为 babel 的缘故,通过 value 属性并不能获取值,我们可以写成:
const value = descriptor.initializer && descriptor.initializer();
以上就是浅谈es6中的装饰器的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product