Ruby's Louvre

每天学习一点点算法

导航

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)
            }

大家可以下载回来看看效果:点我

posted on 2012-06-19 10:27  司徒正美  阅读(3457)  评论(4编辑  收藏  举报