好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

knockout.js的学习笔记3

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的对象,里面储放着各种操作,格式如下:

View Code

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调用的。

现在我们是玩玩,不用大动干戈。

View Code $.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 中就行了。

View Code 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)

}

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

 

 

标签:  knockoutjs

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于knockout.js的学习笔记3的详细内容...

  阅读:36次