前端框架系列之(裝飾器Decorator)

簡介:

裝飾器是ES2016 stage-2的一個草案,但是在babel的支持下,已被廣泛使用,有點類似java裏面的註解。

提案地址Class and Property Decorators

用法:

如果我們要在我們項目中使用最新的stage-2的裝飾器提案怎麼做呢?

Preset: babel-preset-stage-1
Plugins: babel-plugin-transform-decorators, babel-plugin-transform-decorators-legacy
First Pull Request: babel/babylon#587 by @peey
Babylon Label: Spec: Decorators

代碼如下:

@frozen class Foo {
  @configurable(false) @enumerable(true) method() {}
}
function frozen(constructor, parent, elements) {
  return {
    constructor,
    elements,
    finisher(constructor) {
      Object.freeze(constructor.prototype)
      Object.freeze(constructor)
    }
  }
}
function configurable(configurable) {
  return decorator;
  function decorator(previousDescriptor) {
    return {
      ...previousDescriptor,
      descriptor: {
        ...previousDescriptor.descriptor,
        configurable
      }
    }
  }
}
function enumerable(enumerable) {
  return decorator;
  function decorator(previousDescriptor) {
    return {
      ...previousDescriptor,
      descriptor: {
        ...previousDescriptor.descriptor,
        enumerable
      }
    }
  }
}

babel的更多提案大家可以參考:https://github.com/babel/proposals

Demo:

說了那麼多,我們直接上代碼。

項目目錄:

首先我們創建一個叫decorator-demo的項目:

decorator-demo
	demo 
		index.html//demo入口文件
	lib //babel編譯完畢後的文件
  src //工程源文件
  	demo1.js //demo測試入口
  babel.config.js //babel配置文件
  package.json //項目清單

在這裏插入圖片描述

index.html:

入口文件引用一個編譯好的demo1.js文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script src="../lib/demo1.js"></script>
</body>
</html>

demo1.js:

利用裝飾器修改name屬性值爲“yasin”

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
class Person {
    @defineName name;
}

function defineName(target, property, descriptor) {
    delete  descriptor.initializer;
    return {
        ...descriptor,
        value: "yasin"
    };
}

document.write(new Person().name);

babel.config.js:

babel配置文件,不懂的小夥伴可以查看babel官網,也可以參考我之前寫的一篇babel的文章 babel源碼解析一

module.exports = {
    "presets": [
        ["@babel/env", {"modules": false}]
    ],
    "plugins": [
        ["@babel/plugin-proposal-decorators", {"legacy": true}],
        ["@babel/proposal-class-properties", {"loose": true}]
    ]
};

babel裏面用到了:

  • @babel/preset-env:會根據當前環境自動做babel轉換es5代碼需要
  • @babel/plugin-proposal-decorators: babel裝飾器插件
  • @babel/proposal-class-properties: babel類屬性插件

babel、preset和插件的具體使用方法我就不在這裏介紹了

裝飾器使用可以參考@babel/plugin-proposal-decorators官網:

https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy

package.json:

{
  "name": "decorator-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.10.1",
    "@babel/core": "^7.10.2",
    "@babel/plugin-proposal-class-properties": "^7.10.1",
    "@babel/plugin-proposal-decorators": "^7.10.1",
    "@babel/preset-env": "^7.10.2"
  }
}

編譯:

首先在根目錄執行npm install

npm install

在根目錄執行babel編譯

$ npx babel ./src/demo1.js -o ./lib/demo1.js

執行完畢後會在lib目錄下面看到一個編譯過後的demo1.js文件:

var _class, _descriptor, _temp;

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }

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.keys(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.defineProperty(target, property, desc); desc = null; } return desc; }

function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
var Person = (_class = (_temp = function Person() {
  _classCallCheck(this, Person);

  _initializerDefineProperty(this, "name", _descriptor, this);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [defineName], {
  configurable: true,
  enumerable: true,
  writable: true,
  initializer: null
})), _class);

function defineName(target, property, descriptor) {
  delete descriptor.initializer;
  return _objectSpread(_objectSpread({}, descriptor), {}, {
    value: "yasin"
  });
}

document.write(new Person().name);

哈哈,反正我是看不懂寫了啥

運行:

直接瀏覽器打開我們的demo/index.html:

在這裏插入圖片描述

可以看到頁面上出現了我們的“yasin”字段。

修飾類

demo2.js:

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
@defineClass
class Person {
}

function defineClass(target) {
    console.log(target)
}

編譯過後demo2.js:

var _class;

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

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
var Person = defineClass(_class = function Person() {
  _classCallCheck(this, Person);
}) || _class;

function defineClass(target) {
  console.log(target);
}

源碼還是很簡單的吧,也就是把函數Person當參數傳給了defineClass方法

我們試着給Person的原型上加一個name屬性“yasin”:

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
@defineClass
class Person {
}

function defineClass(target) {
    target.prototype.name="yasin";
}
document.write(new Person().name);

重新編譯運行:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LjqhuPUv-1592134958078)(/Users/yinqingyang/doc/h5/study/高級程序設計/前端框架系列之decorator/屏幕快照 2020-06-14 下午6.46.46.png)]

修飾屬性:

demo1.js:

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
class Person {
    @defineName name;
}

function defineName(target, property, descriptor) {
    delete  descriptor.initializer;
    return {
        ...descriptor,
        value: "yasin"
    };
}

document.write(new Person().name);
  • target: Person的原型對象 Person.prototype
  • property: 屬性名稱’name’
  • descriptor: name屬性的描述對象,可通過Object.getOwnPropertyDescriptor獲取

編譯過後的源碼:

var Person = (_class = (_temp = function Person() {
    _classCallCheck(this, Person);

    _initializerDefineProperty(this, "name", _descriptor, this);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [defineName], {
    configurable: true,
    enumerable: true,
    writable: true,
    initializer: null
})), _class);

function defineName(target, property, descriptor) {
    delete descriptor.initializer;
    return _objectSpread(_objectSpread({}, descriptor), {}, {
        value: "yasin"
    });
}

document.write(new Person().name);

源碼還是比較好懂的吧,就不一一解析了。

修飾方法:

demo3.js:

/**
 * @author YASIN
 * @version [React-Native V01, 2020/6/14]
 * @date 2020/6/14
 * @description Person
 */
class Person {
    @defineMethod
    say(msg){
        console.log('hello '+msg);
    }
}

function defineMethod(target,property,descriptor) {
    Object.defineProperty(target, "name", {
        configurable: true,
        enumerable: true,
        value: "yasin"
    });
    const origin=descriptor.value;
    return {
        ...descriptor,
        value:function(msg){
            console.log('my name is '+this.name);
            return origin.call(this,...arguments);
        }
    }
}
new Person().say("world");
  • target: Person的原型對象 Person.prototype
  • property: 屬性名稱’name’
  • descriptor: name屬性的描述對象,可通過Object.getOwnPropertyDescriptor獲取

可以看到,我們給Person的原型定義了一個name屬性:

Object.defineProperty(target, "name", {
        configurable: true,
        enumerable: true,
        value: "yasin"
    });

然後重寫了say方法:

const origin=descriptor.value;
    return {
        ...descriptor,
        value:function(msg){
            console.log('my name is '+this.name);
            return origin.call(this,...arguments);
        }
    }

最後編譯運行:

可以在輸出欄console中看到

demo3.js:47 my name is yasin
demo3.js:31 hello world
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章