mvvm雙向綁定機制的原理和代碼實現

mvvm框架的雙向綁定,即當對象改變時,自動改變相關的dom元素的值,反之,當dom元素改變時,能自動更新對象的值,固然dom元素通常是指可輸出的input元素。javascript

1. 首先實現單向綁定,在指定對象的屬性值發生改變時觸發callback函數。
2. 單向綁定可採用ES5新增的defineProperty實現(或defineProperties),用了ES5註定就不支持IE9如下了,爲了防止遞歸死循環問題,原有屬性須要剪切到一個私有屬性中保存。
3. 循環調用defineProperty定義閉包時產生做用域的問題,爲解決做用域變量對象的值會取到最後一次運行值問題,多定義一層當即調用的閉包函數將值傳入。
4. 咱們定義getFN和setFN函數用於在屬性get和set的時候觸發,它的功能是對私有屬性__private的讀寫並觸發回調函數通知UI層更新界面。
5.單向綁定實現完成後,實現反向的綁定,即UI層onchange以後觸發更新數據,這個相對比較容易,在dom中經過自定義屬性bindKey關聯model的值變化,監聽使用oninput事件,相比onchange的好處是能夠實時變化不用等失焦,並且對右鍵粘貼、菜單粘貼,拖動文字進文本框等方式均可以觸發,徹底無死角,缺點是隻支持IE9以上,可是在IE9如下有等價的onpropertychange能夠用仍是能兼容的。
6.總結,雙向綁定的原理並不複雜,總體代碼不超過50行,很是精簡,不過仍是有一些技術含量,下面是完整的代碼,若是不想使用龐大的框架,能夠用一下。ie9如下是不支持的,如要支持ie9如下可使用avalon,它用vbs作了get,set存取器的封裝,這點仍是比較強大的。css

html:html

<div id="container">
   <p>
   name:<input type="text" bindkey="userName">
   </p>
   <p>
   age:<input type="text" bindkey="age">
   </p>
<div>

js:java

 <script type="text/javascript">
    window.Model={
        userName:"windy",
        age:34,
        skill:["javascript","html","css","jquery","node"],
        
    }
    function bindingModel(model,changeCallback){
        var propertiesMap={};
        model.__private={};
        function getFn(name){
            var result=this.__private[name]
            console.log("get value:"+name+"="+ result);
            return result;
        };
        function setFn(name,val){
            if(this.__private[name]!=val){
                console.log("set value:"+name+"="+val);
             
                this.__private[name]=val;

                if(changeCallback){
                    changeCallback(name,val);
                }
            }
        };
        for(elem in model){
            if(model.hasOwnProperty(elem) && elem!="__private" && typeof(model[elem])!="function"){
                (function(propName,propValue){
                    model.__private[propName]=propValue;// init value
                    propertiesMap[propName]={
                        get:function(){ return getFn.call(this,propName)},
                        set:function(v){ return setFn.call(this,propName,v)},
                        //value:model[elem],
                        //writable: true,
                        enumerable: true,
                        configurable: true
                    }
                })(elem,model[elem]);
            }
        }
        Object.defineProperties(model,propertiesMap)
        
    }
    function bindingBoth(model,dom){
        dom.find("[bindkey]").each(function(item){
            var key=$(this).attr("bindkey");
            $(this).val(model[key]);
            $(this).bind("input",function(){
               model[key]=$(this).val();
            })
        });
        bindingModel(model,function(name,val){
            var el=dom.find("[bindkey="+name+"]");
            if(el.val()!=val){
                el.val(val);
            }
            
        });
    }
    bindingBoth(window.Model,$("#container"))
    </script>
相關文章
相關標籤/搜索