observejs改善元件程式設計體驗

【當耐特】發表於2015-05-18

傳送門

observejs:https://github.com/kmdjs/observejs
本文演示:http://kmdjs.github.io/observejs/list/
本文程式碼:https://github.com/kmdjs/observejs/tree/master/example/list

寫在前面

javascript的程式設計體驗一直被改善,從未停止過。從最早的Jscex(現在的windjs),改善非同步程式設計體驗。如今ES6的Generator改善非同步程式設計體驗。還有類似的seajs、requirejs提供極致的模組化開發體驗;五花八門的Class.js改善物件導向程式設計體驗;kmdjs同時改善模組化程式設計、物件導向程式設計和構建體驗;各式各樣的template.js改善資料->標記的體驗。
所有的改善,使程式碼更直觀、友好,使程式易維護、可擴充套件。
最近使用observejs開發元件,發現有幾大優點:

  • Dom操作幾乎絕跡
  • 專注於資料操作
  • 檢視全自動更新
  • 效能棒棒的
  • 僅一行程式碼搞定上面四點

本文使用世界上最簡單的List元件作為例子,大家通過該例子感受一下observejs元件開發改善之處。

元件程式碼

var List = function (option) {
    this.option = option;
    this.data = option.data;
    this.parent = document.querySelector(this.option.renderTo);
    this.tpl =
        '<div class="list-group" style="  text-align: center;width:<%=typeof width === "number"?width+"px":width%>;" >'
        + '            <% for ( var i = 0, len = data.length; i < len; i++) { %>'
        + '<%     var item = data[i]; %>'
        + '<a class="list-group-item <%=item.active ? "active" : ""%> <%=item.disabled ? "disabled" : ""%>" href="<%=item.href%>" target="<%=item.target?item.target:""%>"><%=item.text%><\/a>'
        + '<% } %>'
        + '<\/div>';
    this.render();
    //list.render建議使用debounce來控制執行頻率提高效能,或者和react一樣在下次執行requestAnimFrame的時候更新
    observe(this, "option", this._debounce(this.render, 200));
}
List.prototype = {
    render: function () {
        if (this.node) this.parent.removeChild(this.node);
        this.parent.innerHTML += this._tpl(this.tpl, this.option);
        this.node = this.parent.lastChild;
    },
    clear:function(){
        this.data.size(0);
    },
    remove:function(index){
        this.data.splice(index,1);
    },
    add:function(item){
        this.data.push(item);
    },
    edit:function(index,item){
        this.data[index]=item;
    },
    disable:function(index){
        this.data[index].disabled = true;
    },
    _tpl: function (str, data) {
        var tmpl = 'var __p=[];' + 'with(obj||{}){__p.push(\'' +
            str.replace(/\\/g, '\\\\')
                .replace(/'/g, '\\\'')
                .replace(/<%=([\s\S]+?)%>/g, function (match, code) {
                    return '\',' + code.replace(/\\'/, '\'') + ',\'';
                })
            .replace(/<%([\s\S]+?)%>/g, function (match, code) {
                return '\');' + code.replace(/\\'/, '\'')
                    .replace(/[\r\n\t]/g, ' ') + '__p.push(\'';
            })
            .replace(/\r/g, '\\r')
                .replace(/\n/g, '\\n')
                .replace(/\t/g, '\\t') +
                '\');}return __p.join("");',
 
                func = new Function('obj', tmpl);
 
        return data ? func(data) : func;
    },
    _debounce: function (func, wait, immediate) {
        var timeout;
        return function () {
            var context = this, args = arguments;
            var later = function () {
                timeout = null;
                if (!immediate) func.apply(context, args);
            };
            var callNow = immediate && !timeout;
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
            if (callNow) func.apply(context, args);
        };
    },
}

ps:模版引擎來自GMU,當然你可以使用任何你喜歡的額模版引擎

元件使用

var list = new List({
    data: [
        { text: "天下太貳", disabled: true, active: true },
        { text: "魔獸爭霸", href: "##", target: "_blank" },
        { text: "魔獸世界" },
        { text: "坦克世界" },
        { text: "超級瑪麗", disabled: true }
    ],
    width: 150,
    renderTo: "body"
})
list.edit(1,{text:"haha"})
list.add({ text: "aaaa" });
list.remove(2, 1);
list.disable(2);
list.remove(1);
//運算元據等於操作dom
list.data[0].text="aaaaaaa";

元件分析

其中僅需要一行

observe(this, "option", this._debounce(this.render, 100));

就可以實現option的監聽,option包含元件的配置,以及資料,資料的變換都能夠通知render方法的呼叫。
可以看到render方法摧毀了整個dom,然後根據資料重新渲染dom。所以控制render的執行頻率變得尤其重要。
如果資料變化頻繁,render方法呼叫頻繁,從而降低效能,所以,可以看到上面使用debounce控制render執行的頻率。
如果不使用debounce,也有第二種方案控制render頻率。該方案和react一樣,在requestAnimateFrame下次迴圈的時候去check元件是否要re-render,需要的話就重新渲染。如果一個頁面有一百個元件,都統一在requestAnimateFrame迴圈中check是否re-render。
可以看到,只有render方法裡有dom操作。clear、remove、add、edit、disable都只關心資料,資料變了自動會通知到render。
改善的地方在哪裡?分兩處地方:
1.元件內部程式碼,內部的增刪改查都只關注資料就行
2.元件使用程式碼,運算元據等同於操作dom
當然如果不使用賦值又沒有observejs的情況下可以使用方法呼叫的方式,使得一行程式碼什麼都能幹,但是如果屬性太多,你得定義非常多setXXX方法。但你必須知道:賦值勝於method(即【obj.name=xx】>【obj.setName(xx)】)、約定勝於配置:)。

如果你不是很適應react激進虛擬dom和jsx,如果你反感react放棄了HTML+CSS大量優秀特性功能,如果以後dom效能好了,全世界都i78核+ssd了,那麼HTML+CSS才是王道啊。
observejs也許是你的另一選擇,歡迎嘗試,感謝對observejs提出那麼多寶貴建議和意見的童鞋。
observejs:https://github.com/kmdjs/observejs
本文演示:http://kmdjs.github.io/observejs/list/
本文程式碼:https://github.com/kmdjs/observejs/tree/master/example/list

相關文章