溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Ember.js的視圖層分析

發(fā)布時(shí)間:2021-11-17 16:13:59 來源:億速云 閱讀:117 作者:iii 欄目:web開發(fā)

這篇文章主要講解了“Ember.js的視圖層分析”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Ember.js的視圖層分析”吧!

Ember.js 有一套復(fù)雜的用于創(chuàng)建、管理并渲染連接到瀏覽器 DOM 上的層級(jí)視圖的系 統(tǒng)。視圖負(fù)責(zé)響應(yīng)諸如點(diǎn)擊、拖拽以及滾動(dòng)等的用戶事件,也在視圖底層數(shù)據(jù)變更時(shí)更 新 DOM 的內(nèi)容。

視圖層級(jí)通常由求值一個(gè) Handlebars 模板創(chuàng)建。當(dāng)模板求值后,會(huì)添加子視圖。當(dāng) 那些 子視圖求值后,會(huì)添加它們的子視圖,如此遞推,直到整個(gè)層級(jí)被創(chuàng)建。

即使你并沒有在 Handlebars 模板中顯式地創(chuàng)建子視圖,Ember.js 內(nèi)部仍使用視圖系 統(tǒng)更新綁定的值。例如,每個(gè) Handlebars 表達(dá)式 {{value}} 幕后創(chuàng)建一個(gè)視圖, 這個(gè)視圖知道當(dāng)值變更時(shí)如何更新綁定值。

你也可以在應(yīng)用運(yùn)行時(shí)用 Ember.ContainerView 類對(duì)視圖層級(jí)做出修改。一個(gè)容器 視圖暴露一個(gè)可以手動(dòng)修改的子視圖實(shí)例數(shù)組,而非模板驅(qū)動(dòng)。

視圖和模板串聯(lián)工作提供一套用于創(chuàng)建任何你夢(mèng)寐以求的用戶界面的穩(wěn)健系統(tǒng)。最終用 戶應(yīng)從諸如當(dāng)渲染和事件傳播是的計(jì)時(shí)事件之類的復(fù)雜東西中隔離開。應(yīng)用開發(fā)者應(yīng)可 以一次性把他們的 UI 描述成 Handlebars 標(biāo)記字符串,然后繼續(xù)完成他們的應(yīng)用,而 不必?zé)烙诖_保它一直是最新的。

它解決了什么問題?

子視圖

在典型的客戶端應(yīng)用中,視圖同時(shí)在本身和 DOM 中表示嵌套的元素。在解決這個(gè)問題 的天真方案中,獨(dú)立的視圖對(duì)象表示單個(gè) DOM 元素,專門的引用解決不同種類的視圖 保持對(duì)概念中嵌套在它們內(nèi)部的視圖的跟蹤。

這里是一個(gè)簡單的例子,表示一個(gè)應(yīng)用主視圖,里面有一集合嵌套視圖,且獨(dú)立的元素在集合內(nèi)嵌套。

Ember.js的視圖層分析

這個(gè)系統(tǒng)第一眼看上去毫無異樣,但是想象我們要在上午 8 點(diǎn)而不是上午 9 點(diǎn)開放喬 的七鰓鰻小屋。在這種情況下,我們會(huì)想要重新渲染應(yīng)用視圖。因?yàn)殚_發(fā)者需要構(gòu)建指 向在一個(gè)特殊基礎(chǔ)上的子視圖的引用,這個(gè)重渲染過程存在若干問題。

為了重新渲染應(yīng)用視圖,應(yīng)用視圖也必須手動(dòng)重新渲染子視圖并重新把它們插入到應(yīng)用 視圖的元素中。如果實(shí)現(xiàn)得完美,這個(gè)過程會(huì)正常工作,但它依賴于一個(gè)完美的,專門 的視圖層級(jí)實(shí)現(xiàn)。如果任何一個(gè)視圖沒有精確地實(shí)現(xiàn)它,整個(gè)重新渲染過程會(huì)失敗。

為了避免這些問題,Ember 的視圖層級(jí)從概念上就帶有子視圖的烙印。

   Ember.js的視圖層分析

當(dāng)應(yīng)用時(shí)圖重新渲染時(shí),Ember 而不是應(yīng)用代碼負(fù)責(zé)重新渲染并插入子視圖。這也意味 著 Ember 可以為你執(zhí)行任何內(nèi)存管理,比如清理觀察者和綁定。

這不僅在一定程度上消滅了樣板代碼,也破除了有瑕疵的視圖層級(jí)實(shí)現(xiàn)帶來的未期失敗的可能。

事件委派

在過去,web 開發(fā)者已經(jīng)用在獨(dú)立的單個(gè)元素上添加事件監(jiān)聽器來獲知什么時(shí)候用戶與 它們交互。例如,你會(huì)有一個(gè) <div> 元素,其上注冊(cè)了一個(gè)當(dāng)用戶點(diǎn)擊它時(shí)觸發(fā)的 函數(shù)。

盡管如此,這個(gè)途徑在處理大數(shù)量交互元素上不會(huì)縮放。比如,想象一個(gè)帶有 100 個(gè) <li><ul> ,每個(gè)項(xiàng)目后都有一個(gè)刪除按鈕。既然所有的這些項(xiàng)目行為都是一 致的,為每個(gè)刪除按鈕創(chuàng)建共計(jì) 100 個(gè)事件監(jiān)聽器無疑是低效的。  

Ember.js的視圖層分析

要解決這個(gè)問題,開發(fā)者發(fā)現(xiàn)了一種名為“事件委派”的技術(shù)。你可以在容器元素上注冊(cè) 一個(gè)監(jiān)聽器并使用 event.target 來識(shí)別哪個(gè)元素是用戶點(diǎn)擊的,而不是為問題中的 每個(gè)項(xiàng)目創(chuàng)建一個(gè)監(jiān)聽器。

Ember.js的視圖層分析

實(shí)現(xiàn)這有一些微妙,因?yàn)橐恍┦录ū热?focus 、 blurchange )不會(huì)冒 泡。幸運(yùn)的是,jQuery 已經(jīng)徹底解決了這個(gè)問題;用 jQuery 的 on 方法可以可靠 地處理所有原生瀏覽器事件。

其它 JavaScript 框架用兩種方法中的其一來處理這個(gè)問題。第一種是,它們要你自己 實(shí)現(xiàn)原生解決方案,為每個(gè)項(xiàng)目創(chuàng)建獨(dú)立的視圖。當(dāng)你創(chuàng)建視圖,它在視圖的元素上設(shè) 置一個(gè)監(jiān)聽器。如果你有一個(gè)含有 500 個(gè)項(xiàng)目的列表,你會(huì)創(chuàng)建 500 個(gè)視圖并且每個(gè) 視圖都會(huì)在它自己的元素上設(shè)置一個(gè)監(jiān)聽器。

第二種方法是,框架在視圖層內(nèi)置事件委派。當(dāng)創(chuàng)建一個(gè)視圖,你可以提供一個(gè)事件列 表來在事件發(fā)生時(shí)委派一個(gè)方法來調(diào)用。這只剩下識(shí)別接受事件的方法的點(diǎn)擊上下文 (比如,列表中的哪個(gè)項(xiàng)目)。

你現(xiàn)在要面對(duì)兩個(gè)令人不安的選擇:為每個(gè)項(xiàng)目創(chuàng)建一個(gè)新視圖,這樣會(huì)喪失事件委派 的優(yōu)勢(shì),或是為所有項(xiàng)目創(chuàng)建單個(gè)視圖,這樣必須存儲(chǔ) DOM 中底層 JavaScript 的信息。

要解決這個(gè)問題,Ember 用 jQuery 把所有事件委派到應(yīng)用的根元素(通常是文檔的 body )。當(dāng)一個(gè)事件發(fā)生,Ember 識(shí)別出最近的處理事件視圖并調(diào)用它的事件處理 器。這意味著你可以創(chuàng)建視圖來保存一個(gè) JavaScript 上下文,但仍然從事件委派上受 益。

進(jìn)一步地,因?yàn)?Ember 只為整個(gè) Ember 應(yīng)用注冊(cè)一個(gè)事件,創(chuàng)建新視圖永遠(yuǎn)都不需要 設(shè)置事件監(jiān)聽器,這使得重渲染高效且免于出錯(cuò)。當(dāng)視圖有一個(gè)子視圖,這也意味著不 需要手動(dòng)取消委派重新渲染過程中替換掉的視圖。

渲染管道

大多數(shù) web 應(yīng)用用特殊的模板語言標(biāo)記來指定它們的用戶界面。對(duì)于 Ember.js,我們 已經(jīng)完成用可在值修改的時(shí)候自動(dòng)更新模板的 Handlebars 模板語言來編寫模板。

雖然顯示模板的過程對(duì)開發(fā)者是自動(dòng)的,但其遮蓋了把原始模板轉(zhuǎn)換為最終模板、生成 用戶可見的 DOM 表示的一系列必要步驟。

這是 Ember 視圖的近似生命周期:

Ember.js的視圖層分析

1. 模板編譯

應(yīng)用的模板通過網(wǎng)絡(luò)加載或以字符串形式作為應(yīng)用的載荷。當(dāng)應(yīng)用加載時(shí),它發(fā)送模板 字符串到 Handlebars 來編譯成函數(shù)。一經(jīng)編譯,模板函數(shù)會(huì)被保存,且可以被多個(gè)視 圖重復(fù)使用,每次都它們都需重新編譯。

這個(gè)步驟會(huì)在應(yīng)用中服務(wù)器預(yù)編譯模板的地方發(fā)出。在那些情況下,模板不作為原始的 人類可讀的模板傳輸,而是編譯后的代碼。

因?yàn)?Ember 負(fù)責(zé)模板編譯,你不需要做任何額外的工作來保證編譯后的模板可以重用。

2. 字符串的連接

當(dāng)應(yīng)用在視圖上調(diào)用 appendappendTo 時(shí),一個(gè)視圖渲染過程會(huì)被啟動(dòng)。 appendappendChild 調(diào)用 安排 視圖渲染并在之后插入。這允許應(yīng)用中的 延遲邏輯(譬如綁定同步)在渲染元素之前執(zhí)行。

要開始渲染過程,Ember 創(chuàng)建一個(gè) RenderBuffer 并把它呈遞給視圖來把視圖的內(nèi)容 附加到上面。在這個(gè)過程中,視圖可以創(chuàng)建并渲染子視圖。當(dāng)它這么做時(shí),父視圖創(chuàng)建 并分配一個(gè) RenderBuffer 給子視圖,并把它連接到父視圖的 RenderBuffer 上。

Ember 在渲染每個(gè)視圖前刷新綁定同步隊(duì)列。這樣,Ember 保障不會(huì)渲染需要立即替換 的過期數(shù)據(jù)。

一旦主視圖完成渲染,渲染過程會(huì)創(chuàng)建一個(gè)視圖樹(即“視圖層級(jí)”),連接到緩沖區(qū)樹 上。通過向下遍歷緩沖區(qū)樹并把它們轉(zhuǎn)換為字符串,我們就有了一個(gè)可以插入到 DOM  的字符串。

這里是一個(gè)簡單的例子:

Ember.js的視圖層分析

除子節(jié)點(diǎn)之外(字符串和其它 RenderBuffer ), RenderBuffer 也會(huì)封裝元素標(biāo) 簽名稱、id、class、樣式和其它屬性。這使得渲染過程修改這些屬性(例如樣式)成 為可能,即使在子字符串已經(jīng)渲染完畢。因?yàn)檫@些屬性的許多都可以通過綁定(例如用 bindAttr )控制,這使得渲染過程穩(wěn)健且透明。

3. 元素的創(chuàng)建和插入

在渲染過程的最后,根視圖向 RenderBuffer 請(qǐng)求它的元素。 RenderBuffer 獲得 它的完整字符串并用 jQuery 把它轉(zhuǎn)換成一個(gè)元素。視圖把那個(gè)元素分配到它的 element 屬性并把把它放置到 DOM 中正確的位置( appendTo 指定的位置,如果 應(yīng)用使用 append 即是應(yīng)用的根元素)。

雖然父視圖直接分配它的元素,但每個(gè)子視圖惰性查找它的元素。它通過查找 id 匹 配它的 elementId 屬性的元素來完成這。除非顯式提供,渲染過程生成一個(gè) elementId 屬性比你更分配它的值給視圖的 RenderBuffer ,RenderBuffer 允 許視圖按需查找它的元素。

4. 重新渲染

在視圖把自己插入到 DOM 后,Ember 和應(yīng)用都會(huì)要重新渲染視圖。它們可以在視圖上 調(diào)用 rerender 方法來出發(fā)一次重渲染。

重新渲染會(huì)重復(fù)上面的步驟 2 和步驟 3,有兩點(diǎn)例外:

  • rerender 用新元素替換已有的元素,而不是把元素插入到顯式定義的位置。

  • 除了渲染新元素,它也刪除舊元素并銷毀它的子元素。這允許 Ember 在重新渲染視 圖時(shí)自動(dòng)處理撤銷合適的綁定和觀察者。這使得路徑上的觀察者可行,因?yàn)樽?cè)和撤銷 注冊(cè)所有的嵌套觀察者都是自動(dòng)的。

最常見的導(dǎo)致視圖重新渲染的原因是當(dāng)綁定到 Handlebars 表達(dá)式( {{foo}} )變 更。Ember 內(nèi)部為每個(gè)表達(dá)式創(chuàng)建一個(gè)簡單的視圖,并且在路徑上注冊(cè)一個(gè)觀察者。當(dāng) 路徑變更時(shí),Ember 用新值更新那個(gè)區(qū)域的 DOM。

另一個(gè)常見的情況是一個(gè) {{#if}}{{#with}} 塊。當(dāng)渲染一個(gè)模板時(shí),Ember 為這些塊輔助標(biāo)創(chuàng)建虛擬的視圖。這些虛擬的視圖不會(huì)出現(xiàn)在公共可訪問的視圖層級(jí)里 (當(dāng)從視圖獲取 parentViewchildViews 時(shí)),但它們的存在啟用了一致的重 渲染。

當(dāng)傳遞到 {{#if}}{{#with}} 的路徑變更,Ember 自動(dòng)重新渲染虛擬視圖替換 它的內(nèi)容,重要的是,也會(huì)銷毀所有的子視圖來釋放內(nèi)存。

除了這些情景,應(yīng)用有時(shí)也會(huì)要顯式地重新渲染視圖(通常是一個(gè) ContainerView ,見下)。在這種情況下,應(yīng)用可以直接調(diào)用 rerender ,且 Ember 會(huì)把一項(xiàng)重渲染工作加入隊(duì)列,用相同的語義元素。

這個(gè)過程像是這樣:

Ember.js的視圖層分析

視圖層級(jí)

父與子

當(dāng) Ember 渲染一個(gè)模板化的視圖,它會(huì)生成一個(gè)視圖層級(jí)。讓我們假設(shè)已有一個(gè)模板 form

原文鏈接:

{{view App.Search placeholder="Search"}} {{#view Ember.Button}}Go!{{/view}}

然后我們像這樣把它插入到 DOM 中:

var view = Ember.View.create({   templateName: 'form' }).append();

這會(huì)創(chuàng)建一個(gè)如下小巧的視圖等級(jí): 

Ember.js的視圖層分析

你可以用 parentViewchildViews 屬性在視圖層級(jí)中游走。

var children = view.get('childViews') // [ <App.Search>, <Ember.Button> ] children.objectAt(0).get('parentView') // 視圖

一個(gè)常見的 parentView 使用方法是在子視圖的實(shí)例里。

App.Search = Ember.View.extend({   didInsertElement: function() {     // this.get('parentView') 指向 `view`   } })

生命周期鉤子

為了容易地在視圖的生命周期的不同點(diǎn)上執(zhí)行行為,有若干你可以實(shí)現(xiàn)的鉤子。

  • willInsertElement: 這個(gè)鉤子在視圖渲染后插入 DOM 之前調(diào)用。它不提供對(duì)視圖的 element 的訪問。

  • didInsertElement: 這個(gè)鉤子在視圖被插入到 DOM 后立即調(diào)用。它提供到視圖的 element 的訪問,且對(duì)集成到外部庫非常有用。任何顯式的 DOM 設(shè)置代碼應(yīng)限于這個(gè)鉤子。

  • willDestroyElement: 這個(gè)鉤子在元素從 DOM 移除前立即調(diào)用。這提供了銷毀任何與 DOM 節(jié)點(diǎn)關(guān)聯(lián)的外部狀態(tài)的機(jī)會(huì)。像 didInsertElement 一樣,它對(duì)于集成外部庫非常有用。

  • willRerender: 這個(gè)鉤子在視圖被重新渲染前立即調(diào)用。如果你想要在視圖被重新渲染前執(zhí)行一些銷毀操作,這會(huì)很有用。

  • becameVisible: 這個(gè)鉤子在視圖的 isVisible 或它的祖先之一的 isVisible變?yōu)檎嬷?,且關(guān)聯(lián)的元素也變?yōu)榭梢姾笳{(diào)用。注意這個(gè)鉤子只在所有可見性由 isVisible 屬性控制的時(shí)候可靠。

  • becameHidden: 這個(gè)鉤子在視圖的 isVisible 或它的祖先之一的 isVisible變?yōu)榧僦?,且關(guān)聯(lián)的元素也變?yōu)殡[藏后調(diào)用。注意這個(gè)鉤子只在所有可見性由 isVisible 屬性控制的時(shí)候可靠。

應(yīng)用可以通過在視圖上定義一個(gè)與鉤子同名的方法來實(shí)現(xiàn)鉤子。或者,在視圖上為鉤子 注冊(cè)一個(gè)監(jiān)聽器也是可行的。

view.on('willRerender', function() {   // do something with view });

虛擬視圖

正如上文所述,Handlebars 在視圖層級(jí)內(nèi)創(chuàng)建視圖來表現(xiàn)綁定值。每次你使用 Handlebars 表達(dá)式,無論是一個(gè)簡單值還是一個(gè)諸如 {{#with}}{{#if}} 的 塊表達(dá)式,Handlebars 會(huì)創(chuàng)建一個(gè)新視圖。

因?yàn)?Ember 只把這些視圖用于內(nèi)部簿記,它們對(duì)于視圖的公共 parentViewchildViews API 是隱藏的。公共視圖層級(jí)只反射用 {{view}} 輔助標(biāo)記或通過 ContainerView 創(chuàng)建的視圖(見下)。

例如,考慮下面的 Handlebars 模板:

<h2>Joe's Lamprey Shack</h2> {{controller.restaurantHours}} {{#view App.FDAContactForm}}   如果你在喬的七鰓鰻小屋用餐后不適,請(qǐng)用下面的表格向 FDA 提交申訴。   {{#if controller.allowComplaints}}     {{view Ember.TextArea valueBinding="controller.complaint"}}     <button {{action submitComplaint}}>提交</button>   {{/if}} {{/view}}

渲染這個(gè)模板會(huì)創(chuàng)建這樣的層級(jí): 

 Ember.js的視圖層分析

幕后,Ember 跟蹤為 Handlebars 表達(dá)式創(chuàng)建的額外的虛擬視圖: 

Ember.js的視圖層分析     

TextArea 中, parentView 會(huì)指向 FDAContactForm ,并且 FDAContactFormchildViews 會(huì)是一個(gè)只包含 TextArea 的數(shù)組。

你可以通過 _parentView_childViews 來查看內(nèi)部視圖層級(jí),這會(huì)包含虛擬視 圖:

var _childViews = view.get('_childViews'); console.log(_childViews.objectAt(0).toString()); //> <Ember._HandlebarsBoundView:ember1234>

警告! 你不應(yīng)該在應(yīng)用代碼中依賴于這些內(nèi)部 API。它們會(huì)在任何時(shí)候更改并且 沒有任何公共合約。返回值也不能被觀察或被綁定。它可能不是 Ember 對(duì)象。如果覺 得有使用它們的需求,請(qǐng)聯(lián)系我們,這樣我們可以為你的使用需求暴露一個(gè)更好的公共 API。

底線:這個(gè) API 就像是 XML。如果你覺得你需要用到它,那么你很可能沒有足夠理解 問題。三思!

事件冒泡

視圖的一個(gè)任務(wù)是響應(yīng)原始用戶事件并把它們翻譯成對(duì)你應(yīng)用而言有語義的事件。

例如,一個(gè)刪除按鈕把原始的 click 事件翻譯成應(yīng)用特定的“把這個(gè)元素從數(shù)組中刪 除”。

為了響應(yīng)用戶事件,創(chuàng)建一個(gè)視圖的子類來把事件實(shí)現(xiàn)為方法:

App.DeleteButton = Ember.View.create({   click: function(event) {     var stateManager = this.getPath('controller.stateManager');     var item = this.get('content');     stateManager.send('deleteItem', item);   } });

當(dāng)你創(chuàng)建一個(gè)新的 Ember.Application 實(shí)例,它用 jQuery 的事件委派 API 給每個(gè) 原生瀏覽器事件注冊(cè)一個(gè)事件處理器。當(dāng)用戶觸發(fā)一個(gè)事件,應(yīng)用事件分配器會(huì)找出離 事件最近的視圖并實(shí)現(xiàn)那個(gè)事件。

一個(gè)視圖通過定義與事件同名的方法來實(shí)現(xiàn)事件。當(dāng)事件名稱由多個(gè)詞組成(如 mouseup )方法名會(huì)用 Camel 命名法把事件名作為方法名( mousUp )。

事件會(huì)在視圖層級(jí)中冒泡,直到事件到達(dá)根視圖。一個(gè)事件處理器可以用與常規(guī) jQuery 事件處理器相同的技術(shù)來停止事件傳播:

  • 在視圖中 return false

  • event.stopPropagation

例如,假設(shè)你已經(jīng)定義了如下的視圖類:

App.GrandparentView = Ember.View.extend({   click: function() {     console.log('Grandparent!');   } }); App.ParentView = Ember.View.extend({   click: function() {     console.log('Parent!');     return false;   } }); App.ChildView = Ember.View.extend({   click: function() {     console.log('Child!');   } });

這是使用它們的 Handlebars 模板。

{{#view App.GrandparentView}}   {{#view App.ParentView}}     {{#view App.ChildView}}       <h2>點(diǎn)擊這里!</h2>     {{/view}}   {{/view}} {{/view}}

如果你點(diǎn)擊 <h2> ,你會(huì)在瀏覽器控制臺(tái)里看見下面的輸出:

Child! Parent!

你可以看出 Ember 在接受事件的最深層級(jí)視圖上調(diào)用了處理器。事件繼續(xù)上浮到 ParentView ,但不會(huì)到達(dá) GrandparentView 因?yàn)?ParentView 從它的事件處理 器中返回了 false 。

你可以使用常規(guī)事件冒泡技術(shù)來實(shí)現(xiàn)常見的模式。例如,你可以實(shí)現(xiàn)一個(gè)帶有 submit 方法的 FormView 。因?yàn)闉g覽器在用戶向文本域輸入回車的時(shí)候會(huì)觸發(fā) submit 事件,在表單視圖上定義一個(gè) submit 方法會(huì)“剛好完成任務(wù)”。

  1. App.FormView = Ember.View.extend({ 

  2.   tagName: "form", 

  3.   submit: function(event) { 

  4.     // 會(huì)在任何用戶觸發(fā)瀏覽器的 

  5.     // `submit` 方法時(shí)被調(diào)用 

  6.   } 

  7. });

{{#view App.FormView}}   {{view Ember.TextFieldView valueBinding="controller.firstName"}}   {{view Ember.TextFieldView valueBinding="controller.lastName"}}   <button type="submit">確定</button> {{/view}}

添加新事件

Ember 內(nèi)置了如下原生瀏覽器事件的支持:

事件名

方法名

touchstarttouchStart
touchmovetouchMove
touchendtouchEnd
touchcanceltouchCancel
keydownkeyDown
keyupkeyUp
keypresskeyPress
mousedownmouseDown
mouseupmouseUp
contextmenucontextMenu
clickclick
dblclickdoubleClick
mousemovemouseMove

事件名

方法名

focusinfocusIn
focusoutfocusOut
mouseentermouseEnter
mouseleavemouseLeave
submitsubmit
changechange
dragstartdragStart
dragdrag
dragenterdragEnter
dragleavedragLeave
dragoverdragOver
dropdrop
dragenddragEnd

當(dāng)你創(chuàng)建一個(gè)新應(yīng)用時(shí),你可以向事件分配器添加額外的事件:

App = Ember.Application.create({   customEvents: {     // 添加 loadedmetadata 媒體播放器事件     'loadedmetadata': "loadedMetadata"   } });

要使這能對(duì)自定義事件奏效,HTML5 規(guī)范必須定義事件為“bubbling”,否則 jQuery 必 須為這個(gè)事件提供一個(gè)事件委派折中方案。

模板化視圖

如同迄今你在本指導(dǎo)中所見,你在應(yīng)用中會(huì)用的大多數(shù)視圖是依靠模板的。當(dāng)使用模板 時(shí),你不需要編寫你的視圖層級(jí),因?yàn)槟0鍟?huì)為你創(chuàng)建它。

渲染時(shí),視圖模板可以把視圖附加到它的子視圖數(shù)組中。模板的 {{view}} 輔助標(biāo)記 內(nèi)部會(huì)調(diào)用視圖的 appendChild 方法。

調(diào)用 appendChild 會(huì)做兩件事:

  1. 把視圖添加到 childViews 數(shù)組。

  2. 立即渲染子視圖并把它添加到父視圖的渲染緩沖區(qū)。

Ember.js的視圖層分析

你不應(yīng)該在視圖離開渲染狀態(tài)后調(diào)用 appendChild 。模板渲染出“混合內(nèi)容”(包含 視圖和純文本),所以當(dāng)渲染過程完成后,父視圖不知道到底把新的子視圖插入到哪 里。

在上例中,想象試圖把一個(gè)新視圖插入到父視圖的 childViews 數(shù)組中。它應(yīng)該立即 放在 App.MyView 的閉合標(biāo)簽 </div> 后?還是在整個(gè)視圖的閉合標(biāo)簽 </div> 后?這個(gè)答案不總是正確的。

因?yàn)檫@種含糊性,創(chuàng)建視圖層級(jí)的唯一方法就是用模板的 {{view}} 輔助標(biāo)記,它總 是把視圖插入到相對(duì)任何純文本的正確位置。

雖然這個(gè)機(jī)制對(duì)大多數(shù)情景奏效,偶爾你也會(huì)想要直接程序控制一個(gè)視圖的子視圖。在 這種情況下,你可以用 Ember.ContainerView ,它顯式地暴露了實(shí)現(xiàn)此目的的 API。

容器視圖

容器視圖不包含純文本。它們完全由子視圖(可能依靠模板)構(gòu)成。

ContainerView 暴露兩個(gè)用于修改本身內(nèi)容的公共 API:

  • 一個(gè)可寫的 childViews 數(shù)組,你可以把 Ember.View 實(shí)例插入到其中。

  • 一個(gè) currentView 屬性,設(shè)置時(shí)會(huì)把新值插入到子視圖數(shù)組。如果存在早先的 currentView 值,它會(huì)被從 childViews 數(shù)組刪除。

這里是一個(gè)用 childViews API 創(chuàng)建新視圖的例子,由假想的 DescriptionView 開始,并可以在任何時(shí)候用 addButton 方法添加一個(gè)新按鈕:

App.ToolbarView = Ember.ContainerView.create({   init: function() {     var childViews = this.get('childViews');     var descriptionView = App.DescriptionView.create();     childViews.pushObject(descriptionView);     this.addButton();     return this._super();   },   addButton: function() {     var childViews = this.get('childViews');     var button = Ember.ButtonView.create();     childViews.pushObject(button);   } });

如你在上例中所見,我們以兩個(gè)視圖初始化 ContainerView ,并且可以在運(yùn)行時(shí)添 加額外的視圖。存在一個(gè)方便的捷徑來設(shè)置視圖,而不用覆蓋 init 方法:

App.ToolbarView = Ember.ContainerView.create({   childViews: ['descriptionView', 'buttonView'],   descriptionView: App.DescriptionView,   buttonView: Ember.ButtonView,   addButton: function() {     var childViews = this.get('childViews');     var button = Ember.ButtonView.create();     childViews.pushObject(button);   } });

如上,當(dāng)用這個(gè)速記方法時(shí),你把 childViews 指定為一個(gè)字符串?dāng)?shù)組。在初始化 時(shí),每個(gè)字符串會(huì)作為在查找視圖實(shí)例或類的關(guān)鍵字。那個(gè)視圖會(huì)被自動(dòng)實(shí)例化,如果 必要,會(huì)加入到 childViews 數(shù)組中。 

{{#if controller.isAuthenticated}}   <h2>歡迎 {{controller.name}}</h2> {{/if}} {{#with controller.user}}   <p>你有 {{notificationCount}} 條通知。</p> {{/with}}

在上面的模板中,當(dāng) isAuthenticated 屬性從 false 變?yōu)?true 時(shí),Ember 會(huì) 重新渲染這個(gè)塊,用原始的外部作用域作為它的上下文。

{{#with}} 輔助標(biāo)記把它的塊的上下文修改為當(dāng)前控制器的 user 屬性。當(dāng) user 屬性被修改。Ember 重新渲染塊,并用 controller.user 的新值作為它的上 下文。

視圖作用域

除了 Handlebars 上下文,Ember 中的模板也有當(dāng)前視圖的概念。無論當(dāng)前上下文是什 么, view 屬性總是引用到最近的視圖。

注意 view 屬性不會(huì)引用由 {{#if}} 之類的塊表達(dá)式創(chuàng)建的內(nèi)部視圖。這允許你 區(qū)分 Handlebars 上下文,在 Handlebars 中和在視圖層級(jí)中的工作方式是一樣的。

因?yàn)?view 指向一個(gè) Ember.View 實(shí)例,你可以用 view.propertyName 之類的 表達(dá)式訪問視圖上的任何屬性。你可以用 view.parentView 訪問視圖的父視圖。

例如,想象你有一個(gè)帶有如下屬性的視圖:

App.MenuItemView = Ember.View.create({   templateName: 'menu_item_view',   bulletText: '*' });

&hellip;&hellip;和下面的模板:

{{#with controller}}   {{view.bulletText}} {{name}} {{/with}}

盡管 Handlebars 上下文已經(jīng)變?yōu)楫?dāng)前的控制器,你仍然可以用 view.bulletText 訪問視圖的 bulletText 。

模板變量

迄今為止,我們已經(jīng)在 Handlebars 模板中邂逅了 controller 屬性。它是從哪來的呢?

Ember 中的 Handlebars 上下文可以繼承它們的父上下文中的變量。在 Ember 在當(dāng)前 上下文中查找變量之前,它首先檢查它的模板變量。當(dāng)一個(gè)視圖創(chuàng)建了一個(gè)新的 Handlebars 作用域,它們自動(dòng)繼承它們父作用域的變量。

Ember 定義了這些 viewcontroller 變量,所以當(dāng)一個(gè)表達(dá)式使用 viewcontroller 變量名,它們總是最先被找到。

如上所述,Ember 設(shè)置了 Handlebars 上下文中的 view 變量,無論何時(shí)模板中使用 了 {{#view}} 輔助標(biāo)記。起初,Ember 把 view 變量設(shè)置為正在渲染模板的視 圖。

Ember 設(shè)置了 Handlebars 上下文中的 controller 變量,無論已渲染的視圖是否存 在 controller 屬性。如果視圖沒有 controller 屬性,它從時(shí)間上最近的擁有該 屬性的視圖上繼承 controller 變量。

其它變量

Ember 中的 Handlebars 輔助標(biāo)記也會(huì)指定變量。例如, {{#with controller.person as tom}} 形式指定一個(gè) tom 變量,它的后代作用域 是可訪問的。即使一個(gè)子上下文有 tom 屬性,這個(gè) tom 變量會(huì)廢除它。

這個(gè)形式的最大好處是,它允許你簡寫長路徑,而不喪失對(duì)父作用域的訪問權(quán)限。

{{#each}} 輔助標(biāo)記中,提供 {{#each person in people}} 形式尤其重要。 在這個(gè)形式中,后代上下文可以訪問 person 變量,但在模板調(diào)用 each 的地方 保留相同的作用域。

{{#with controller.preferences}}   <h2>Title</h2>   <ul>   {{#each person in controller.people}}     {{! prefix here is controller.preferences.prefix }}     <li>{{prefix}}: {{person.fullName}}</li>   {{/each}}   <ul> {{/with}}

注意這些變量繼承了 ContainerView 中的那些,即使它們不是 Handlebars 上下文 層級(jí)中的一部分。

從視圖中訪問模板變量

在大多數(shù)情況下,你會(huì)需要從模板中訪問這些模板變量。在一些不尋常的情景下,你會(huì) 想要在視圖的 JavaScript 代碼中訪問范圍內(nèi)的變量。

你可以訪問視圖的 templateVariables 屬性來達(dá)成此目的,它會(huì)返回一個(gè)包含當(dāng)視 圖渲染后存在于其作用于的變量的 JavaScript 對(duì)象。 ContainerView 也可以訪問 這個(gè)屬性,它指向時(shí)間上最近的模板依賴的視圖的模板變量。

目前,你不能觀察或綁定一個(gè)包含 templateVariables 的路徑。

感謝各位的閱讀,以上就是“Ember.js的視圖層分析”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對(duì)Ember.js的視圖層分析這一問題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI