ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

ES6 装饰器

2021-09-12 22:31:40  阅读:212  来源: 互联网

标签:function ES6 return target class descriptor 修饰 装饰


1、类的修饰 decorator(target) {}

许多面向对象的语言都有修饰器(Decorator)函数,用来修改类的行为。

@testable
class MyClass {};

function testable(target) {
    target.isTestable = true;
}

MyClass.isTestable;  // true

上面代码中,@testable就是一个修饰器。它修改了MyClass这个类的行为,为它加上了静态属性isTestable。

2、装饰器基本语法

@decorator
class A {};

// 等同于
class A {};
A = decorator(A) || A;

也就是说,修饰器是一个对类进行处理的函数。修饰器函数的第一个参数,就是所要修饰的目标类。

function decorator(target) {
    target.age = 20;   // 为类添加静态属性
    target.prototype.grade = 3;  // 为类添加实例属性
}

注意,修饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,修饰器能在编译阶段运行代码。也就是说,修饰器本质就是编译时执行的函数。

如果觉得一个参数不够用,可以在修饰器外面再封装一层函数。见下面例子:

// 定义装饰器的外层函数
function mixins(...list) {
    // 返回一个装饰器函数
    return function(target) {
        Object.assign(target.prototype, ...list);
    }
}

const args = {
    f1() {}
    f2() {}
    f3() {}
};

// 使用mixins装饰器
@mixins(args)
class MyClass {};

let inst = new MyClass();
inst.f3();

实例:实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。

class MyComponent extends React.Component {};
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

有了装饰器,就可以改写上面的代码。

@connect(mapStateToProps, mapDispatchToProps)
export default class MyComponent extends React.Component {};

3、修饰类的方法 decorator(target, key, descriptor) {}

修饰器不仅可以修饰类,还可以修饰类的属性。

class Person {
    @readonly
    // readonly(Person.prototype, 'name', descriptor);
    // 类似于
    // Object.defineProperty(Person.prototype, 'name', descriptor);
    name() { return `${this.name}` }
}

定义 readonly装饰器:第一个参数是类的原型对象;第二个参数是所要修饰的属性名;第三个参数是该属性的描述对象。

function readonly(target, key, descriptor) {
    // descriptor对象原来的值如下
    // {
    //     value: specifiedFunction,
    //     enumerable: false,
    //     configurable: true,
    //     writable: true
    // }
    decorator.writable = false;  // 不可写
    return descriptor;
}

再例:

class A {
    @nonenumerable
    get kidCount() {
        return this.children.length;
    }
}
function nonenumerable(target, key, descriptor) {
    descriptor.enumerable = false;
    return descriptor;
}

再例:

class Math {
    @log
    add(a, b) {
        return a + b;
    }
}
function log(target, key, descriptor) {
    var oldValue = descriptor.value;
    // 修改 descriptor属性
    descriptor.value = function() {
        console.log(`${name}`, arguments);
        return oldValue.apply(this, arguments);
    }
    return descriptor;
}

修饰器还起到了注释的作用。

用装饰器写组件:

@Component({
    tag: 'my-component',
    styleUrl: 'my-component.scss'
})
export class MyComponent {
    @Prop() first: string;
    @Prop() last: string;
    @State() isVisible: boolean = true;

    render() {
        return(
            <p>my name is {this.first} {this.last}</p>
        )
    }
}

如果同一个方法有多个修饰器,会像剥洋葱一样,先从外到内进入,然后由内向外执行。
定义一个装饰器的外层包装函数:

function dec(id) {
    // 返回一个用于修饰类成员的装饰器
    return (target, key, descriptor) => {
        descriptor.writable = false;
    }
}
class Demo {
    @dec(1)
    @dec(2)
    @dec(3)
    method() {}
}
// 装饰器的进入顺序:dec(1) -> dec(2) -> dec(3)
// 装饰器的执行顺序:dec(3) -> dec(2) -> dec(1)

除了注释,修饰器还能用来类型检查。所以,对于类来说,这项功能相当有用。从长期来看,它将是 JavaScript 代码静态分析的重要工具。

4、为什么修饰器不能用于函数?

修饰器只能用于类和类的方法,不能用于修饰函数,因为存在函数提升。
由于存在函数提升,使得修饰器不能用于函数。类是不会提升的,所以就没有这方面的问题。

如果一定要修饰函数,可以采用高阶函数的形式直接执行。

function decoratorFn(fn) {
    return function() {
        const res = fn.apply(this, arguments);
        return res;
    }
}

function doSomething() {};
const doSomething2 = decoratorFn(doSomething);

5、core-decorators.js

core-decorators.js是一个第三方模块,提供了几个常见的修饰器,通过它可以更好地理解修饰器。
(1)@autobind 修饰器使得方法中的this对象,绑定原始对象。
(2)@readonly 修饰器使得属性或方法不可写。
(3)@override 修饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。
(4)@deprecate 或 @deprecated 修饰器在控制台显示一条警告,表示该方法将废除。
(5)@suppressWarnings 修饰器抑制deprecated修饰器导致的console.warn()调用。但是,异步代码发出的调用除外。

import { autobind, readonly } from 'core-decorators';

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

    @readonly
    age = 23;
}

6、使用修饰器实现自动发布事件

我们可以使用修饰器,使得对象的方法被调用时,自动发出一个事件。

使用的事件“发布/订阅”库 Postal.js

const postal = require('postal/lib/postal.lodash');

封装装饰器函数:

export default function publish(topic, channel) {
    const channelName = channel || '/';
    const msgChannel = postal.channel(channelName);
    msgChannel.subscribe(topic, v => {
        console.log('频道', channelName);
        console.log('事件', topic);
        console.log('数据', v);
    });
    // 返回装饰器函数
    return function(target, key, descriptor) {
        const fn = descriptor.value;
        descriptor.value = function() {
            let value = fn.apply(this, arguments);
            msgChannel.publish(topic, value);
        }
    };
}

使用装饰器:

import publish from './publish.js';
class FooComponent {
    @publish('foo.some.message', 'component')
    someMethod() {
        return { a: 1 };
    }
    @publish('foo.some.other')
    anotherMethod() {
        return { b: 2 };
    }
}
let foo = new FooComponent();
foo.someMethod();
foo.anotherMethod();

7、Mixin

在修饰器的基础上,可以实现Mixin模式。所谓Mixin模式,就是对象继承的一种替代方案,中文译为“混入”(mix in),意为在一个对象之中混入另外一个对象的方法。

function mix(...list) {
    return function (target) {
        Object.assign(target.prototype, ...list);
    };
}

8、Trait

Trait 也是一种修饰器,效果与 Mixin 类似,但是提供更多功能,比如防止同名方法的冲突、排除混入某些方法、为混入的方法起别名等等。

import { traits, alias } from 'traits-decorator';

class TFoo {
    foo() { console.log('foo'); }
}
const obj = {
    bar() { console.log('bar');},
    foo() { console.log('foo');}
}
@traits(TFoo, obj::alias({foo: 'aliasFoo'}))
class MyClass {};

let inst = new MyClass();
inst.foo();
inst.bar();
inst.aliasFoo();

9、 Babel 转码器 支持装饰器
目前,Babel 转码器已经支持 Decorator。

首先,安装babel-core和babel-plugin-transform-decorators。由于后者包括在babel-preset-stage-0之中,所以改为安装babel-preset-stage-0亦可。

然后,设置配置文件.babelrc。

{
    "plugins": ["transform-decorators"]
}

Babel 的官方网站提供一个在线转码器,只要勾选 Experimental,就能支持 Decorator 的在线转码。

参考链接阮一峰ES6教程

 


完!!!

 

    7人点赞   JavaScript    

作者:夏海峰
链接:https://www.jianshu.com/p/8745660de050
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:function,ES6,return,target,class,descriptor,修饰,装饰
来源: https://www.cnblogs.com/sexintercourse/p/15260390.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有