1. 程式人生 > >使用babel深入理解es7的decorator

使用babel深入理解es7的decorator

es7提出了decorator的語法,讓我們能寫出更優雅更好維護的程式碼,裝飾器的本質是對現有的功能進行包裝,可以用來加鉤子或者增強功能等,js從語法層面支援這種寫法,讓我們可以保持程式碼解耦。
比如我們有一個函式

funciton update() {
    console.log('update db')
}

我們想在執行這個函式時打日誌,我們可能會這樣改寫

funciton update() {
    log('log')
    console.log('update db')
}

或者用高階函式裝飾update

decoratorUpdate = funciton( ) {
    log
('log') update(); }

這樣會讓我們的程式碼被業務無關的程式碼侵入,增加了耦合度。
在es7裡我們可以這樣寫

funciton log(className, propName, descriptor) {
    var value = descriptor.value;
    descriptor.value = funciton() {
        console.log('update db');
        value();
    }
}

class Curd{
    @log
    update() {
    }
}

這樣我們的程式碼裡,業務相關的程式碼和無關的程式碼分離。更加清晰。
下面我們使用babel看一下es7的decorator本質是什麼。

1.首先我們安裝babel。

npm install babel-loader  babal-core  babel-preset-es2015 babel-plugin-transform-decorators-legacy

寫某個資料夾下新建.babelrc檔案。

{
    "presets": [
                // 把es6轉成es5
        'babel-preset-es2015'
    ],
    // 把裝飾器語法轉成es5
    "plugins": ['transform-decorators-legacy']
  }

然後,新建index.js

/*
    target 類的prototype 
    name 類prototype上的屬性
    descriptor 描述符
*/

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

// descriptor為資料描述符
function log(target, name, descriptor) {
    // 儲存原來的值
    var value = descriptor.value;

    // 加鉤子
    descriptor.value = function() {
        console.log('log');
        // 呼叫原來的值
        value();
    }
    return descriptor;
}

// descriptor為存取描述符
function get(target, name, descriptor) {
    var value = descriptor.get;

    descriptor.get = function() {
        console.log('log');
        console.log(value())
    }
    return descriptor
}

function decoratorFactory(type) {
    switch(type) {
        case 1: return function(target, name, descriptor) {
                    var value = descriptor.value;
                    descriptor.value = function() {
                        console.log('you will say hello =>');
                        console.log(value())
                    }
                    return descriptor;
                }
        case 2: return function(target, name, descriptor) {
                    var value = descriptor.value;
                    descriptor.value = function() {
                        console.log('you will say world =>');
                        console.log(value())
                    }
                    return descriptor;
                }
    }
}

function classDecorator(className) {
    className.flag = true;
    return className;
}

@classDecorator
class a {
    @readonly
    name() {
        return 1;
    }

    @log
    update() {
        console.log('update db');
    };

    @get
    get say() {
        return 1
    }
    // @後面跟的值是一個函式,所以我們可以計算一個表示式,返回一個函式
    @(false ? decoratorFactory(1) : decoratorFactory(2)) 
    hello() {
        console.log('hello')
    }

    @decoratorFactory(2) 
    world() {
        console.log('world')
    }

    @decoratorFactory(1) 
    @decoratorFactory(2) 
    double() {
        console.log('double')
    }
}

//a.prototype.name = 1 // throw error
console.log(new a().update())
console.log(new a().hello())
console.log(new a().world())
console.log(new a().double())
console.log(new a().say)

在當我資料夾下執行babel index.js -o index-babel.js得到

'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

var _dec, _dec2, _dec3, _dec4, _class, _desc, _value, _class2;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

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;
    }

    desc = decorators.slice().reverse().reduce(function (desc, decorator) {
        return decorator(target, property, desc) || desc;
    }, desc);

    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;
}

/*
    target 類的prototype 
    name 類prototype上的屬性
    descriptor 描述符
*/

function readonly(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

// descriptor為資料描述符
function log(target, name, descriptor) {
    // 儲存原來的值
    var value = descriptor.value;

    // 加鉤子
    descriptor.value = function () {
        console.log('log');
        // 呼叫原來的值
        value();
    };
    return descriptor;
}

// descriptor為存取描述符
function get(target, name, descriptor) {
    var value = descriptor.get;

    descriptor.get = function () {
        console.log('log');
        console.log(value());
    };
    return descriptor;
}

function decoratorFactory(type) {
    switch (type) {
        case 1:
            return function (target, name, descriptor) {
                var value = descriptor.value;
                descriptor.value = function () {
                    console.log('you will say hello =>');
                    console.log(value());
                };
                return descriptor;
            };
        case 2:
            return function (target, name, descriptor) {
                var value = descriptor.value;
                descriptor.value = function () {
                    console.log('you will say world =>');
                    console.log(value());
                };
                return descriptor;
            };
    }
}

function classDecorator(className) {
    className.flag = true;
    return className;
}

var a = (_dec = false ? decoratorFactory(1) : decoratorFactory(2), _dec2 = decoratorFactory(2), _dec3 = decoratorFactory(1), _dec4 = decoratorFactory(2), classDecorator(_class = (_class2 = function () {
    function a() {
        _classCallCheck(this, a);
    }

    _createClass(a, [{
        key: 'name',
        value: function name() {
            return 1;
        }
    }, {
        key: 'update',
        value: function update() {
            console.log('update db');
        }
    }, {
        key: 'hello',
        value: function hello() {
            console.log('hello');
        }
    }, {
        key: 'world',
        value: function world() {
            console.log('world');
        }
    }, {
        key: 'double',
        value: function double() {
            console.log('double');
        }
    }, {
        key: 'say',
        get: function get() {
            return 1;
        }
        // @後面跟的值是一個函式,所以我們可以計算一個表示式,返回一個函式

    }]);

    return a;
}(), (_applyDecoratedDescriptor(_class2.prototype, 'name', [readonly], Object.getOwnPropertyDescriptor(_class2.prototype, 'name'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'update', [log], Object.getOwnPropertyDescriptor(_class2.prototype, 'update'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'say', [get], Object.getOwnPropertyDescriptor(_class2.prototype, 'say'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'hello', [_dec], Object.getOwnPropertyDescriptor(_class2.prototype, 'hello'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'world', [_dec2], Object.getOwnPropertyDescriptor(_class2.prototype, 'world'), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, 'double', [_dec3, _dec4], Object.getOwnPropertyDescriptor(_class2.prototype, 'double'), _class2.prototype)), _class2)) || _class);

//a.prototype.name = 1 // throw error

console.log(new a().update());
console.log(new a().hello());
console.log(new a().world());
console.log(new a().double());
console.log(new a().say);

done。

2 在webpack中使用decorator
加配置

loaders: [
            {
                test: /\.js|jsx$/, //是一個正則,代表js或者jsx字尾的檔案要使用下面的loader
                loader: "babel-loader",
                query: {
                    // 把es6轉成es5
                    presets: ['es2015'],
                    // 把decorator轉成es5
                    plugins: ['transform-decorators-legacy']
                }
            }

        ]