Knockout雙向綁定


knockout雙工綁定基於 observe 模式,性能高。核心就是observable對象的定義。這個函數最后返回了一個也叫做 observable 的函數,也就是用戶定義值的讀寫器(accessor)。

this.firstName=ko.observable(“Bert”);
this.firstName();
this.firstName(“test”);

ko.observable做了什么

ko.observable = function (initialValue) {
    var _latestValue = initialValue; //保留上一次的參數,與observable形成閉包
     
    function observable() {
        if (arguments.length > 0) {
            // Write,Ignore writes if the value hasn't changed
            if (observable.isDifferent(_latestValue, arguments[0])) {
                observable.valueWillMutate();
                _latestValue = arguments[0];
                if (DEBUG) observable._latestValue = _latestValue;
                observable.valueHasMutated();
            }
     
            return this; // Permits chained assignments
        }
        else {
            // Read
            ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
            return _latestValue;
        }
    }
    ko.subscribable.call(observable);
    ko.utils.setPrototypeOfOrExtend(observable, ko.observable['fn']);
     
    if (DEBUG) observable._latestValue = _latestValue;
    /**這里省略了專為 closure compiler 寫的語句**/

    return observable;
     
}

通過 ko.subscribable.call(observable); 使這個函數有了被訂閱的功能,讓 firstName 在改變時能通知所有訂閱了它的對象。其實就是維護了一個回調函數的隊列,當自己的值改變時,就執行這些回調函數。根據上面的代碼,回調函數是在 observable.valueHasMutated(); 執行的。

ko.computed做了什么

this.fullName = ko.computed(function() {
        return this.firstName() + " " + this.lastName();    
    }, this);
$.computed = function(obj, scope){
    //computed是由多個$.observable組成
    var getter, setter
    if(typeof obj == "function"){
        getter = obj
    }else if(obj && typeof obj == "object"){
        getter = obj.getter;
        setter = obj.setter;
        scope  = obj.scope;
    }
    var v
    var ret = function(neo){
        if(arguments.length ){
            if(typeof setter == "function"){//setter不一定存在的
                if(v !== neo ){
                    setter.call(scope, neo);
                    v = neo;
                }
            }
            return ret;
        }else{
            v = getter.call(scope);
            return v;
        }
    }
    return ret;
}
$.dependencyDetection = (function () {
    var _frames = [];
    return {
        begin: function (ret) {
            _frames.push(ret);
        },
        end: function () {
            _frames.pop();
        },
        collect: function (self) {
            if (_frames.length > 0) {
                self.list = self.list || [];
                var fn = _frames[_frames.length - 1];
                if ( self.list.indexOf( fn ) >= 0)
                    return;
                self.list.push(fn);
            }
        }
    };
})();
$.valueWillMutate = function(observable){
    var list = observable.list
    if($.type(list,"Array")){
        for(var i = 0, el; el = list[i++];){
            el();
        }
    }
}

雙向綁定如何實現

$.buildEvalWithinScopeFunction =  function (expression, scopeLevels) {
    var functionBody = "return (" + expression + ")";
    for (var i = 0; i < scopeLevels; i++) {
        functionBody = "with(sc[" + i + "]) { " + functionBody + " } ";
    }
    return new Function("sc", functionBody);
}
$.applyBindings = function(model, node){       
   
    var nodeBind = $.computed(function (){
        var str = "{" + node.getAttribute("data-bind")+"}"
        var fn = $.buildEvalWithinScopeFunction(str,2);
        var bindings = fn([node,model]);
        for(var key in bindings){
            if(bindings.hasOwnProperty(key)){
                var fn = $.bindingHandlers["text"]["update"];
                var observable = bindings[key]
                $.dependencyDetection.collect(observable);//綁定viewModel與UI
                fn(node, observable)
            }
        }
    },node);
    return nodeBind
     
}
$.bindingHandlers = {}
$.bindingHandlers["text"] = {
    'update': function (node, observable) {
        var val = observable()
        if("textContent" in node){
            node.textContent = val;
        }
    }
}
window.onload = function(){
    var model = new MyViewModel();
    var node = document.getElementById("node");
    $.applyBindings(model, node);
}

KO使用

1、ko綁定方式,立即執行用於需要后處理的一些數值

//點擊事件
data-bind="click:$root.fun1.bind($param1,param2)"
//立即執行
data-bind="attr: { src : $root.fun2(param1,param2) }”
//缺省參數
data-bind="event: { mouseover: myFunction }"
<script type="text/javascript">  
    var viewModel = {  
        myFunction: function(data, event) {  
            if (event.shiftKey) {  
                //do something different when user has shift key down  
            } else {  
                //do normal action  
            }  
        }  
    };  
    ko.applyBindings(viewModel);  
</script>

注意:在bind方式傳遞參數時,data和event兩個參數依然被缺省傳遞。 新加入的參數,在使用時排在第一位,定義時只能排在$data后面

2、event事件

<input type="text" placeholder="輸入關鍵字搜索" data-bind="event:{keyup:$root.fun1.bind($data,$element)}">

完整的 key press 過程分為兩個部分,按鍵被按下,然后按鍵被松開並復位。
當按鈕被松開時,發生 keyup 事件。它發生在當前獲得焦點的元素上。
keydown事件發生在鍵盤的鍵被按下的時候,接下來觸發 keypress事件。  keyup 事件在按鍵被釋放的時候觸發。
KeyPress 只能捕獲單個字符;KeyDown 和KeyUp 可以捕獲組合鍵。 

3、

self.weeklyRecommend(this);  //監控對象整體發生變化時響應
self.weeklyRecommend(ko.mapping.fromJs(this));  //可以監控對象下每個元素的改變

4、ko事件注冊

ko.bindingHandlers.singleExamHover = {
    init: function(element, valueAccessor){
        $(element).hover(
            function(){
                //todo 
            },
            function(){
                //todo
            }
        );
    },
    update:function(element, valueAccessor){
        var _value = ko.unwrap(valueAccessor());
        if(_value){
            $(element).addClass("current");
        }else{
            $(element).removeClass("current");
        }
    }
};
<div class="h-set-homework" data-bind="singleExamHover:question.checked”>

5、事件冒泡

By default, Knockout will allow the click event to continue to bubble up to any higher level event handlers。

If necessary, you can prevent the event from bubbling by including an additional binding that is named clickBubble and passing false to it 

<div data-bind="click: myDivHandler">
    <button data-bind="click: myButtonHandler, clickBubble: false">
        Click me
    </button>
</div>

Normally, in this case myButtonHandler would be called first, then the click event would bubble up to myDivHandler. However, the clickBubble binding that we added with a value of false prevents the event from making it past myButtonHandler

6、$data

This is the view model object in the current context. In the root context,  $data and  $root are equivalent. Inside a nested binding context, this parameter will be set to the current data item (e.g., inside a  with: person binding,  $data will be set to  person).  $data is useful when you want to reference the viewmodel itself, rather than a property on the viewmodel.
<div data-bind="click:changeEditor.bind($data,$element,param1,param2)"></div>
<script>
changeEditor : function(ele,param1,param2){
     console.log(this)
     console.log(ele==event.currenttarget)
}
</script>

 


免責聲明!

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



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