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)

            }



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

你可能感兴趣的:(knockout)