我的MVVM框架 v2發布


此版本仍然有許多knouckoutjs的影子,其中最下方那個normalizeJSON直接抄自knouckoutjs,我深感內疚。

knouckoutjs的聲明式綁定的值部分是非常復雜,它允許用戶直接在里面使用函數,表達式什么,然后再往它兩邊花括號一包就是一個JSON的字面量。但直接轉換為JSON是不行的,IE對鍵名為關鍵字的對象肯定報錯,必須要用雙引號括起。於是才有了normalizeJSON這東西。另一方面,knouckoutjs為VM的每個字段建立關聯的方式非常復雜冗長,大量使用閉包。這個我的MVVM框架avalon做了改進。我非常討厭knouckoutjs的事件綁定方式,那還不如直接用onclick=xxx綁好了,或結合jQuery, mass Framework能得到更大的回報。不過在v2中,我還沒有想到更好的方法,既不跟風照搬,也沒有搞出相應方案,因此事件綁定部分是一片空白。

說說avalon v2的優點吧。只有730行,非常精簡,雖然建立在功能殘缺的基礎上,沒有knouckoutjs的虛擬結點,沒有事件綁定,沒有options綁定。

雙向綁定鏈的許多概念已經成形。我們做一件事,要做好,就要知道怎么去做,什么分步做。這些步驟,其中細節,全部都要抽象成特定的術語才能指導我們行動。knouckoutjs之所以能在前端MVC擁有一席之地,是因為它確定了許多以后被大家抄襲的東西。knouckout的三大理論基石:

  • 位於VM中的監控屬性, 監控數組, 依賴監控屬性。
  • 位於視圖中的聲明式綁定,以data-bind的形式寫在標簽中。
  • 位於視圖中的動態模板,就是那些被聲明式綁定圈起來的區域。

avalon也建立對應的概念來指導自己的發展:

  • 位於VM中的原子監控者,依賴監控者,監控數組與綁定監控者。原子監控者對應ko的監控屬性,它是由M中字段抽象而成,是一個函數。依賴監控者對應ko的依賴監控屬性。在VM中,有些字段是建立在M的兩個或多個字段的基礎上。比如fullName 對應為 firstName+" "+lastName,但在M中是找不到它的跟影,但它在視圖中卻又是一個獨立的個體,有專門的文本域給它。綁定監控者是我獨創的,它是根據視圖中的數據綁定轉化而為,在ko中這些依然稱之為依賴監控屬性。但這一類監控函數分明是真正用於連結DOM樹與VM的。它通過框架提供的默認綁定器將自己中數據渲染到DOM中。它的結構比一般的依賴監控屬性復雜多了。依賴監控屬性,knouckoutjs稱之為Dependent Observables,在另一個著名的emberjs框架中,它叫做computed。在綁定監控者這類東西顯然不需要返回值,它一般將比它低一級的原子監控者,依賴監控者當作事件回調來監控用戶行為就行了。當用戶改變了文本域的值,就相當於對這個原子監控者進行寫入,從而引發整條依賴的更新。綁定監控者是與DOM樹某個節點或某一些節點交道,當底層涌上來的變動要求它也刷新時,它就調用框架自帶的綁定器進行干活。因此你看到綁定器都有一個叫update的函數,就是干這事。
  • 位於視圖中的聲明式綁定,我使用bind,但也可以配置你喜歡的屬性名。我個人喜歡稱之為數據綁定,其實它還兼容流程控制,事件綁定的活兒。
  • 位於視圖中的動態模板,就是那些被聲明式綁定圈起來的區域。動態模板與流程綁定最密切,它的實現好壞,牽及到最小化刷新的實現與內存的占有率。這個v2有實現比knouckout要高明多了。

avalon在數據綁定上做了許多友好的改進,雖然與knouckoutjs一樣支持表達式,是沒有花括號的JSON。但它要求用戶不用寫雙引號,這辛苦活由框架去實現了。

avalon的自帶綁定器有text綁定,html綁定,value綁定,class綁定,style綁定,attr綁定,display綁定,checked綁定,with綁定,if綁定,unless綁定,foreach綁定

有關它們的使用,可以查看這里

不過大家也急着看它們是怎么用的,因為寫這文章時,我已經完成非常先進的v3了。這世界就是這樣,當日本把小靈通賣給中國時,人家已經普及下下一代的方案。另一更讓人心痛的比喻時,當中國在炫耀那垃圾的J10時,美國早在幾十年前把飛碟,水上飛機,鴨嘴機玩一遍了。這就是差距。比差距更可怕的是知識的斷層。面試時有人問我既然Sizzle這么快了,為什么還要自己寫個?這說得那啥的,人家兜里有一百萬跟我屁事啊!反正,我是搞出選擇器,加載器,動畫引擎,異步列隊,操作流等東西出來。知識體系,至少是在前端的應用層是完整的。MVVM是前端究極解決方案。從 v1到v2,起碼我算是逐漸吃透相關概念,形成自己的解決方案。這個對於后來想進入這領域的國人來說,也留下一點帶路石。

define("avalon",["$attr","$event"], function(){
    $.log("已加載avalon v2", 7);
    var BINDING = $.config.bindname || "bind", bridge = {}, uuid = 0, expando = new Date - 0;
    //將一個普通的對象轉換為ViewModel,ViewModel里面每個對象都是監控者(監控函數或監控數組)
    $.ViewModel = function(data, model){
        model = model || {};
        if(Array.isArray(data)){
            return listWatch(data);
        }
        for(var p in data) {
            if(data.hasOwnProperty(p)) {
                addWatchs(p, data[p], model);
            }
        }
        return model;
    }
    //監控數組,listWatch,它實質上就是一個數組,不過它的許多方法都被覆寫了,
    //以便與DOM實現同步在您添加、刪除、移動、刷新或替換數組中的項目時觸發對應元素的局部刷新
    //我們可以在頁面通過foreach綁定此對象
    function listWatch(array, models){
        models = models || [];
        for(var index = 0; index < array.length; index++){
            var f =  addWatchs(index, array[index], models);
            f.$value = f.$value || f;
        }
        String("push,pop,shift,unshift,splice,sort,reverse").replace($.rword, function(method){
            var nativeMethod = models[ method ];
            models[ method ] = function(){
                nativeMethod.apply( models, arguments)
                var Watchs = models["$"+expando];
                for(var i = 0, Watch; Watch = Watchs[i++];){
                    Watch(method, arguments);
                }
            }
        });
        models.removeAt = function(index){//移除指定索引上的元素
            models.splice(index, 1);
        }
        models.remove = function(item){//移除第一個等於給定值的元素
            var array = models.map(function(el){
                return el();
            })
            var index = array.indexOf(item);
            models.removeAt(index);
        }
        //模擬監控函數的行為,監控函數在foreach都會生成一個$value函數
        models.$value = function(){
            return models
        }
        models.$value.$uuid = ++uuid;
        return models;
    }
    //將ViewMode綁定到元素節點上,沒有指定默認是綁在body上
    $.View = function(model, node){
        node = node || document.body;
        //開始在其自身與孩子中綁定
        return setBindingsToElementAndChildren.call( node, model );
    }
    //我們根據用戶提供的最初普通對象的鍵值,選用不同的方式轉換成各種監控函數或監控數組
    var err = new Error("只能是字符串,數值,布爾,Null,Undefined,函數以及純凈的對象")
    function addWatchs( key, val, model ){
        switch( $.type( val )){
            case "Null":
            case "Undefined":
            case "String":
            case "Number":
            case "Boolean":
                return atomWatch( key, val, model );
            case "Function":
                return depsWatch( key, val, model, "get");
            case "Array":
                var models = model[key] || (model[key] = []);
                return listWatch( val, models );
            case "Object":
                if($.isPlainObject( val )){
                    if( $.isFunction( val.setter ) && $.isFunction( val.getter )){
                        return  depsWatch( key, val, model, "setget");
                    }else{
                        var object = model[key] || (model[key] = {});
                        $.ViewModel( val,object );
                        object.$value = function(){
                            return object
                        }
                        return object
                    }
                }else{
                    throw err
                }
                break;
            default:
                throw err
        }
    }

    //atomWatch,原子監控者,它是最簡單的監控函數,是指在ViewModel定義時,鍵值為基礎類型的個體
    //它們是位於雙向依賴鏈的最底層。不需要依賴於其他Watch!
    function atomWatch( key, val, host ){
        function Watch( neo ){
            if( bridge[ expando ] ){ //收集依賴於它的depsWatch,以便它的值改變時,通它們更新自身
                $.Array.ensure( Watch.$deps, bridge[ expando ] );
            }
            if( arguments.length ){//在傳參不等於已有值時,才更新自已,並通知其的依賴
                if( Watch.$val !== neo ){
                    Watch.$val = neo;
                    updateDeps( Watch );
                }
            }
            return Watch.$val;
        }
        Watch.$val = val;
        Watch.$uuid = ++uuid
        return addWatch( key, Watch, host );
    }

    //depsWatch,依賴監控者,是指在ViewModel定義時,值為類型為函數,或為一個擁有setter、getter函數的對象。
    //它們是位於雙向綁定鏈的中間層,需要依賴於其他atomWatch或depsWatch的返回值計算自己的value。
    //當頂層的VM改變了,通知底層的改變
    //當底層的VM改變了,通知頂層的改變
    //當中間層的VM改變,通知兩端的改變
    function depsWatch( key, val,host, type){
        var getter, setter//構建一個至少擁有getter,scope屬性的對象
        if(type == "get"){//getter必然存在
            getter = val;
        }else if(type == "setget"){
            getter = val.getter;
            setter = val.setter;
            host = val.scope || host;
        }
        function Watch( neo ){
            if( bridge[ expando ] ){
                //收集依賴於它的depsWatch與bindWatch,以便它的值改變時,通知它們更新自身
                $.Array.ensure( Watch.$deps, bridge[ expando ] );
            }
            var change = false;
            if( arguments.length ){//寫入新值
                if( setter ){
                    setter.apply( host, arguments );
                }
            }else{
                if( !("$val" in Watch) ){
                    if( !Watch.$uuid ){
                        bridge[ expando ] = Watch;
                        Watch.$uuid = ++uuid;
                    }
                    neo = getter.call( host );
                    change = true;
                    delete bridge[ expando ];
                }
            }
            //放到這里是為了當是最底層的域的值發出改變后,當前域跟着改變,然后再觸發更高層的域
            if( change && (Watch.$val !== neo) ){
                Watch.$val = neo;
                //通知此域的所有直接依賴者更新自身
                updateDeps( Watch );
            }
            return Watch.$val;
        }
        return addWatch( key, Watch, host );
    }
    //bindWatch,綁定監控者,用於DOM樹或節點打交道的Watch,它們僅在用戶調用了$.View(viewmodel, node ),
    //把寫在元素節點上的bind屬性的分解出來之時生成的。
    //names values 包含上一級的鍵名與值
    function bindWatch (node, names, values, key, str, binding, model ){
        function Watch( neo ){
            if( !Watch.$uuid ){ //只有在第一次執行它時才進入此分支
                if( key == "foreach" ){
                    var arr = model[str]
                    var p = arr["$"+expando] || ( arr[ "$"+ expando] =  [] );
                    $.Array.ensure( p ,Watch);
                    arguments = ["start"];
                }
                bridge[ expando ] = Watch;
            }
            var callback, val;
            try{
                val = Function(names, "return "+ str).apply(null, values );
            }catch(e){
                return  $.log(e, 3)
            }
            if(typeof val == "function" ){ //&& isFinite( val.$uuid ) 如果返回值也是個域
                callback = val; //這里的域為它所依賴的域
                val = callback();//如果是監控體
            }
            if( !Watch.$uuid ){
                delete bridge[ expando ];
                Watch.$uuid = ++uuid;
                //第四個參數供流程綁定使用
                binding.init && binding.init(node, val, callback, Watch);
            }
            var method = arguments[0], args = arguments[1]
            if( typeof binding[method] == "function" ){
                //處理foreach.start, sort, reserve, unshift, shift, pop, push....
                var ret = binding[method]( Watch, val, Watch.fragments, method, args );
                if(ret){
                    val = ret;
                }
            }
            //只有執行到這里才知道要不要中斷往下渲染
            binding.update(node, val, Watch, model, names, values);
            return Watch.$val = val;
        }
        return addWatch( "interacted" ,Watch, node);
    }
    //執行綁定在元素標簽內的各種指令
    //MVVM不代表什么很炫的視覺效果之類的,它只是組織你代碼的一種方式。有方便后期維護,松耦合等等優點而已
    var inputOne = $.oneObject("text,password,textarea,tel,url,search,number,month,email,datetime,week,datetime-local")
    $.ViewBindings = {
        text: {
            update:  function( node, val ){
                val = val == null ? "" : val+""
                if(node.childNodes.length === 1 && node.firstChild.nodeType == 3){
                    node.firstChild.data = val;
                }else{
                    $( node ).text( val );
                }
            }
        },
        value:{
            init: function(node, val, Watch){
                if(/input|textarea/i.test(node.nodeName) && inputOne[node.type]){
                    $(node).on("input",function(){
                        Watch(node.value)
                    });
                }
            },
            update:  function( node, val ){
                node.value = val;
            }
        },
        html: {
            update:  function( node, val ){
                $( node ).html( val );
            },
            stopBindings: true
        },
        //通過display樣式控制顯隱
        display: {
            update:  function( node, val ){
                node.style.display = val ? "" : "none";
            }
        },
        enable: {
            update:  function( node, val ){
                if (val && node.disabled)
                    node.removeAttribute("disabled");
                else if ((!val) && (!node.disabled))
                    node.disabled = true;
            }
        },
        style: {
            update:  function( node, val ){
                var style = node.style, styleName;
                for (var name in val) {
                    styleName = $.cssName(name, style) || name;
                    style[styleName] = val[ name ] || "";
                }
            }
        },
        "class": {
            update:  function( node, val ){
                if (typeof val == "object") {
                    for (var className in val) {
                        var shouldHaveClass = val[className];
                        toggleClass(node, className, shouldHaveClass);
                    }
                } else {
                    val = String(val || '');
                    toggleClass(node, val, true);
                }
            }
        } ,
        attr: {
            update:  function( node, val ){
                for (var name in val) {
                    $.attr(node, name, val[ name ] );
                }
            }
        },
        checked: {
            init:  function( node, val, Watch ){
                if(typeof Watch !== "function"){
                    throw new Error("check的值必須是一個Feild")
                }
                $(node).bind("change",function(){
                    Watch(node.checked);
                });
            },
            update:function( node, val ){
                if ( node.type == "checkbox" ) {
                    if (Array.isArray( val )) {
                        node.checked = val.indexOf(node.value) >= 0;
                    } else {
                        node.checked = val;
                    }
                } else if (node.type == "radio") {
                    node.checked = ( node.value == val );
                }
            }
        },
        template: {
            //它暫時只供內部使用
            update: function( node, val, callback, model, names, values){
                var transfer = callback(), code = transfer[0], Watch = transfer[1];
                var fragment = Watch.fragments[0];         //取得原始模板
                if( code > 0 ){                            //處理with if 綁定
                    fragment.recover();                    //將Watch所引用着的節點移出DOM樹
                    var elems = getChildren( fragment );   //取得它們當中的元素節點
                    node.appendChild( fragment );          //將Watch所引用着的節點放回DOM樹
                    if( elems.length ){
                        if( code == 2 ){                    //處理with 綁定
                            model = transfer[2]
                        }
                        return setBindingsToChildren.call( elems, model, true, names, values )
                    }
                }else if( code === 0 ){                    //處理unless 綁定
                    fragment.recover();
                }
                if( code < 0  && val ){                    //處理foreach 綁定
                    var fragments = Watch.fragments, models = val;
                    if(!models.length){
                         fragments[0].recover();
                         return
                    }
                    for( var i = 0, el ; el = fragments[i]; i++){
                        el.recover();                      //先回收,以防在unshift時,新添加的節點就插入在后面
                        elems = getChildren( el );
                        node.appendChild( el );            //繼續往元素的子節點綁定數據
                        setBindingsToChildren.call( elems, models[i], true, names, values );
                    }
                }
            },
            stopBindings: true
        }
    }
    $.ViewBindings.disable = {
        update: function( node, val ){
            $.ViewBindings.enable.update(node, !val);
        }
    }
    //if unless with foreach四種bindings都是基於template bindings
    "if,unless,with,foreach,case".replace($.rword, function( type ){
        $.ViewBindings[ type ] = {
            init: function(node, _, _, Watch){
                node.normalize();                  //合並文本節點數
                var fragment = node.ownerDocument.createDocumentFragment(), el
                while((el = node.firstChild)){
                    fragment.appendChild(el);     //將node中的所有節點移出DOM樹
                }
                Watch.fragments = [];             //添加一個數組屬性,用於儲存經過改造的文檔碎片
                Watch.fragment = fragment;         //最初的文檔碎片,用於克隆
                Watch.cloneFragment = function( dom, unshift ){ //改造文檔碎片並放入數組
                    dom = dom || Watch.fragment.cloneNode(true);
                    var add = unshift == true ? "unshift" : "push"
                    Watch.fragments[add]( patchFragment(dom) );
                    return dom;
                }
                var clone = Watch.cloneFragment();  //先改造一翻,方便在update時調用recover方法
                node.appendChild( clone );          //將文檔碎片中的節點放回DOM樹
            },
            update : function(node, val, Watch, model, names, values){
                $.ViewBindings['template']['update'](node, val, function(){
                    switch(type){//返回結果可能為 -1 0 1 2
                        case "if":
                            return [ !!val - 0, Watch];//1 if
                        case "unless":
                            return [!val - 0, Watch]; //0  unless
                        case "with":
                            return [2, Watch, val];   //2  with
                        default:
                            return [-1, Watch];       //-1 foreach
                    }
                }, model, names, values);
            },
            stopBindings: true
        }
    });
    //foreach綁定擁有大量的子方法,用於同步數據的增刪改查與排序
    var foreach = $.ViewBindings.foreach;
    foreach.start = function( Watch, models, fragments, method, args ){
        if(!Array.isArray(models)){
            var array = []
            for(var key in models){
                //通過這里模擬數組行為
                if(models.hasOwnProperty(key) && (key !== "$value") && (key != "$"+expando)){
                    var value = models[key];
                    value.$value = value;
                    array.push( value );
                }
            }
            models = array
        }
        for(var i = 1; i < models.length; i++ ){
            Watch.cloneFragment();
        }
        return models
    };
    //push ok
    foreach.push = function( Watch, models, fragments, method, args ){
        var l = fragments.length
        for(var index = 0; index < args.length; index++ ){
            var n = index + l;
            var f =  addWatchs(n, models[n], models);
            f.$value = f;
            Watch.cloneFragment()
        }
    }
    //unshift ok
    foreach.unshift = function( Watch, models, fragments, method, args ){
        for(var index = 0; index < args.length; index++ ){
            var f =  addWatchs(index, models[index], models);
            f.$value = f;
            Watch.cloneFragment(0, true)
        }
        for( index = 0; index < models.length; index++ ){
            models[index].$key = index
        }
    }
    // shift pop ok
    foreach.shift = function( Watch, models, fragments, method, args ){
        var fragment = fragments[method]()
        fragment.recover();
        for(var index = 0; index < models.length; index++ ){
            models[index].$key = index
        }
    }
    foreach.pop = foreach.shift;
    foreach.splice = function( Watch, models, fragments, method, args ){
        var start = args[0], n = args.length - 2;
        var removes = fragments.splice(start, args[1]);
        //移除對應的文檔碎片
        for(var i = 0; i < removes.length; i++){
            removes[i].recover();
        }
        for(i = 0; i < n; i++ ){
            //將新數據封裝成域
            var index = start + i
            var f =  addWatchs(index, models[ index ], models);
            f.$value = f;
            //為這些新數據創建對應的文檔碎片
            var dom = Watch.fragment.cloneNode(true);
            Watch.fragments.splice(index, 0, patchFragment(dom) );
        }
        for( index = start+n; index < models.length; index++ ){
            models[index].$key = index
        }
    }
    //對文檔碎片進行改造,通過nodes屬性取得所有子節點的引用,以方便把它們一並移出DOM樹或插入DOM樹
    function patchFragment( fragment ){
        fragment.nodes = $.slice( fragment.childNodes );
        fragment.recover = function(){
            this.nodes.forEach(function( el ){
                this.appendChild(el)
            },this);
        }
        return fragment;
    }
    //$.ViewBindings.class的輔助方法
    var toggleClass = function (node, className, shouldHaveClass) {
        var classes = (node.className || "").split(/\s+/);
        var hasClass = classes.indexOf( className) >= 0;//原className是否有這東西
        if (shouldHaveClass && !hasClass) {
            node.className += (classes[0] ? " " : "") + className;
        } else if (hasClass && !shouldHaveClass) {
            var newClassName = "";
            for (var i = 0; i < classes.length; i++)
                if (classes[i] != className)
                    newClassName += classes[i] + " ";
            node.className = newClassName.trim();
        }
    }


    //為當前元素把數據隱藏與視圖模塊綁定在一塊
    //參數分別為model, pnames, pvalues
    $.fn.model = function(){
        return $._data(this[0], "$model")
    }
    $.fn.$value = function(){
        var watch = $(this).model()
        if(typeof watch == "function"){
            return watch();
        }
    }
    //取得標簽內的屬性綁定,然后構建成bindWatch,並與ViewModel關聯在一塊
    function setBindingsToElement( model, pnames, pvalues ){
        var node = this;
        pnames = pnames || [];
        pvalues = pvalues || [];
        var attr = node.getAttribute( BINDING ), names = [], values = [], continueBindings = true,
        key, val, binding;
        $._data(node,"$model",model);
        for(var name in model){
            if(model.hasOwnProperty(name)){
                names.push( name );
                values.push( model[ name ] );
            }
        }
        if(pnames.length){
            pnames.forEach(function(name, i){
                if(names.indexOf(name) === -1){
                    names.push(name);
                    values.push(pvalues[i])
                }
            })
        }
        var array = normalizeJSON("{"+ attr+"}",true);
        for(var i = 0; i < array.length; i += 2){
            key = array[i]
            val = array[i+1];
            binding = $.ViewBindings[ key ];
            if( binding ){
                if( binding.stopBindings ){
                    continueBindings = false;
                }
                if(key == "foreach" && Array.isArray(model[key]) && !model[key].length ){
                    continueBindings = false;
                  //  continue
                }
                bindWatch(node, names, values, key, val, binding, model);
            }
        }
        return continueBindings;
    }
    //在元素及其后代中將數據隱藏與viewModel關聯在一起
    //參數分別為model, pnames, pvalues
    function setBindingsToElementAndChildren(){
        if ( this.nodeType === 1  ){
            var continueBindings = true;
            if( hasBindings( this ) ){
                continueBindings = setBindingsToElement.apply(this, arguments);
            }
            if( continueBindings ){
                var elems = getChildren( this );
                elems.length && setBindingsToChildren.apply(elems, arguments);
            }
        }
    }
    //參數分別為model, pnames, pvalues
    function setBindingsToChildren( ){
        for(var i = 0, n = this.length; i < n ; i++){
            setBindingsToElementAndChildren.apply( this[i], arguments );
        }
    }
    //通知此監控函數或數組的所有直接依賴者更新自身
    function updateDeps(Watch){
        var list = Watch.$deps || [] ;
        if( list.length ){
            var safelist = list.concat();
            for(var i = 0, el; el = safelist[i++];){
                delete el.$val;
                el()
            }
        }
    }
    //判定是否設置數據綁定的標記
    function hasBindings( node ){
        var str = node.getAttribute( BINDING );
        return typeof str === "string" && str.indexOf(":") > 1
    }
    //取得元素的所有子元素節點
    function getChildren( node ){
        var elems = [] ,ri = 0;
        for (node = node.firstChild; node; node = node.nextSibling){
            if (node.nodeType === 1){
                elems[ri++] = node;
            }
        }
        return elems;
    }
    //為監控函數添加更多必須的方法或屬性
    function addWatch( key, Watch, host ){
        //收集依賴於它的depsWatch與bindWatch,以便它的值改變時,通知它們更新自身
        Watch.toString = Watch.valueOf = function(){
            if( bridge[ expando ] ){
                $.Array.ensure( Watch.$deps, bridge[ expando ] );
            }
            return Watch.$val
        }
        if(!host.nodeType){
            Watch.$key = key;
            host[ key ] = Watch;
        }
        Watch.$deps = [];
        Watch();
        return Watch;
    }
    //============================================================
    // 將bindings變成一個對象或一個數組 by 司徒正美
    //============================================================
    function normalizeJSON(json, array){
        var keyValueArray = parseObjectLiteral(json),resultStrings = [],keyValueEntry;
        for (var i = 0; keyValueEntry = keyValueArray[i]; i++) {
            if (resultStrings.length > 0 && !array)
                resultStrings.push(",");
            if (keyValueEntry['key']) {
                var key = keyValueEntry['key'].trim();
                var quotedKey = ensureQuoted(key, array), val = keyValueEntry['value'].trim();
                resultStrings.push(quotedKey);
                if(!array)
                    resultStrings.push(":");
                if(val.charAt(0) == "{" && val.charAt(val.length - 1) == "}"){
                    val = normalizeJSON( val );//逐層加引號
                }
                resultStrings.push(val);
            } else if (keyValueEntry['unknown']) {
                resultStrings.push(keyValueEntry['unknown']);//基於跑到這里就是出錯了
            }
        }
        if(array){
            return resultStrings
        }
        resultStrings = resultStrings.join("");
        return "{" +resultStrings +"}";
    };
    //============================================================
    // normalizeJSON的輔助函數 by 司徒正美
    //============================================================
    var restoreCapturedTokensRegex = /\@mass_token_(\d+)\@/g;
    function restoreTokens(string, tokens) {
        var prevValue = null;
        while (string != prevValue) { //把原字符串放回占位符的位置之上
            prevValue = string;
            string = string.replace(restoreCapturedTokensRegex, function (match, tokenIndex) {
                return tokens[tokenIndex];
            });
        }
        return string;
    }
    function parseObjectLiteral(objectLiteralString) {
        var str = objectLiteralString.trim();
        if (str.length < 3)
            return [];
        if (str.charAt(0) === "{")// 去掉最開始{與最后的}
            str = str.substring(1, str.length - 1);
        // 首先用占位符把字段中的字符串與正則處理掉
        var tokens = [];
        var tokenStart = null, tokenEndChar;
        for (var position = 0; position < str.length; position++) {
            var c = str.charAt(position);//IE6字符串不支持[],開始一個個字符分析
            if (tokenStart === null) {
                switch (c) {
                    case '"':
                    case "'":
                    case "/":
                        tokenStart = position;//索引
                        tokenEndChar = c;//值
                        break;
                }//如果再次找到一個與tokenEndChar相同的字符,並且此字符前面不是轉義符
            } else if ((c == tokenEndChar) && (str.charAt(position - 1) !== "\\")) {
                var token = str.substring(tokenStart, position + 1);
                tokens.push(token);
                var replacement = "@mass_token_" + (tokens.length - 1) + "@";//對應的占位符
                str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
                position -= (token.length - replacement.length);
                tokenStart = null;
            }
        }
        // 將{},[],()等括起來的部分全部用占位符代替
        tokenEndChar = tokenStart = null;
        var tokenDepth = 0, tokenStartChar = null;
        for (position = 0; position < str.length; position++) {
            var c = str.charAt(position);
            if (tokenStart === null) {
                switch (c) {
                    case "{":
                        tokenStart = position;
                        tokenStartChar = c;
                        tokenEndChar = "}";
                        break;
                    case "(":
                        tokenStart = position;
                        tokenStartChar = c;
                        tokenEndChar = ")";
                        break;
                    case "[":
                        tokenStart = position;
                        tokenStartChar = c;
                        tokenEndChar = "]";
                        break;
                }
            }
            if (c === tokenStartChar)
                tokenDepth++;
            else if (c === tokenEndChar) {
                tokenDepth--;
                if (tokenDepth === 0) {
                    var token = str.substring(tokenStart, position + 1);
                    tokens.push(token);
                    replacement = "@mass_token_" + (tokens.length - 1) + "@";
                    str = str.substring(0, tokenStart) + replacement + str.substring(position + 1);
                    position -= (token.length - replacement.length);
                    tokenStart = null;
                }
            }
        }
        //拆解字段,還原占位符的部分
        var result = [];
        var keyValuePairs = str.split(",");
        for (var i = 0, j = keyValuePairs.length; i < j; i++) {
            var pair = keyValuePairs[i];
            var colonPos = pair.indexOf(":");
            if ((colonPos > 0) && (colonPos < pair.length - 1)) {
                var key = pair.substring(0, colonPos);
                var value = pair.substring(colonPos + 1);
                result.push({
                    'key': restoreTokens(key, tokens),
                    'value': restoreTokens(value, tokens)
                });
            } else {//到這里應該拋錯吧
                result.push({
                    'unknown': restoreTokens(pair, tokens)
                });
            }
        }
        return result;
    }
    function ensureQuoted(key, array) {
        var trimmedKey = key.trim()
        if(array){
            return trimmedKey;
        }
        switch (trimmedKey.length && trimmedKey.charAt(0)) {
            case "'":
            case '"':
                return key;
            default:
                return "'" + trimmedKey + "'";
        }
    }
})

相關鏈接:avalon v1


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM