knockout.js的學習筆記3


上一節主要是說viewModel各個域中相互通知,本節開始介紹viewModel與節點的相互通知。

我們在body上添加如下HTML片斷:

  The name is <span data-bind="text: fullName" id="node"></span>

然后將第一節提到的$.applyBindings瘋狂刪減到這樣:

             $.applyBindings = function(model, node){
                var str = node.getAttribute("data-bind");
                str = "{"+str+"}"
                var bindings = eval("0,"+str);
                for(var key in bindings){//如果直接eval肯定會報錯,因為它找到fullName
                    console.log(key)
                }
            }
            window.onload = function(){
                var model = new MyViewModel();
                var node = document.getElementById("node");
                $.applyBindings(model, node)
            }

意料中的失敗,因為fullName在window中找不到。knockoutjs里面有一個叫buildEvalWithinScopeFunction處理此問題:

       $.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 改成這樣:

            $.applyBindings = function(model, node){
                var str = node.getAttribute("data-bind");
                str = "{"+str+"}"
                var fn = $.buildEvalWithinScopeFunction(str,2);
                var bindings = fn([node,model])
                console.log(bindings.text == model.fullName)//到這里我們就把viewModel與節點關聯起來了
            }

在data-bind定義兩個東西,一個是viewModel中的域,另一個是對應的操作,在這里是text!在knockout中有一個叫ko.bindingHandlers的對象,里面儲放着各種操作,格式如下:

ko.bindingHandlers['event'] = {
    'init' : function (element, valueAccessor, allBindingsAccessor, viewModel) { }
};

ko.bindingHandlers['submit'] = {
    'init': function (element, valueAccessor, allBindingsAccessor, viewModel) { }
};

ko.bindingHandlers['visible'] = {
    'update': function (element, valueAccessor) { }
}

ko.bindingHandlers['enable'] = {
    'update': function (element, valueAccessor) { }
};

ko.bindingHandlers['disable'] = {
    'update': function (element, valueAccessor) { }
};

ko.bindingHandlers['value'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) { },
    'update': function (element, valueAccessor) { }
};

ko.bindingHandlers['options'] = {
    'update': function (element, valueAccessor, allBindingsAccessor) { }
};

ko.bindingHandlers['selectedOptions'] = {
    'init': function (element, valueAccessor, allBindingsAccessor) { },
    'update': function (element, valueAccessor) { }
};

ko.bindingHandlers['text'] = {
    'update': function (element, valueAccessor) {
        ko.utils.setTextContent(element, valueAccessor());
    }
};

ko.bindingHandlers['html'] = {
    'init': function() {
        return { 'controlsDescendantBindings': true };
    },
    'update': function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
            ko.utils.setHtml(element, value);
    }
};

init可以猜測是用於第一次綁定元素時調用的,update是每次viewModel調用的。

現在我們是玩玩,不用大動干戈。

          $.applyBindings = function(model, node){
                var str = node.getAttribute("data-bind");
                str = "{"+str+"}"
                var fn = $.buildEvalWithinScopeFunction(str,2);
                var bindings = fn([node,model]);
                for(var key in bindings){
                    if(bindings.hasOwnProperty(key)){
                        var fn = $.bindingHandlers["text"]["update"];
                        fn(node,bindings[key])
                    }
                }
            }
            $.bindingHandlers = {}
            $.bindingHandlers["text"] = {
                'update': function (node, observable) {
                    var val = observable()
                    val = val == null ? "" : val+"";
                    if("textContent" in node){//優先考慮標准屬性textContent
                        node.textContent = val;
                    }else{
                        node.innerText = val;
                    }
                    //處理IE9的渲染BUG
                    if (document.documentMode == 9) {
                        node.style.display = node.style.display;
                    }

                }
            }
            window.onload = function(){
                var model = new MyViewModel();
                var node = document.getElementById("node");
                $.applyBindings(model, node);
            }

到這里,我們就可以把Planet Earth正確地顯示在span中,但當viewModel中的FullName發生改變時,span並沒有發生改變,緣由是我們沒有把它們綁在一起。很簡單,我們把$.applyBindings里面的邏輯都整進一個$.computed 中就行了。


            var validValueType = $.oneObject("Null,NaN,Undefined,Boolean,Number,String")
            $.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();
                    }
                }
            }
            $.observable = function(value){
                var v = value;//將上一次的傳參保存到v中,ret與它構成閉包
                function ret(neo){
                    if(arguments.length){ //setter
                        if(!validValueType[$.type(neo)]){
                            $.error("arguments must be primitive type!")
                            return ret
                        }
                        if(v !== neo ){
                            v = neo;
                            $.valueWillMutate(ret);//向依賴者發送通知
                        }
                        return ret;
                    }else{                //getter
                        $.dependencyDetection.collect(ret);//收集被依賴者
                        return v;
                    }
                }
                value = validValueType[$.type(value)] ? value : void 0;
                ret(arguments[0]);//必須先執行一次
                return ret
            }

            $.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(!validValueType[$.type(neo)]){
                                $.error("arguments must be primitive type!")
                                return ret
                            }
                            if(v !== neo ){
                                setter.call(scope, neo);
                                v = neo;
                                $.valueWillMutate(ret);//向依賴者發送通知
                            }
                        }
                        return ret;
                    }else{
                        $.dependencyDetection.begin(ret);//讓其依賴知道自己的存在
                        v = getter.call(scope);
                        $.dependencyDetection.end();
                        return v;
                    }
                }
                ret(); //必須先執行一次
                return ret;
            }
            function MyViewModel() {
                this.firstName = $.observable('Planet');
                this.lastName = $.observable('Earth');
                this.fullName = $.computed({
                    getter: function () {
                        return this.firstName() + " " + this.lastName();
                    },
                    setter: function (value) {
                        var lastSpacePos = value.lastIndexOf(" ");
                        if (lastSpacePos > 0) { // Ignore values with no space character
                            this.firstName(value.substring(0, lastSpacePos)); // Update "firstName"
                            this.lastName(value.substring(lastSpacePos + 1)); // Update "lastName"
                        }
                    },
                    scope: this
                });
            }
            $.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()
                    val = val == null ? "" : val+"";
                    if("textContent" in node){//優先考慮標准屬性textContent
                        node.textContent = val;
                    }else{
                        node.innerText = val;
                    }
                    //處理IE9的渲染BUG
                    if (document.documentMode == 9) {
                        node.style.display = node.style.display;
                    }

                }
            }
            window.onload = function(){
                var model = new MyViewModel();
                var node = document.getElementById("node");
                var nodeBind = $.applyBindings(model, node);
                $.log("+++++++++++++++++++++++++++")
                $.log(model.fullName.list[0] == nodeBind);
                $.log(model.lastName.list[0] == model.fullName);
                $.log(model.firstName.list[0] == model.fullName);
                //  $.log(model.lastName.list[0] == model.fullName)
                setTimeout(function(){
                    model.fullName("xxx yyy")
                },1500)
                setTimeout(function(){
                    model.fullName("111 222")
                },3000)
            }

大家可以下載回來看看效果:點我


免責聲明!

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



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