理解Angular Nested Scope 的關(guān)鍵:Prototype Chain

/* 原文發(fā)表在自己的博客上 歡迎踩踩 */

一個(gè)人人都要踩的坑

Angular容易上手的一個(gè)重要原因就是data binding非常簡(jiǎn)單朴则,當(dāng)你在controller里面給scope綁定上一個(gè)object,立刻就能在view中show出來情屹,而且也能夠非常輕松地實(shí)現(xiàn)two way binding。生活十分愉快概龄。

但突然有一天昌腰,不知道從加了哪一行代碼開始,two way binding不工作了坏挠。你翻箱倒柜把書從頭翻到尾,到SO上求爺爺告奶奶邪乍,最終你發(fā)現(xiàn)降狠,你遇到了一個(gè)名叫nested scope的問題。

這個(gè)坑長(zhǎng)什么樣

我們來舉一個(gè)不能再簡(jiǎn)單的栗子庇楞,童鞋們可以到這里看demo榜配。首先我們有個(gè)html頁(yè)面充當(dāng)view

<body ng-controller="MainCtrl">
  <p>Hello {{name}}!</p>
  <input ng-model="name">
  <div ng-if="includeForm">
    <input ng-model="name">
  </div>
</body>

接著咱們有段javascript

var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
  $scope.includeForm = true;
});

很容易看出,我們這個(gè) angular app吕晌,其實(shí)就是把 $scope.name 綁定到 p 和兩個(gè) input element 上蛋褥。初始化后,頁(yè)面是這樣的睛驳,初始值都是 world

一切都在掌控之中
一切都在掌控之中

然后我們?cè)诘谝粋€(gè) input box 里面將文字改成 kitty


完美烙心!
完美!

接下來屬于高危動(dòng)作乏沸,睜大你的雙眼:修改第二個(gè) input box 里的 text淫茵,把它改成 peng 。驚人的是屎蜓,Title 和 第一個(gè) input 里的 kitty 并未隨之改變痘昌。


發(fā)生了什么...
發(fā)生了什么...

最后就是見證奇跡的時(shí)刻钥勋,修改第一個(gè) input box 的值炬转,改回成 world,Title 立刻隨著一起改變算灸,但第二個(gè) input box 像是與這個(gè)世界失去了聯(lián)系扼劈。

已經(jīng)徹底被玩壞
已經(jīng)徹底被玩壞

在分析上面的 case 中 到底發(fā)生了什么之前,我們一起回顧一下 JavaScript 的基礎(chǔ)知識(shí) Inheritance and the prototype chain菲驴。JS 高玩自行跳過這個(gè)章節(jié) :)

什么是prototype

我們知道在 C++/Java/C# 這樣的面向?qū)ο缶幊陶Z(yǔ)言中荐吵,我們可以使用繼承(inheritance)來實(shí)現(xiàn)屬性和方法的共享,減少冗余代碼的書寫。

JavaScript 也支持繼承先煎,但是它并沒有類的概念贼涩,而是使用 prototype 來實(shí)現(xiàn)這一目標(biāo)。JavaScript 中的每個(gè)對(duì)象都有一個(gè)內(nèi)部私有的鏈接指向另一個(gè)對(duì)象薯蝎,這個(gè)對(duì)象就是原對(duì)象的原型(prototype)遥倦。這個(gè)原型對(duì)象也有自己的原型,直到對(duì)象的原型為 null 為止(也就是沒有原型)占锯。這種一級(jí)一級(jí)的鏈結(jié)構(gòu)就稱為原型鏈袒哥。

擁有了繼承之后,JavaScript 的 Object 就擁有了兩種屬性消略,一種是對(duì)象自身的屬性堡称,另外一種是繼承于原型鏈上的屬性。當(dāng)我們?nèi)プx取 Object 的某個(gè)屬性時(shí)艺演,首先查看當(dāng)前 Object 是否擁有該屬性却紧,有的話返回值,如果沒有的話钞艇,找到它的 prototype啄寡,看看這個(gè)對(duì)象上是否有有該屬性。JavaScript 會(huì)順著 prototype chain 一路上去哩照,直到找到這個(gè)屬性活著 prototype chain 到頭為止挺物。

關(guān)于 prototype 更加詳細(xì)和通透的解釋,大家可以參考 這篇文章 和 ruanyf 老師的大作飘弧,我高中語(yǔ)文老是不及格识藤,就不給大家添麻煩了。我就帶大家來看個(gè)小小的栗子次伶。第一步痴昧,我們創(chuàng)建一個(gè) object,就叫它爹吧冠王。

> parent = { "first_name": "Peng"}
< Object {first_name: "Peng"}

爹有一個(gè)屬性叫做 first_name赶撰,值為 Peng。接著我們生一個(gè)兒子柱彻,

> child = Object.create(parent)

Object.create() 這個(gè)函數(shù)會(huì)創(chuàng)建一個(gè)新的 Object 并將新Object 的 prototype 指定為 傳入的參數(shù)豪娜。比如這里,我們傳入的參數(shù)是 parent哟楷,那么 child 的prototype 就是 parent瘤载,child 會(huì)從 parent 這里繼承屬性。比如:

> child.first_name
< "Peng"

child 上本身并沒有 first_name 這個(gè)屬性卖擅,但是他爹有鸣奔,于是依然得到了 Peng 這個(gè)值墨技。到這里為止,我們展示了如何從 prototype 上繼承一個(gè) primitive value 挎狸。繼承 object property 也是一樣的扣汪。

> parent = { "name" : { "first": "peng", "last": "lv"}}
> child = Object.create(parent)
> child.name.first
< "peng"

童鞋們可以在瀏覽器的 console 里面玩一下


沒圖我說個(gè)球啊
沒圖我說個(gè)球啊

到這里,即使是從沒聽說過 prototype 的朋友肯定也明白了锨匆,這不就是老鼠的兒子會(huì)打洞么私痹。但是關(guān)于 prototype 的繼承,我想把 MDN 文檔里的一句話高亮出來

Setting a property to an object creates an own property. The only exception to the getting and setting behavior rules is when there is an inherited property with a getter or a setter.

最重要的就是第一句了统刮,Setting a property to an object creates an own property紊遵。當(dāng)我們?nèi)?get property 的時(shí)候,會(huì)順著 prototype chain 一直往上找侥蒙,但是 set property 并不會(huì)這樣暗膜,而是為當(dāng)前對(duì)象生成一個(gè)新的 property 。比如這樣:

右手 左手 不是慢動(dòng)作重播鞭衩!
右手 左手 不是慢動(dòng)作重播学搜!

自此 child 和 parent 就失聯(lián)了。下面我們可以來看看 Angular 的 nested scope 是怎么一回事论衍。

Angular 如何創(chuàng)建child scope

在使用 Angular 的一些 built-in directive 時(shí)瑞佩,比如 ng-if/ng-include/ng-repeat/ng-switch/etc 時(shí),需要注意的一點(diǎn)是坯台,Angular 會(huì)為其生成一個(gè)新的 scope炬丸,這個(gè) scope 繼承自 外層的 scope⊙牙伲回到我們最上面提到的 demo

<body ng-controller="MainCtrl">
  <p>Hello {{name}}!</p>
  <input ng-model="name">
  <div ng-if="includeForm">
    <input ng-model="name">
  </div>
</body>

MainCtrl 上有一個(gè) scope 作為膠水來粘合 controller 和 view稠炬,而ng-if 又會(huì)生成一個(gè) scope,這個(gè) scope 向上繼承 controller 的 scope咪啡。這個(gè)繼承 Angular 是如何實(shí)現(xiàn)的呢首启,我們來看源碼

function createChildScopeClass(parent) {
  function ChildScope() {
    this.$$watchers = this.$$nextSibling =
        this.$$childHead = this.$$childTail = null;
    this.$$listeners = {};
    this.$$listenerCount = {};
    this.$$watchersCount = 0;
    this.$id = nextUid();
    this.$$ChildScope = null;
  }
  ChildScope.prototype = parent;
  return ChildScope;
}

我們看到,創(chuàng)建 child scope 的時(shí)候撤摸,會(huì)把 child scope 的 prototype (原型) 設(shè)置為 parent 毅桃。根據(jù)我們上面剛剛溫習(xí)的 prototype 繼承機(jī)制,當(dāng)在第二個(gè) input box 里訪問 ng-model="name" 時(shí)准夷,會(huì)先到 ng-if 上的 child scope 尋找 name 這個(gè)屬性钥飞,如果沒有,沿著 prototype 找到 parent scope冕象,最終找到 name 這個(gè)屬性代承。

而當(dāng)我們往第二個(gè) input box 里面輸入新的值(見 第三張圖片)汁蝶,則觸發(fā)了 prototype 的另一個(gè)規(guī)則 Setting a property to an object creates an own property , ng-if 上的 child scope 增加了一個(gè)新的屬性 name 渐扮,parent scope 上的 name 和 child scope 的 name 從此再無瓜葛论悴。當(dāng)我們?cè)俅涡薷?第一個(gè) input box 里的值時(shí),實(shí)際上我們修改的 parent scope 上的 name 墓律,對(duì)于 第二個(gè) input box 來說膀估,并沒有什么卵用。

鳥都不鳥你啊
鳥都不鳥你啊

問題到這里已經(jīng)清楚了耻讽,Angular 的 nested scope 使用了 prototype 這個(gè)機(jī)制來實(shí)現(xiàn) child scope 對(duì) parent scope 的繼承察纯,當(dāng)我們修改 child scope 上的屬性時(shí),會(huì)導(dǎo)致無法更新 parent scope 的屬性针肥。那么該如何解救它們呢饼记?

有兩招

Dot Notation 和 $parent

第一招,江湖人稱 Dot Notation

換做人能夠聽懂的語(yǔ)言就是慰枕,避免給 child scope 上的屬性賦值具则。還記得上文我們講解 prototype chain 的時(shí)候說過,屬性是 object 也可以繼承

> parent = { "name" : { "first": "peng", "last": "lv"}}
> child = Object.create(parent)
> child.name.first = "hulk"
< "hulk"
> parent.name
< Object {first: "hulk", last: "lv"}

parent 有個(gè)屬性叫 name具帮,name 有個(gè)屬性叫 first 博肋。如果我們修改 child.name.first ,第一步是查找 child 上的 name 屬性蜂厅,沒有找到匪凡,根據(jù) prototype chain 找到了 parent 上的 name 屬性,然后修改了它的 property first 掘猿。整個(gè)過程并沒有給 child 的 property name 賦值病游。

當(dāng)然,如果你直接修改 child.name稠通,name 的繼承就消失了礁遵。

> child.name = {"first": "captain", last: "america"}
< Object {first: "captain", last: "america"}
> parent.name
< Object {first: "hulk", last: "lv"}

Dot Notation,這個(gè)名字真的是傳神啊采记,修改和訪問 $scope上屬性的屬性($scope.name.first)佣耐,而不是直接操作 $scope的屬性($scope.name),多一個(gè) Dot 唧龄,就解決了 two way binding 的坑兼砖。

第二招,見招拆招既棺,使用$parent讽挟。

不是擔(dān)心修改 child scope 上的屬性么,直接訪問 parent scope 上的屬性不就行了么丸冕。我們?cè)賮砜?Angular 的代碼

child.$parent = parent;

child scope 直接有個(gè) $parent 屬性來 reference parent scope耽梅。

<body ng-controller="MainCtrl">
  <p>Hello {{name}}!</p>
  <input ng-model="name">
  <div ng-if="includeForm">
    <input ng-model="$parent.name">
  </div>
</body>

這個(gè)方法過于暴力,博主不推薦使用胖烛,被同事爆的風(fēng)險(xiǎn)太高了眼姐。


每篇文章的最后總該總結(jié)點(diǎn)什么诅迷,不能虎頭蛇尾....

想到了!以上問題只在 Angular 1.x 出現(xiàn)众旗,因?yàn)?.0開始就沒有 scope 咯~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末罢杉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贡歧,更是在濱河造成了極大的恐慌滩租,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件利朵,死亡現(xiàn)場(chǎng)離奇詭異律想,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绍弟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門蜘欲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晌柬,你說我怎么就攤上這事姥份。” “怎么了年碘?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵澈歉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我屿衅,道長(zhǎng)埃难,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任涤久,我火速辦了婚禮涡尘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘响迂。我一直安慰自己考抄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布蔗彤。 她就那樣靜靜地躺著川梅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪然遏。 梳的紋絲不亂的頭發(fā)上贫途,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音待侵,去河邊找鬼丢早。 笑死,一個(gè)胖子當(dāng)著我的面吹牛秧倾,可吹牛的內(nèi)容都是我干的怨酝。 我是一名探鬼主播傀缩,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼凫碌!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胃榕,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤盛险,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后勋又,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體苦掘,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年楔壤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鹤啡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蹲嚣,死狀恐怖递瑰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情隙畜,我是刑警寧澤抖部,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站议惰,受9級(jí)特大地震影響慎颗,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜言询,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一俯萎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧运杭,春花似錦夫啊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至躁愿,卻和暖如春叛本,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤钟。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工来候, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逸雹。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓营搅,卻偏偏與公主長(zhǎng)得像云挟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子转质,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理园欣,服務(wù)發(fā)現(xiàn),斷路器休蟹,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • 我基本從來不寫工作的事兒沸枯。 因?yàn)楣ぷ鲗?shí)在沒啥好寫的,不就是工作唄赂弓。 然後今天打算稍微寫一點(diǎn)绑榴,就寫JS吧。 我一直相...
    LostAbaddon閱讀 1,431評(píng)論 22 21
  • You Don't Know JS: this & Object Prototypes Chapter 5: Pr...
    大橙子CZ閱讀 565評(píng)論 0 2
  • 文章作者:Tyan博客:noahsnail.com 3.4 Dependencies A typical ente...
    SnailTyan閱讀 4,128評(píng)論 2 7
  • 在JavaScript中盈魁,一切都是對(duì)象翔怎。 使用深度優(yōu)先遍歷,element是一個(gè)DOM元素,selectors是一...
    garble閱讀 564評(píng)論 0 1