模仿,anjularjs 雙向綁定 ,純javascript實現

    anjularjs中的雙向綁定很有意思,下面模仿其源碼,寫了一個簡單的版本,就是魯棒性差了點。

wKiom1RkoE6CEn6rAAA6mIc19S0230.jpg

    從"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);
        };
      },






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章