徹底理解Angular中scope的屬性更新問題

起因是在StackOverflow上看到了這個帖子:Directive isolate scope with ng-repeat scope in AngularJS - Stack Overflow

題主原本想問的是一個AngularJS中對指令使用ng-repeat情況下的孤立作用域問題,最佳答主指出了題主理解的根本性錯誤商佑,并用了最簡單的demo來復(fù)現(xiàn)這個問題贱迟。

有問題的Demo
html代碼:

<div ng-app="my-app" ng-controller="MainController">
    <div>
        Selected: {{selected.first}} {{selected.last}}
    </div>
    <div>
        <ul>
            <li ng-repeat="name in names"
                ng-class="{ active: $index == selected }"
                ng-click="selected = $index">
                {{$index}}: {{name.first}} {{name.last}}
            </li>
        </ul>
    </div>
</div>

js代碼:

var module = angular.module('my-app', []);

function MainController($scope) {
    $scope.names = [
        {first: "John", last: "Smith"},
        {first: "Jane", last: "Smith"}
    ];
    $scope.selected = undefined;
}

點擊行就會發(fā)現(xiàn)停局,點擊并不能觸發(fā)"Selected:"后面出現(xiàn)被選中行的文字现柠。

正常工作的demo
html代碼:

<div ng-app="my-app" ng-controller="MainController">
    <div>
        Selected: {{selected.first}} {{selected.last}}
    </div>
    <div>
        <ul>
            <li ng-repeat="name in names"
                ng-class="{ active: $index == state.selected }"
                ng-click="state.selected = $index">
                {{$index}}: {{name.first}} {{name.last}}
            </li>
        </ul>
    </div>
</div>

js代碼:

var module = angular.module('my-app', []);

function MainController($scope) {
    $scope.names = [
        {first: "John", last: "Smith"},
        {first: "Jane", last: "Smith"}
    ];
    $scope.state = { selected: undefined };
}

對比兩個Demo磕潮,就會發(fā)現(xiàn)效床,問題的關(guān)鍵在于正確代碼中使用state對象來封裝了這個selected屬性候引。那么,為什么使用了state對象封裝就能解決這個問題呢辱揭?

最佳答主解釋說离唐,selected是一個prmitive類型(姑且翻譯為基本數(shù)據(jù)類型,包括null问窃、undefined、boolean完沪、number域庇、string和symbol(es6),參考這里)覆积,這意味著听皿,當子類在寫這個屬性時會覆蓋父類的同名屬性。并強調(diào)了一條AngularJS中的黃金法則:所有的模型值必須包含一個"."宽档。

這讓我想到在讀《AngularJS權(quán)威教程》P8時也看到了一句類似的話:由于JavaScript自身的特點尉姨,以及它在傳遞值和引用時的不同處理方式,通常認為吗冤,在視圖中通過對象的屬性而非對象本身來進行引用綁定又厉,是Angular中的最佳實踐九府。

這么解釋就可以了嗎?非也非也覆致,對前端小白來說侄旬,看到這些話時仍然是丈二和尚摸不著頭腦。

如果你在Chrome中安裝了Batarang插件煌妈,在點擊任一行前查看每一個li行的Model儡羔,會發(fā)現(xiàn)其Model中并沒有selected屬性,而點擊之后璧诵,其Model中就多了這個屬性汰蜘,并且與父Model中的selected值不一致,父Model中它的值仍為undefined之宿。這個首先能說明鉴扫, 每一個li行在被點擊時,確實創(chuàng)建了自己的selected屬性澈缺,并且覆蓋了父類的同名屬性坪创。

最佳答主在答案中說到了一個關(guān)鍵的地方:the parameters passed into ngRepeat for use on your directive's attributes do use a prototypically-inherited scope。這里面提到了原型繼承這個東西姐赡。首先莱预,要搞明白Javascript中的原型(prototype)是怎么回事,這個可以參考Javascript繼承機制的設(shè)計思想 - 阮一峰的網(wǎng)絡(luò)日志

簡單總結(jié)一下:Javascript中一個"類"所有實例對象需要共享的屬性和方法项滑,都放在prototype對象里面.

這個StackOverflow問題依沮,其實歸根到底還是一個js基礎(chǔ)問題,運行一下下面的代碼枪狂,你就知道是怎么回事了:

var Parent = function(){
} ;

Parent.prototype.name = 'parent' ;
Parent.prototype.obj = {name : 'objparent'} ;

var Child = function(){
};
Child.prototype = new Parent() ;

var parent = new Parent() ;
var child = new Child() ;

console.log(parent.name) ; //parent
console.log(child.name) ;  //parent

child.name = 'child' ;

console.log(parent.name) ; //parent
console.log(child.name) ;  //child

console.log(parent.obj.name) ;  //objparent
console.log(child.obj.name) ;  //objparent

child.obj.name = 'objchild' ;
console.log(parent.obj.name) ;  //objchild
console.log(child.obj.name) ; //objchild


console.log(parent.obj) ;  //Object name:"objchild"
console.log(child.obj) ;  //Object name:"objchild"

child.obj = {name : 'test'} ;
console.log(parent.obj) ;  //Object name:"objchild"
console.log(child.obj) ;  //Object name:"test"

在Parent原型中危喉,name是string類型,屬于Javascript中的Primitive類型(基本數(shù)據(jù)類型)州疾,在子類中寫name時辜限,不會修改父類的name值;
而obj是Javascript中的Object類型严蓖,不過直接在子類中修改obj對象薄嫡,其表現(xiàn)與上面的name一樣
只有修改obc.name時,才能保證父類與子類的obc.name保持一致颗胡。

這說明毫深,子類無論是寫與父類同名的Primitive類型屬性,還是寫Object屬性毒姨,都是在子類中創(chuàng)建了新的屬性覆蓋了父類的同名屬性哑蔫。只有寫對象(Object)的屬性(即使用點表達式),才能實際上修改到父類中同名對象的屬性值。

正因為如此闸迷,在Angular設(shè)置Model時嵌纲,在父級作用域中宜使用.(點表達式)來處理基本數(shù)據(jù)模型,本質(zhì)就是對像的原型繼承會保持同一個引用。這樣就可以避免上面的問題稿黍。

其實從根本上來說疹瘦,是因為在子類進行屬性寫操作時,只有使用點表達式才能通過原型鏈訪問父類的對象的屬性值巡球。

JavaScript 秘密花園也提到了這一點:
當查找一個對象的屬性時言沐,JavaScript 會向上遍歷原型鏈,直到找到給定名稱的屬性為止酣栈。到查找到達原型鏈的頂部 - 也就是 Object.prototype - 但是仍然沒有找到指定的屬性险胰,就會返回 undefined

那么,為什么不使用點表達式矿筝,就是覆蓋父類的同名屬性呢起便?從Javascript的設(shè)計上來說,這個是給予了我們方便:在使用第三方JS類庫的時候窖维,往往有時候他們定義的原型方法是不能滿足我們的需要榆综,但是又離不開這個類庫,直接對屬性或者方法進行寫操作就可以重寫他們的原型中的一個或者多個屬性或function铸史。

注:以上說的點表達式不是絕對的鼻疮,比如訪問數(shù)組元素就是不用點表達式的。

寫這篇文章時參考了下面的文章琳轿,我列出了一些關(guān)鍵的要點判沟,需要的可以自行參考:

  • AngularJS子級作用域問題(ngInclude;ngView;ngSwitch;ngRepeat) - 簡書
    AngularJS的繼承是通過javascript的原型繼承方式實現(xiàn)的,進行原型繼承即意味著父作用域在子作用域的原型鏈上崭篡。因為原型鏈的檢索只會在屬性檢索的時候觸發(fā)挪哄,不會在改變屬性值的時候觸發(fā)。所以我們需要把原始類型轉(zhuǎn)換成對象琉闪,把值綁定在對象的屬性上迹炼。

  • Feenan's Blog-[譯]深入理解ng里的scope-程序人生
    scope里的原型繼承比較容易理解,一般情況下都不需要你去了解它的實現(xiàn),但是當你在子作用域里綁定父作用域里的基本數(shù)據(jù)類型(比如,整型,字符串,布爾型)的時候,這種情況下就會出現(xiàn)問題,你會發(fā)現(xiàn)它并沒有像你指望的那樣去運行,當修改子作用域里的基本數(shù)據(jù)類型時,并不會修改父作用域,而是在子作用域里創(chuàng)建一個新的屬性,這并不是ng干的,這只是js里原型繼承所導(dǎo)致的,關(guān)于這個問題,可以看看這個例子

  • AngularJS中scope基于原型鏈的繼承 // 進擊的馬斯特

    • 讀子類的屬性時,子類有這個屬性(hasOwnProperty)的時候則讀子類自己的塘偎,子類沒有的時候讀父類的疗涉,不管子類有沒有這個屬性,在子類上都不會有新屬性被創(chuàng)建吟秩。
    • 寫子類的屬性時,如果子類有這個屬性(hasOwnProperty)則寫子類的绽淘,子類沒有的話就會在子類上新建一個同名的新屬性涵防,而父類繼承過來的屬性被隱藏。
  • 深入理解Angular作用域 - 簡書

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市壮池,隨后出現(xiàn)的幾起案子偏瓤,更是在濱河造成了極大的恐慌,老刑警劉巖椰憋,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厅克,死亡現(xiàn)場離奇詭異,居然都是意外死亡橙依,警方通過查閱死者的電腦和手機证舟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窗骑,“玉大人女责,你說我怎么就攤上這事〈匆耄” “怎么了抵知?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長软族。 經(jīng)常有香客問我刷喜,道長,這世上最難降的妖魔是什么立砸? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任掖疮,我火速辦了婚禮,結(jié)果婚禮上仰禽,老公的妹妹穿的比我還像新娘氮墨。我一直安慰自己,他們只是感情好吐葵,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布规揪。 她就那樣靜靜地躺著,像睡著了一般温峭。 火紅的嫁衣襯著肌膚如雪猛铅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天凤藏,我揣著相機與錄音奸忽,去河邊找鬼。 笑死揖庄,一個胖子當著我的面吹牛栗菜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹄梢,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼疙筹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起而咆,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤霍比,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后暴备,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悠瞬,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年涯捻,在試婚紗的時候發(fā)現(xiàn)自己被綠了浅妆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡汰瘫,死狀恐怖狂打,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情混弥,我是刑警寧澤趴乡,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站蝗拿,受9級特大地震影響晾捏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哀托,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一惦辛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仓手,春花似錦胖齐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至添坊,卻和暖如春剿另,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贬蛙。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工雨女, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阳准。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓氛堕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親野蝇。 傳聞我的和親對象是個殘疾皇子岔擂,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

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