欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

實(shí)例剖析AngularJS框架中數(shù)據(jù)的雙向綁定運(yùn)用

 更新時(shí)間:2016年03月04日 14:51:34   作者:張小俊128  
這篇文章主要介紹了AngularJS框架中數(shù)據(jù)的雙向綁定運(yùn)用實(shí)例,包括數(shù)據(jù)綁定中的關(guān)鍵函數(shù)與監(jiān)聽器觸發(fā)的相關(guān)講解,需要的朋友可以參考下

數(shù)據(jù)綁定

通過把一個(gè)文本輸入框綁定到person.name屬性上,就能把我們的應(yīng)用變得更有趣一點(diǎn)。這一步建立起了文本輸入框跟頁(yè)面的雙向綁定。

201634144020971.png (184×293)

在這個(gè)語(yǔ)境里“雙向”意味著如果view改變了屬性值,model就會(huì)“看到”這個(gè)改變,而如果model改變了屬性值,view也同樣會(huì)“看到”這個(gè)改變。Angular.js 為你自動(dòng)搭建好了這個(gè)機(jī)制。如果你好奇這具體是怎么實(shí)現(xiàn)的,請(qǐng)看我們之后推出的一篇文章,其中深入討論了digest_loop 的運(yùn)作。

要建立這個(gè)綁定,我們?cè)谖谋据斎肟蛏鲜褂胣g-model 指令屬性,像這樣:

<div ng-controller="MyController">
 <input type="text" ng-model="person.name" placeholder="Enter your name" />
 <h5>Hello {{ person.name }}</h5>
</div>

現(xiàn)在我們建立好了一個(gè)數(shù)據(jù)綁定(沒錯(cuò),就這么容易),來看看view怎么改變model吧:

試試看:

201634144103466.jpg (321×88)

當(dāng)你在文本框里輸入時(shí),下面的名字也自動(dòng)隨之改變,這就展現(xiàn)了我們數(shù)據(jù)綁定的一個(gè)方向:從view到model。

我們也可以在我們的(客戶端)后臺(tái)改變model,看這個(gè)改變自動(dòng)在前端體現(xiàn)出來。要展示這一過程,讓我們?cè)?nbsp; MyController 的model里寫一個(gè)計(jì)時(shí)器函數(shù), 更新 $scope 上的一個(gè)數(shù)據(jù)。下面的代碼里,我們就來創(chuàng)建這個(gè)計(jì)時(shí)器函數(shù),它會(huì)在每秒計(jì)時(shí)(像鐘表那樣),并更新 $scope 上的clock變量數(shù)據(jù):

app.controller('MyController', function($scope) {
 $scope.person = { name: "Ari Lerner" };
 var updateClock = function() {
  $scope.clock = new Date();
 };
 var timer = setInterval(function() {
  $scope.$apply(updateClock);
 }, 1000);
 updateClock();
});

可以看到,當(dāng)我們改變model中clock變量的數(shù)據(jù),view會(huì)自動(dòng)更新來反映此變化。用大括號(hào)我們就可以很簡(jiǎn)單地讓clock變量的值顯示在view里:

<div ng-controller="MyController">
 <h5>{{ clock }}</h5>
</div>


互動(dòng)

前面我們把數(shù)據(jù)綁定在了文本輸入框上。請(qǐng)注意, 數(shù)據(jù)綁定并非只限于數(shù)據(jù),我們還可以利用綁定調(diào)用 $scope 中的函數(shù)(這一點(diǎn)之前已經(jīng)提到過)。

對(duì)按鈕、鏈接或任何其他的DOM元素,我們都可以用另一個(gè)指令屬性來實(shí)現(xiàn)綁定:ng-click 。這個(gè) ng-click 指令將DOM元素的鼠標(biāo)點(diǎn)擊事件(即 mousedown 瀏覽器事件)綁定到一個(gè)方法上,當(dāng)瀏覽器在該DOM元素上鼠標(biāo)觸發(fā)點(diǎn)擊事件時(shí),此被綁定的方法就被調(diào)用。跟上一個(gè)例子相似,這個(gè)綁定的代碼如下:

<div ng-controller="DemoController">
 <h4>The simplest adding machine ever</h4>
 <button ng-click="add(1)" class="button">Add</button>
 <button ng-click="subtract(1)" class="button">Subtract</button>
 <h4>Current count: {{ counter }}</h4>
</div>

不論是按鈕還是鏈接都會(huì)被綁定到包含它們的DOM元素的controller所有的 $scope 對(duì)象上,當(dāng)它們被鼠標(biāo)點(diǎn)擊,Angular就會(huì)調(diào)用相應(yīng)的方法。注意當(dāng)我們告訴Angular要調(diào)用什么方法時(shí),我們將方法名寫進(jìn)帶引號(hào)的字符串里。

app.controller('DemoController', function($scope) {
 $scope.counter = 0;
 $scope.add = function(amount) { $scope.counter += amount; };
 $scope.subtract = function(amount) { $scope.counter -= amount; };
});

 請(qǐng)看:

201634144205361.jpg (448×176)

$scope.$watch

$scope.$watch( watchExp, listener, objectEquality );

為了監(jiān)視一個(gè)變量的變化,你可以使用$scope.$watch函數(shù)。這個(gè)函數(shù)有三個(gè)參數(shù),它指明了”要觀察什么”(watchExp),”在變化時(shí)要發(fā)生什么”(listener),以及你要監(jiān)視的是一個(gè)變量還是一個(gè)對(duì)象。當(dāng)我們?cè)跈z查一個(gè)參數(shù)時(shí),我們可以忽略第三個(gè)參數(shù)。例如下面的例子:

$scope.name = 'Ryan';

$scope.$watch( function( ) {
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log('$scope.name was updated!');
} );

AngularJS將會(huì)在$scope中注冊(cè)你的監(jiān)視函數(shù)。你可以在控制臺(tái)中輸出$scope來查看$scope中的注冊(cè)項(xiàng)目。

你可以在控制臺(tái)中看到$scope.name已經(jīng)發(fā)生了變化 – 這是因?yàn)?scope.name之前的值似乎undefined而現(xiàn)在我們將它賦值為Ryan!

對(duì)于$wach的第一個(gè)參數(shù),你也可以使用一個(gè)字符串。這和提供一個(gè)函數(shù)完全一樣。在AngularJS的源代碼中可以看到,如果你使用了一個(gè)字符串,將會(huì)運(yùn)行下面的代碼:

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

這將會(huì)把我們的watchExp設(shè)置為一個(gè)函數(shù),它也自動(dòng)返回作用域中我們已經(jīng)制定了名字的變量。

$$watchers
$scope中的$$watchers變量保存著我們定義的所有的監(jiān)視器。如果你在控制臺(tái)中查看$$watchers,你會(huì)發(fā)現(xiàn)它是一個(gè)對(duì)象數(shù)組。

$$watchers = [
  {
    eq: false, // 表明我們是否需要檢查對(duì)象級(jí)別的相等
    fn: function( newValue, oldValue ) {}, // 這是我們提供的監(jiān)聽器函數(shù)
    last: 'Ryan', // 變量的最新值
    exp: function(){}, // 我們提供的watchExp函數(shù)
    get: function(){} // Angular's編譯后的watchExp函數(shù)
  }
];

$watch函數(shù)將會(huì)返回一個(gè)deregisterWatch函數(shù)。這意味著如果我們使用$scope.$watch對(duì)一個(gè)變量進(jìn)行監(jiān)視,我們也可以在以后通過調(diào)用某個(gè)函數(shù)來停止監(jiān)視。

$scope.$apply
當(dāng)一個(gè)控制器/指令/等等東西在AngularJS中運(yùn)行時(shí),AngularJS內(nèi)部會(huì)運(yùn)行一個(gè)叫做$scope.$apply的函數(shù)。這個(gè)$apply函數(shù)會(huì)接收一個(gè)函數(shù)作為參數(shù)并運(yùn)行它,在這之后才會(huì)在rootScope上運(yùn)行$digest函數(shù)。

AngularJS的$apply函數(shù)代碼如下所示:

$apply: function(expr) {
  try {
   beginPhase('$apply');
   return this.$eval(expr);
  } catch (e) {
   $exceptionHandler(e);
  } finally {
   clearPhase();
   try {
    $rootScope.$digest();
   } catch (e) {
    $exceptionHandler(e);
    throw e;
   }
  }
}

上面代碼中的expr參數(shù)就是你在調(diào)用$scope.$apply()時(shí)傳遞的參數(shù) – 但是大多數(shù)時(shí)候你可能都不會(huì)去使用$apply這個(gè)函數(shù),要用的時(shí)候記得給它傳遞一個(gè)參數(shù)。

下面我們來看看ng-keydown是怎么來使用$scope.$apply的。為了注冊(cè)這個(gè)指令,AngularJS會(huì)使用下面的代碼。

var ngEventDirectives = {};
forEach(
 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
 function(name) {
  var directiveName = directiveNormalize('ng-' + name);
  ngEventDirectives[directiveName] = ['$parse', function($parse) {
   return {
    compile: function($element, attr) {
     var fn = $parse(attr[directiveName]);
     return function ngEventHandler(scope, element) {
      element.on(lowercase(name), function(event) {
       scope.$apply(function() {
        fn(scope, {$event:event});
       });
      });
     };
    }
   };
  }];
 }
);

上面的代碼做的事情是循環(huán)了不同的類型的事件,這些事件在之后可能會(huì)被觸發(fā)并創(chuàng)建一個(gè)叫做ng-[某個(gè)事件]的新指令。在指令的compile函數(shù)中,它在元素上注冊(cè)了一個(gè)事件處理器,它和指令的名字一一對(duì)應(yīng)。當(dāng)事件被出發(fā)時(shí),AngularJS就會(huì)運(yùn)行scope.$apply函數(shù),并讓它運(yùn)行一個(gè)函數(shù)。

只是單向數(shù)據(jù)綁定嗎?
上面所說的ng-keydown只能夠改變和元素值相關(guān)聯(lián)的$scope中的值 – 這只是單項(xiàng)數(shù)據(jù)綁定。這也是這個(gè)指令叫做ng-keydown的原因,只有在keydown事件被觸發(fā)時(shí),能夠給與我們一個(gè)新值。

但是我們想要的是雙向數(shù)據(jù)綁定!
我們現(xiàn)在來看一看ng-model。當(dāng)你在使用ng-model時(shí),你可以使用雙向數(shù)據(jù)綁定 – 這正是我們想要的。AngularJS使用$scope.$watch(視圖到模型)以及$scope.$apply(模型到視圖)來實(shí)現(xiàn)這個(gè)功能。

ng-model會(huì)把事件處理指令(例如keydown)綁定到我們運(yùn)用的輸入元素上 – 這就是$scope.$apply被調(diào)用的地方!而$scope.$watch是在指令的控制器中被調(diào)用的。你可以在下面代碼中看到這一點(diǎn):

$scope.$watch(function ngModelWatch() {
  var value = ngModelGet($scope);

  //如果作用域模型值和ngModel值沒有同步
  if (ctrl.$modelValue !== value) {

    var formatters = ctrl.$formatters,
      idx = formatters.length;

    ctrl.$modelValue = value;
    while(idx--) {
      value = formatters[idx](value);
    }

    if (ctrl.$viewValue !== value) {
      ctrl.$viewValue = value;
      ctrl.$render();
    }
  }

  return value;
});

如果你在調(diào)用$scope.$watch時(shí)只為它傳遞了一個(gè)參數(shù),無論作用域中的什么東西發(fā)生了變化,這個(gè)函數(shù)都會(huì)被調(diào)用。在ng-model中,這個(gè)函數(shù)被用來檢查模型和視圖有沒有同步,如果沒有同步,它將會(huì)使用新值來更新模型數(shù)據(jù)。這個(gè)函數(shù)會(huì)返回一個(gè)新值,當(dāng)它在$digest函數(shù)中運(yùn)行時(shí),我們就會(huì)知道這個(gè)值是什么!

為什么我們的監(jiān)聽器沒有被觸發(fā)?
如果我們?cè)?scope.$watch的監(jiān)聽器函數(shù)中停止這個(gè)監(jiān)聽,即使我們更新了$scope.name,該監(jiān)聽器也不會(huì)被觸發(fā)。

正如前面所提到的,AngularJS將會(huì)在每一個(gè)指令的控制器函數(shù)中運(yùn)行$scope.$apply。如果我們查看$scope.$apply函數(shù)的代碼,我們會(huì)發(fā)現(xiàn)它只會(huì)在控制器函數(shù)已經(jīng)開始被調(diào)用之后才會(huì)運(yùn)行$digest函數(shù) – 這意味著如果我們馬上停止監(jiān)聽,$scope.$watch函數(shù)甚至都不會(huì)被調(diào)用!但是它究竟是怎樣運(yùn)行的呢?

$digest函數(shù)將會(huì)在$rootScope中被$scope.$apply所調(diào)用。它將會(huì)在$rootScope中運(yùn)行digest循環(huán),然后向下遍歷每一個(gè)作用域并在每個(gè)作用域上運(yùn)行循環(huán)。在簡(jiǎn)單的情形中,digest循環(huán)將會(huì)觸發(fā)所有位于$$watchers變量中的所有watchExp函數(shù),將它們和最新的值進(jìn)行對(duì)比,如果值不相同,就會(huì)觸發(fā)監(jiān)聽器。

當(dāng)digest循環(huán)運(yùn)行時(shí),它將會(huì)遍歷所有的監(jiān)聽器然后再次循環(huán),只要這次循環(huán)發(fā)現(xiàn)了”臟值”,循環(huán)就會(huì)繼續(xù)下去。如果watchExp的值和最新的值不相同,那么這次循環(huán)就會(huì)被認(rèn)為發(fā)現(xiàn)了臟值。理想情況下它會(huì)運(yùn)行一次,如果它運(yùn)行超10次,你會(huì)看到一個(gè)錯(cuò)誤。

因此當(dāng)$scope.$apply運(yùn)行的時(shí)候,$digest也會(huì)運(yùn)行,它將會(huì)循環(huán)遍歷$$watchers,只要發(fā)現(xiàn)watchExp和最新的值不相等,變化觸發(fā)事件監(jiān)聽器。在AngularJS中,只要一個(gè)模型的值可能發(fā)生變化,$scope.$apply就會(huì)運(yùn)行。這就是為什么當(dāng)你在AngularJS之外更新$scope時(shí),例如在一個(gè)setTimeout函數(shù)中,你需要手動(dòng)去運(yùn)行$scope.$apply():這能夠讓AngularJS意識(shí)到它的作用域發(fā)生了變化。

創(chuàng)建自己的臟值檢查
到此為止,我們已經(jīng)可以來創(chuàng)建一個(gè)小巧的,簡(jiǎn)化版本的臟值檢查了。當(dāng)然,相比較之下,AngularJS中實(shí)現(xiàn)的臟值檢查要更加先進(jìn)一些,它提供瘋了異步隊(duì)列以及其他一些高級(jí)功能。

設(shè)置Scope
Scope僅僅只是一個(gè)函數(shù),它其中包含任何我們想要存儲(chǔ)的對(duì)象。我們可以擴(kuò)展這個(gè)函數(shù)的原型對(duì)象來復(fù)制$digest和$watch。我們不需要$apply方法,因?yàn)槲覀儾恍枰谧饔糜虻纳舷挛闹袌?zhí)行任何函數(shù) – 我們只需要簡(jiǎn)單的使用$digest。我們的Scope的代碼如下所示:

var Scope = function( ) {
  this.$$watchers = [];  
};

Scope.prototype.$watch = function( ) {

};

Scope.prototype.$digest = function( ) {

};

我們的$watch函數(shù)需要接受兩個(gè)參數(shù),watchExp和listener。當(dāng)$watch被調(diào)用時(shí),我們需要將它們push進(jìn)入到Scope的$$watcher數(shù)組中。

var Scope = function( ) {
  this.$$watchers = [];  
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {

};

你可能已經(jīng)注意到了,如果沒有提供listener,我們會(huì)將listener設(shè)置為一個(gè)空函數(shù) – 這樣一來我們可以$watch所有的變量。

接下來我們將會(huì)創(chuàng)建$digest。我們需要來檢查舊值是否等于新的值,如果二者不相等,監(jiān)聽器就會(huì)被觸發(fā)。我們會(huì)一直循環(huán)這個(gè)過程,直到二者相等。這就是”臟值”的來源 – 臟值意味著新的值和舊的值不相等!

var Scope = function( ) {
  this.$$watchers = [];  
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};

接下來,我們將創(chuàng)建一個(gè)作用域的實(shí)例。我們將這個(gè)實(shí)例賦值給$scope。我們接著會(huì)注冊(cè)一個(gè)監(jiān)聽函數(shù),在更新$scope之后運(yùn)行$digest!

var Scope = function( ) {
  this.$$watchers = [];  
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};


var $scope = new Scope();

$scope.name = 'Ryan';

$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log(newValue, oldValue);
} );

 
$scope.$digest();

成功了!我們現(xiàn)在已經(jīng)實(shí)現(xiàn)了臟值檢查(雖然這是最簡(jiǎn)單的形式)!上述代碼將會(huì)在控制臺(tái)中輸出下面的內(nèi)容:

Ryan undefined

這正是我們想要的結(jié)果 – $scope.name之前的值是undefined,而現(xiàn)在的值是Ryan。

現(xiàn)在我們把$digest函數(shù)綁定到一個(gè)input元素的keyup事件上。這就意味著我們不需要自己去調(diào)用$digest。這也意味著我們現(xiàn)在可以實(shí)現(xiàn)雙向數(shù)據(jù)綁定!

var Scope = function( ) {
  this.$$watchers = [];  
};

Scope.prototype.$watch = function( watchExp, listener ) {
  this.$$watchers.push( {
    watchExp: watchExp,
    listener: listener || function() {}
  } );
};

Scope.prototype.$digest = function( ) {
  var dirty;

  do {
      dirty = false;

      for( var i = 0; i < this.$$watchers.length; i++ ) {
        var newValue = this.$$watchers[i].watchExp(),
          oldValue = this.$$watchers[i].last;

        if( oldValue !== newValue ) {
          this.$$watchers[i].listener(newValue, oldValue);

          dirty = true;

          this.$$watchers[i].last = newValue;
        }
      }
  } while(dirty);
};


var $scope = new Scope();

$scope.name = 'Ryan';

var element = document.querySelectorAll('input');

element[0].onkeyup = function() {
  $scope.name = element[0].value;

  $scope.$digest();
};

$scope.$watch(function(){
  return $scope.name;
}, function( newValue, oldValue ) {
  console.log('Input value updated - it is now ' + newValue);

  element[0].value = $scope.name;
} );

var updateScopeValue = function updateScopeValue( ) {
  $scope.name = 'Bob';
  $scope.$digest();
};

使用上面的代碼,無論何時(shí)我們改變了input的值,$scope中的name屬性都會(huì)相應(yīng)的發(fā)生變化。這就是隱藏在AngularJS神秘外衣之下數(shù)據(jù)雙向綁定的秘密!

相關(guān)文章

最新評(píng)論