常名斋

ECMA 提案-'decorator' 研究

ECMA 提案-'decorator' 研究
2018-11-04 · 3 min read
Javascript Program

//ps:根据大佬的解释,现在提案修改,不推荐使用legacy这种模式了,新版模式待研究后再更新Orz

提案状态:Stage-2

链接:https://tc39.github.io/proposal-decorators/

需求: Babel-7

插件:@babel/plugin-proposal-decorators

          (ps:babel7以后插件名称全部有所改动)

快速安装:

    yarn global add @babel/cli
    yarn global add @babel/node
    yarn add @babel/plugin-proposal-decorators

使用babel-node、babel-cli这样的名称只能安装到6版本的babel工具,7版本的工具全部改由@babel前缀

新建.babelrc文件:

    {
        "plugins":[
    				["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }],
    				["@babel/plugin-proposal-decorators",{"legacy":true}]]
    }

新建index.js文件:

    function testable(isTestable) {
        return function(target) {
            target.isTestable = isTestable;
        }
    }
    
    @testable(true)
    class MyTestableClass {}
    console.log(MyTestableClass.isTestable)// true
     
    
    @testable(false)
    class MyClass {}
    console.log(MyClass.isTestable)// false

然后执行:

babel-node index.js

即可看到对应的输出结果。

思考:

  • 装饰器语法本身只是单纯的用一个函数去处理一个类,大多数时候都是对其prototype的修改,用es5也能实现对应的功能,但是使用装饰器能使代码更加简洁,结构更加清晰。

  • 虽然在js中,class和function不存在本质上的区别,只是一个语法糖的存在,但是由于函数的作用域提升的问题,如果函数中有自由变量的使用,就会出现不可预期的一些bug。

    例如如下代码:(案例来自阮一峰es6教程)

    var counter = 0;
    
    var add = function () {
      counter++;
    };
    
    @add
    function foo() {
    }

在实际编译后,会变成如下:

    @add
    function foo() {
    }
    
    var counter;
    var add;
    
    counter = 0;
    
    add = function () {
      counter++;
    };

从而导致程序出粗。

  • 由于装饰器可以优雅的修改prototype,猜想可以优雅的尝试AOP编程,例如:
    function AOP(options) {
        const {beforeFunc,afterFunc}=Object.assign({
            beforeFunc:()=>{},
            afterFunc:()=>{}
        },options)
        return function(target, key, descriptor) {
            const oldTarget = descriptor.value
            descriptor.value=function(){
                beforeFunc.apply(this,arguments)
                const value=oldTarget.apply(this,arguments)
                afterFunc.apply(this.arguments)
                return value
            }
            return descriptor
        }
    }
    function logBefore(){
        console.log("before")
    }
    function logAfter(){
        console.log("after")
    }
    class MyClass {
        @AOP({
            beforeFunc:logBefore,
            afterFunc:logAfter
        })
        func(){
            console.log("run")
        }
    }
    const obj=new MyClass()
    obj.func()

这样每次就简单清晰的注入了before和after两个函数,在这两个函数中可以做数据预处理、数据状态监测等等一系列的功能,包括GA数据的监测也可以这样放入。这样和JAVA的AOP思路就十分的接近了。

  • 同样由于可以处理proptype,理论上可以实现类的依赖注入,传入依赖的字符串数组,然后解析出是npm模块还是本地文件,然后使用inline require动态加载对应的库,注入到类中。同样可以注入的还可以有项目的config的配置变量等等。
  • 待续
至大无外,至小无内。