Javascript元編程之Annotation

語言的自由度

自由度這個概念在不一樣領域有不一樣的定義,咱們借鑑數學中構成一個空間的維數來表達其自由度的作法,在此指的是:解決同一個問題彼此不相關的設計方法學數量。javascript

例如,解決一個好比商品打折的問題,如何設計順序、提取函數,具體的思路可能有不少,可是這可能都是從面向過程(OP)的角度,一樣解決這個問題,若是另外一門語言還支持面向對象(OO)的設計方法,那麼咱們認爲後者的自由度要多一些,由於OO提供了幾乎徹底從另外一個角度解決問題的能力。html

既然自由度能夠借鑑「維數」的定義,咱們來嘗試分析一下計算機語言的「維數」,在此以前,咱們有必要簡單分析一下語言是怎樣一步一步變得複雜的。java

本文關注的重點是命令式風格的計算機語言。git

第一步,出現告終構體(數據結構)、常量、變量、算符、順序、分支、循環等這些體現「命令」的基本方面;github

第二步,例程的出現,包括函數、過程等;web

第三步,宏的出現,包括宏、模板、泛型;編程

第四步,對客觀世界在結構化上抽象能力出現,包括OO等;數據結構

第五步,元編程能力的出現,如註釋、反射等等; …框架

從計算機語言歷史來看,以上步驟不必定按照時間順序展開,咱們更關注的是語言能力提高帶來的意義。其中,第二步的完成,標誌着結構化程序設計方法的出現,對大型軟件工程提供了較好的支持,第三步是對第二步的進一步抽象,第四步所表明的意義更加劇大,其中很是重要的一點,意味着終於能夠支持實現「層次化」,能夠實現將「內核」與「外圍」作分離,將相對穩定與潛在變化的部分分開,也就是說,編碼所表達的內容再也不只能扁平化,終於進化出了「階級」。函數

從本質上來說,以上演進反映了語言自身抽象能力的不斷提高。

這裏很是有意思的一個現象是,抽象化的不斷提高,會使得語言的維度提高至某個分數維——抽象的本質是在空間上提供了某種自類似的遞歸映射,從而體現出「分形」的結構形式,分形結構表現出在原有空間中增長了分數維,可是獲得一個新的整數維是困難的,即好比1維能夠提高至1.5維,可是沒法到達2維。

因此,目前絕大部分計算機高級語言的維度是1.X。

可是第五步,意味着語言開始真正走向一個更高的維度。

事實上,實現元編程有多種方式,從語言自己來說,能夠分爲兩類:加強型API與新的語法實現,前者的表明是反射,後者的表明爲Annotation。

咱們來看一個例子:

public class TestCase{
    @Before
    public void setUp() throws Exception{}
    @After
    public void tearDown() throws Exception{}
    @Test
    public void add() {}
}
複製代碼

上面是Java語言中使用Annotation類型定義了一個單元測試的三個階段,在這裏: @Before、@After、@Test用「變量」定義了「變量」,同時定義了執行的順序,這裏是「對編碼再進行編碼」的過程,是元編程的一種典型的實現。

咱們固然也能夠經過加強型API(反射或者用設計約束(好比摸版方法))來解決,可是不管哪種,都不如Annotation的方式要簡單直接明瞭。

根本的緣由,在於加強型API的實現方式與原有代碼這兩個表達邏輯的維度存在過多的「相關性」,即1.X維的,但Annotation的方式在相關性上大大減小,兩個維度更加解耦,因此後者的自由度更高。

以下JS基於Mocha的單元測試代碼:

describe('測試過程1', function() {
	it('1+1', function() {
		expect(fn_add(1, 1)).to.be.equal(2);
	});
});
複製代碼

咱們指望以下編程風格:

'@test(step=測試過程1,name=1+1,expect=2';
var step0 = function(){
	return fn_add(1, 1);
}
複製代碼

###JS實現基於註釋的元編程

咱們嘗試將Annotation的機制引入JS,以下:

'@Log(level=info,dateFormat=YYYY-MM-DD HH:mm)’;
var logInfo = function(_msg){
	console.log(_msg);
} 
複製代碼

複雜的場景,考慮多個註釋的相關性:

'@Start';
var serverStart = function(){}

'@Rule(fileType=.(html|htm))';
var proHtml = function(_req,_res){}

'@Rule(fileType=.(jpg|gif|webp))';
var proPic = function(_req,_res){}

'@Finish';
var serverFinish = function(){}
複製代碼

At-js框架

基於以上想法,咱們實現了At-js框架並開源,At-js的實現思路很是簡單,在Node.js端,經過覆蓋運行時JS文件加載機制實現對Annotation類型的識別判斷並對原生文件進行Enhance處理,爲性能考慮,At-js採用了正則掃描而非AST的方式。

At-js使用方法包括:定義註釋與使用註釋。

定義註釋:

require('at-js').define('helloworld',{//annotation's name
	scope: 'var', build: function () {//the scope of it's effected
    	return "return function(_msg) {console.log('[helloworld]'+_msg);};"//the real script
	}
})
複製代碼

使用註釋:

'@helloworld';
var sayHello = function(){}

sayHello('here')
複製代碼

運行效果:

[Helloworld]here

如下代碼描述了一個單元測試過程(https://github.com/CheMingjun/at-test):

'@test.start';
var start = function () {
	ds = {};
}

'@test.step(timeout=2000)';
var test0 = function* () {
	ds.test0 = 'finish';
	var rtn = yield (function(){
		return function(_next){
    		setTimeout(function(){
        		_next(null,3);
    	},2000)
		}
	})();
    assert.equal(rtn,3);
}

'@test.step';
var test1 = function () {
	ds.test1 = 'finish';
	return ds;
}

'@test.finish';
var fh = function () {
	ds = null;
}
複製代碼

At-js支持Var級及File不一樣級別的註釋定義,上例屬於File級別複雜的註釋定義,二者的API以下:

Var型註釋定義:

{
        scope:'var',
        build:function(_ctx, _argAry){
            //_ctx
            {
                filePath,//應該該註釋的文件位置
                name,//註釋名稱
                desc,//註釋中的變量表(key-value)
                refName,//被註釋的變量名稱
                refType//被註釋的變量類型(undefined|function|generator|object)
            }
            //_aryAry 被註釋變量簽名中的參數列表
        
            return //返回該變量被替換以後的代碼
        }
    }
複製代碼

File型註釋定義:

{
        return {
            which: {//針對改組annotation中的每一項作處理
                'test.start': function (_ctx, _argAry) {
                    //_ctx 與 _argAry 同上定義
                    //處理邏輯
                }
            }, script: function () {
                return //返回該文件追加的代碼
            }
        }
    }
複製代碼

在實際生產過程當中,以下一套註釋實現了ORM:

'@dao.column';
	var id;

	'@dao.column(name=name)';
	var name;

	'@dao.column(name=status)';
	var status;

	'@dao.column(name=creator_id)';
	var creatorId;

	'@dao.column(name=creator_name)';
	var creatorName;

	'@dao.column(name=gmt_create)';
	var createTime = function (_time) {
    		var mm = require('moment');
    		return mm(_time).format("YYYY-MM-DD HH:mm:ss");
	}

	'@dao.column(name=gmt_update)';
	var updateTime = function (_time) {
    		var mm = require('moment');
    		return mm(_time).format("YYYY-MM-DD HH:mm:ss");
	}

	'@dao.column(name=type)';
	var type;
複製代碼

總結

本文給出了語言自由度的簡單定義,並在此基礎上論述了在語言發展過程當中呈現的不一樣複雜性,並探討了元編程是如何從根本上增長語言的自由度的。在第二部分,咱們嘗試在JS語言基礎上增長原生的元編程能力並介紹了該思路的實現:At-js框架。

相關文章
相關標籤/搜索