anjularjs中的双向绑定很有意思,下面模仿其源码,写了一个简单的版本,就是鲁棒性差了点。

模仿,anjularjs 双向绑定 ,纯javascript实现_ 双向绑定

    从"wo zi ji de"中可以看到,当输入框的内容变化时,其上2排内容也在动态变化。有点小兴奋啊。其中x变量用angularjs的双向绑定,y变量用的自己的(zi ji de)。

  HTML:

	<p>angularjs</p>					
	<p ng-bind='x'></p>					
	<input type='text' ng-model='x'; class='form-control' />
	
	
	
	<p>wo zi ji de</p>				
	<p by-bind='y'></p>					
	<p by-bind='y'></p>				
	<input type='text' by-model='y'; class='form-control' />





    实现原理:

    1.$watch函数的2个参数,第一个是要监听的变量,第二个是变量改变时运行的函数。这2个存入JSON格式的watcher中,watchFn:该函数返回当前时刻的值,可能变了,可能没变。如下所示,last代表上次的值,初始为1.

var watcher = {
					watchFn:function(watchValue,self){
										//return self.data.get(watchValue);
										return self.result2[0].value;
									},
					listenerFn:listenerFunction,
					last:1
				};

  2.由于不止监听一个变量,所以会有很多个watcher,所有watcher存入$$watcher中。两个美元代表私有变量,anjularjs中是代表$scope的私有变量,不让用户接触的。

this.$$watchers.push(watcher);

  3.有了要监听的变量,下一步就是实现监听了。就是$digest对象的职责了。$digest对$$watcher中的每一个watcher进行排查,看其当前值与last值是否不同,不同当然就说明被监听的值已经变了。那么对应使用ng-bind(by-bind)显示的值,也应该更新。这就是watcher中的listenerFn函数要干的事了。


 4.说明:ByScope对象来模仿$scope对象。$scope.x 对应html中<p ng-bind='x'/>和<input ng-model='x'/>。而ByScope模仿时(<p by-bind='y'/><input by-model='y'/>),是使用了一个map键值数组,'y'是key,key对应的值就相当于$scope.x;

       $scope.x 只需一句话,不用自己初始$scope变量,这些工作我都没做-_-!。你没听错,所以要自己建立ByScope对象,并将y值传入。  为什么呢?

   var bysowhat = new ByScope("y");

    anjularjs源码中,使用下面代码中的  get = compileToFn(watchExp, 'watch')。compileToFn可以把按照字符串的值转为对应的变量和函数,只有脚本语言可以办到哦。这个函数的源码我是编不完的。

$watch: function(watchExp, listener, objectEquality) {
        var scope = this,
            get = compileToFn(watchExp, 'watch'),
            array = scope.$$watchers,
            watcher = {
              fn: listener,
              last: initWatchVal,
              get: get,
              exp: watchExp,
              eq: !!objectEquality
            };



  

    JAVASCRIPT:

	function ListControler($scope){
			$scope.x = 100;
		
		}	
		
		(function($){
		
			
			
			
			
			//深度优先遍历DOM树,找by-bin='y'的节点
			//node:节点
			//attri:"by-bind"
			//返回值:目标结点数组
			function bianli2(node,attri,value,resultNode){
				var allNodes = node.childNodes;
				if(allNodes != undefined){
					//有子节点,继续递归
					for(var i=0;i<allNodes.length;i++){
						if(allNodes[i].nodeType == 1 && allNodes[i].nodeName!="SCRIPT"){
							//所有元素节点,除script
							//查找自定义属性
							var attrStr = allNodes[i].getAttribute(attri);
							//找到时,将节点传给resultNode
							if(attrStr == value)
								resultNode.push(allNodes[i]);
							//alert(allNodes[i].nodeName);
							bianli(allNodes[i]);
						
						}
					}
				}
			}
			
			
	
			
			
			//实现$watch	
			function ByScope(watchValueName){
				//存储所有watch
				this.$$watchers = [];
				this.data = new Map();
				//this.data.add("y",123);
				this.result1=[];
				this.result2=[];
				//var self = this;
				bianli2(document.body,"by-bind",watchValueName,this.result1);
				bianli2(document.body,"by-model",watchValueName,this.result2);
				//alert(this.data.get("y"));
			
			}
			
		
			
			
			//监听器存入$$watchers
			ByScope.prototype.$watch = function(watchValue, listenerFunction){
				var self = this;
				var watcher = {
					watchFn:function(watchValue,self){
										//return self.data.get(watchValue);
										return self.result2[0].value;
									},
					listenerFn:listenerFunction,
					last:1
				};
				this.$$watchers.push(watcher);
				//每0.1秒运行一次$digest
				setInterval(function(){self.$digest(watchValue)},100);
				//this.$digest(watchValue);
				
			};
			
			//对比监听器内容:$digest
			ByScope.prototype.$digestOnce = function(watchValue){
				var self = this;
				var dirty;
				for(var i=0;i<this.$$watchers.length;i++){
					var newValue = this.$$watchers[i].watchFn(watchValue,self);
					var oldValue = this.$$watchers[i].last;
					//被监视的值发生变化
					if(newValue != oldValue){
						this.$$watchers[i].listenerFn();
						dirty = true;
					}
					//更新监视值
					this.$$watchers[i].last = newValue;
				}
				return dirty;
			};

			
			ByScope.prototype.$digest = function(watchValue){
				var dirty;
				do{
					dirty = this.$digestOnce(watchValue);
				}while(dirty);
			};
			

			
			
			
			var bysowhat = new ByScope("y");
			
			

			
			
			bysowhat.$watch('y',function(){
				for(var i=0;i<bysowhat.result1.length;i++){
					bysowhat.result1[i].innerHTML = bysowhat.result2[0].value;
				}
			});
			
			
			
			
			
		
		
		
		})(jQuery);



    我的代码看完了,再来看看源码逐行解析

1.

 get = compileToFn(watchExp, 'watch'),

由于watchExp是通过字符串传递的,所以要把字符串转为变量或者函数(函数也是变量)。就像setInterval('myfunction',100)中的函数名字符串'myfunction'。


2.

 array = scope.$$watchers,
            watcher = {
              fn: listener,
              last: initWatchVal,
              get: get,
              exp: watchExp,
              eq: !!objectEquality
            };

watcher对象存入监听过程中的基本信息: exp:要监听的变量;fn:发现变量数值变化时所执行的函数;last:变量上一次的值;eq:深度监听,get:如前所述。


3.

  // in the case user pass string, we need to compile it, do we really need this ?
        if (!isFunction(listener)) {
          var listenFn = compileToFn(listener || noop, 'listener');
          watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
        }

如果通过$scope.$watch传递的第二个变量不是函数,是字符串,那么就把字符串转为函数,话说谁会传字符串啊_-! 你会放弃火车票,而拿火车票的相片给检票员看吗?这也是为什么源码注释成"do we really need this?"


4.

 if (typeof watchExp == 'string' && get.constant) {
          var originalFn = watcher.fn;
          watcher.fn = function(newVal, oldVal, scope) {
            originalFn.call(this, newVal, oldVal, scope);
            arrayRemove(array, watcher);
          };
        }

    为啥呢



5.

 if (!array) {
          array = scope.$$watchers = [];
        }
        // we use unshift since we use a while loop in $digest for speed.
        // the while loop reads in reverse order.
        array.unshift(watcher);

    源码中,第一个watcher加入时,还没有array存在,所以这时要创建array。

            unshift代表在数组头插入数据,相当于把数组当栈用。为了后面$digest的循环速度。



6.

 return function() {
          arrayRemove(array, watcher);
        };

这构成了闭包,目的是让下面这行

arrayRemove(array, watcher);

在想被执行的时候再执行。




       */
      $watch: function(watchExp, listener, objectEquality) {
        var scope = this,
            get = compileToFn(watchExp, 'watch'),
            array = scope.$$watchers,
            watcher = {
              fn: listener,
              last: initWatchVal,
              get: get,
              exp: watchExp,
              eq: !!objectEquality
            };

        // in the case user pass string, we need to compile it, do we really need this ?
        if (!isFunction(listener)) {
          var listenFn = compileToFn(listener || noop, 'listener');
          watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);};
        }

        if (typeof watchExp == 'string' && get.constant) {
          var originalFn = watcher.fn;
          watcher.fn = function(newVal, oldVal, scope) {
            originalFn.call(this, newVal, oldVal, scope);
            arrayRemove(array, watcher);
          };
        }

        if (!array) {
          array = scope.$$watchers = [];
        }
        // we use unshift since we use a while loop in $digest for speed.
        // the while loop reads in reverse order.
        array.unshift(watcher);

        return function() {
          arrayRemove(array, watcher);
        };
      },