Angular2雙向綁定及變化檢測

前幾天總結(jié)了MVC闸英、MVP、MVVM設(shè)計(jì)模式介袜,其中MVVM的核心機(jī)制就是雙向綁定甫何。React、Vue遇伞、Angular的雙向綁定辙喂,都是基于MVVM的設(shè)計(jì)模式。

什么是雙向綁定

如圖:

雙向綁定.jpg

雙向綁定機(jī)制維護(hù)了頁面(View)與數(shù)據(jù)(Data)的一致性鸠珠。如今加派,MVVM已經(jīng)是前段流行框架必不可少的一部分。

Angular2中的雙向綁定

雙向綁定跳芳,也是Angular2的核心概念之一芍锦,Angular2的雙向綁定是這樣的:

  • data=>view:數(shù)據(jù)綁定,模板語法是 []
  • view=>data:事件綁定飞盆,模板語法是 ()
  • Angular其實(shí)并沒有一個(gè)雙向綁定的實(shí)現(xiàn)娄琉,他的雙向綁定就是數(shù)據(jù)綁定+事件綁定,模板語法是 [()] 吓歇。

Angular2官方給的例子:

<!--value是數(shù)據(jù)綁定孽水,input是事件綁定-->
<input [value]="currentHero.name"   
       (input)="currentHero.name=$event.target.value"
       >
<!--等價(jià)-->
<input [(ngModel)]="currentHero.name">

上面是input空間的雙向綁定語法,很清楚的說明了雙向綁定與兩個(gè)單向綁定的關(guān)系城看。這里沒有使用ngModule語法女气,ngModule語法內(nèi)部實(shí)現(xiàn)與這個(gè)差不多。

事件綁定

  1. 用戶操作出發(fā)DOM事件通知
  2. Angular監(jiān)聽到了通知测柠,然后執(zhí)行模板語法炼鞠,上面的例子就是將input控件的輸入值賦給了currentHero.name缘滥。

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

由于js語言并沒有屬性變化通知的機(jī)制,所以angular也不知道誰發(fā)生了變化谒主,在什么時(shí)候變了朝扼。Angular的變化機(jī)制是:

image.png

上面的例子中input的數(shù)據(jù)綁定過程如下:

  1. 代碼修改了currentHero.name的值。
  2. 觸發(fā)整個(gè)組件樹的變化檢查霎肯。
  3. input顯示了修改后的值擎颖。
數(shù)據(jù)何時(shí)變化

主要入下集中情況可能改變數(shù)據(jù):

  • 用戶輸入操作,比如點(diǎn)擊观游,提交等搂捧。
  • 請求服務(wù)端數(shù)據(jù)。
  • 定時(shí)事件懂缕,比如setTimeout异旧,setInterval

這幾點(diǎn)有個(gè)共同點(diǎn)提佣,就是他們都是異步的吮蛹。也就是說,所有的異步操作是可能導(dǎo)致數(shù)據(jù)變化的根源因素拌屏。

如何通知變化

在Angularjs中是由代碼$scope.$apply()或者$scope.$digest觸發(fā)潮针,而Angular2接入了ZoneJS,由它監(jiān)聽了Angular所有的異步事件。ZoneJS重寫了所有的異步API(所謂的猴子補(bǔ)丁倚喂,MonkeyPath)每篷。ZoneJS會(huì)通知Angular可能有數(shù)據(jù)發(fā)生變化,需要檢測更新端圈。

變化檢測原理 -- 臟檢查

所謂臟檢查就是存儲(chǔ)所有變量的值焦读,每當(dāng)可能有變量發(fā)生變化需要檢查時(shí),就將所有變量的舊值跟新值進(jìn)行比較舱权,不相等就說明檢測到變化矗晃,需要更新對應(yīng)的視圖。

AngularJS與Angular2變化檢測的區(qū)別

Angularjs的變化檢測機(jī)制也是臟檢查宴倍,而Angular2的變化檢測性能比Angularjs提升了很多张症。

Angular2

Angular的核心是組件化,組件的嵌套會(huì)使得最終形成一棵組件樹鸵贬。Angular的變化檢測可以分組件進(jìn)行俗他,每個(gè)組件都有對應(yīng)的變化檢測器ChangeDetector±疲可想而知兆衅,這些變化檢測器也會(huì)構(gòu)成一棵樹。

另外,Angular的數(shù)據(jù)流是自頂而下的羡亩,從父組件到子組件單向流動(dòng)摩疑。單向數(shù)據(jù)流向保證了高效、可預(yù)測的變化檢測夕春,盡管檢查了負(fù)組件之后未荒,自組件可能會(huì)改變父組件的數(shù)據(jù)使得父組件需要再次被檢查专挪,這是不被推薦的數(shù)據(jù)處理方式及志。在開發(fā)模式下,Angular會(huì)進(jìn)行二次檢查寨腔,如果出現(xiàn)上述情況速侈,二次檢查就會(huì)報(bào)錯(cuò):ExpressionChangedAfterItHasBeenCheckedError(關(guān)于這個(gè)問題的答案,可以在參考資料中找到)迫卢。而在生產(chǎn)環(huán)境中倚搬,臟檢查只會(huì)執(zhí)行一次。

Angularjs

相比之下乾蛤,Angularjs采用的是雙向數(shù)據(jù)流每界,錯(cuò)綜復(fù)雜的數(shù)據(jù)流使得他不得不多次檢查,使得數(shù)據(jù)最終趨向穩(wěn)定家卖。理論上眨层,數(shù)據(jù)永遠(yuǎn)不可能穩(wěn)定,Angularjs的策略是上荡,臟檢查超過10次就認(rèn)定程序有問題趴樱。

angular2-change-detection-moscowjs-31-9-638.jpg

變化檢測優(yōu)化

優(yōu)化策略

有2個(gè)思路:

  1. OnPush策略:我知道我沒變,別查我酪捡。
  2. 手動(dòng)控制刷新:我變了叁征,只查我。

變化檢測策略 OnPush

Angular還讓開發(fā)者擁有制定變化策略的能力逛薇。

export enum ChangeDetectionStrategy { 
  OnPush, // 表示變化檢測對象的狀態(tài)為`CheckOnce` 
  Default, // 表示變化檢測對象的狀態(tài)為`CheckAlways`
}

ChangeDetectionStrategy可以看到捺疼,Angular有兩種變化檢測策略。Default是Angular默認(rèn)的變化檢測策略永罚,也就是臟檢查(只要有值發(fā)生變化帅涂,就全部檢查)。開發(fā)者可以根據(jù)場景來設(shè)置更加高效的變化檢測方式:OnPush尤蛮。OnPush策略媳友,就是只有當(dāng)輸入數(shù)據(jù)的引用發(fā)生變化或者有事件觸發(fā)時(shí),組件進(jìn)行變化檢測产捞。

@Component({
  template: `
    <h2>{{vData.name}}</h2>
    <span>{{vData.email}}</span>
  `,
  // 設(shè)置該組件的變化檢測策略為onPush
  changeDetection: ChangeDetectionStrategy.OnPush
})
class VCardCmp {
  @Input() vData;
}

比如上面這個(gè)例子醇锚,當(dāng)vData的屬性值發(fā)生變化的時(shí)候,這個(gè)組件不會(huì)發(fā)生變化檢測,只有當(dāng)vData重新賦值的時(shí)候才會(huì)焊唬。一般恋昼,只接受輸入的木偶子組件(dumb components)比較適合采用onPush策略。

那什么時(shí)候只要對象的屬性值發(fā)生變化赶促,整個(gè)對象的引用就變了呢液肌?不可變對象(Immutable Object)鸥滨。當(dāng)組件中的輸入對象是不變量時(shí)嗦哆,可采用onPush變化檢測策略婿滓,減少變化檢測的頻率。換個(gè)角度來說凸主,為了更加智能地執(zhí)行變化檢測橘券,可以在只接受輸入的子組件中采用onPush策略。

手動(dòng)控制變化檢測

Angular不僅可以讓開發(fā)者設(shè)置變化檢測策略旁舰,還可以讓開發(fā)者獲取變化檢測對象引用ChangeDetectorRef,手動(dòng)去操作變化檢測嗡官。變化檢測對象引用給開發(fā)者提供的方法有以下幾種:

  • markForCheck():將檢查組件的所有父組件所有子組件箭窜,即使設(shè)置了變化檢測策略為onPush谨湘。
  • detach():將變化檢測對象脫離檢測對象樹,不再進(jìn)行變化檢查紧阔;結(jié)合detectChanges可實(shí)現(xiàn)局部變化檢測坊罢。(采用onPush策略之后的組件detach()無效)
  • detectChanges():將檢測該組件及其子組件,結(jié)合detach可實(shí)現(xiàn)局部檢測活孩。
  • checkNoChanges(): 檢測該組件及其子組件,如果有變化存在則報(bào)錯(cuò)乖仇,用于開發(fā)階段二次驗(yàn)證變化已經(jīng)完成憾儒。
  • reattach():將脫離的變化檢測對象重新鏈接到變化檢測樹上乃沙。

那么,如果是Observable的話警儒,它會(huì)訂閱所有的變量變化训裆,只要在訂閱回調(diào)函數(shù)中手動(dòng)觸發(fā)變化檢測即可實(shí)現(xiàn)最小成本的檢測(仍采用onPush變化檢測策略)眶根。舉個(gè)例子:

@Component({
  template: '{{counter}}',
  changeDetection: ChangeDetectionStrategy.OnPush
})
class CartBadgeCmp {

  @Input() addItemStream:Observable<any>;
  counter = 0;

  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    this.addItemStream.subscribe(() => {
      this.counter++;        // 數(shù)據(jù)模型發(fā)生變化
      this.cd.markForCheck(); // 手動(dòng)觸發(fā)檢測
    })
  }
}

另外边琉,當(dāng)數(shù)據(jù)模型變化太過頻繁,我們可自定義變化檢測的時(shí)機(jī)变姨。舉個(gè)例子:

@Component({
  template: `{{counter}}
  <input type="check" (click)="toggle()">`, 
})
class CartBadgeCmp { 
  counter = 0;
  detectEnabled = false;
 
  constructor(private cd: ChangeDetectorRef) {}

  ngOnInit() {
    // 每10毫秒增加1
    setInterval(()=>{this.counter++}, 10);
  }
  
  toggle(){
      if( this.detectEnabled ){
          this.cd.reattach();  // 鏈接上變化檢測樹
      }
      else{
          this.cd.detach(); // 脫離變化檢測樹
      }
  }
}

總結(jié)

Angular與Angularjs都采用變化檢測機(jī)制族扰,前者優(yōu)于后者主要體現(xiàn)在:

  • 單項(xiàng)數(shù)據(jù)流動(dòng)
  • 以組件為單位維度獨(dú)立進(jìn)行檢測
  • 生產(chǎn)環(huán)境只進(jìn)行一次檢查
  • 可自定義的變化檢測策略:DefaultonPush
  • 可自定義的變化檢測操作:markForcheck()定欧、detectChanges()渔呵、detach()忧额、reattach()厘肮、checkNoChanges()
  • 代碼實(shí)現(xiàn)上的優(yōu)化睦番,據(jù)說采用了VM friendly的代碼。

Reference

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耍属,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子厚骗,更是在濱河造成了極大的恐慌示启,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件领舰,死亡現(xiàn)場離奇詭異夫嗓,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)冲秽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門舍咖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人锉桑,你說我怎么就攤上這事排霉。” “怎么了民轴?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵攻柠,是天一觀的道長。 經(jīng)常有香客問我后裸,道長瑰钮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任微驶,我火速辦了婚禮浪谴,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己较店,他們只是感情好士八,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梁呈,像睡著了一般婚度。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上官卡,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天蝗茁,我揣著相機(jī)與錄音,去河邊找鬼寻咒。 笑死哮翘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毛秘。 我是一名探鬼主播饭寺,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叫挟!你這毒婦竟也來了艰匙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抹恳,失蹤者是張志新(化名)和其女友劉穎员凝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奋献,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡健霹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瓶蚂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糖埋。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖扬跋,靈堂內(nèi)的尸體忽然破棺而出阶捆,到底是詐尸還是另有隱情,我是刑警寧澤钦听,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布洒试,位于F島的核電站,受9級特大地震影響朴上,放射性物質(zhì)發(fā)生泄漏垒棋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一痪宰、第九天 我趴在偏房一處隱蔽的房頂上張望叼架。 院中可真熱鬧畔裕,春花似錦、人聲如沸乖订。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乍构。三九已至甜无,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間哥遮,已是汗流浹背岂丘。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留眠饮,地道東北人奥帘。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像仪召,于是被迫代替她去往敵國和親寨蹋。 傳聞我的和親對象是個(gè)殘疾皇子返咱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 版本:Angular 5.0.0-alpha AngularDart(本文檔中我們通常簡稱 Angular ) 是...
    soojade閱讀 836評論 0 4
  • 連續(xù)看了兩部電影牍鞠,戰(zhàn)狼和建軍大業(yè)。這兩部電影連著看確實(shí)有很大的沖擊难述,見證了我們?nèi)嗣褴婈?duì)從開始到如今萤晴。作為一名老師我...
    一個(gè)閑著的箱子閱讀 1,079評論 0 2
  • 近來晚上總覺得困胁后,但習(xí)慣于晚上看書,所以總會(huì)堅(jiān)持讀上幾篇書再睡攀芯⊥投希可是這樣做侣诺,第二天清晨醒來,總覺得沒睡飽年鸳。 這會(huì)兒...
    邵清清靜閱讀 166評論 0 4
  • 其實(shí)早在好幾年前就在書店看到《秘密》這本書灭忠,當(dāng)時(shí)只是單純覺得這本書的字好少,就沒有買座硕。沒想到,這一錯(cuò)過就是幾年的時(shí)...
    幸福儲(chǔ)錢罐閱讀 198評論 0 1
  • “那么請問你對未來五年的職業(yè)規(guī)劃是怎樣的瘦真?” 光心里默念著“關(guān)你屁事”嘴上微笑著說“我相信未來的五年里,在自己所處...
    小烈吶閱讀 258評論 0 0