日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

html中scope的作用,AngularJS 作用域(Scope)

發布時間:2023/12/2 javascript 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 html中scope的作用,AngularJS 作用域(Scope) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

AngularJS 作用域(Scope)

作用域(Scope)

是一個存儲應用數據模型的對象

為 表達式 提供了一個執行上下文

作用域的層級結構對應于 DOM 樹結構

作用域可以監聽 表達式 的變化并傳播事件

作用域有什么

作用域提供了 ($watch) 方法監聽數據模型的變化

作用域提供了 ($apply) 方法把不是由Angular觸發的數據模型的改變引入Angular的控制范圍內(如控制器,服務,及Angular事件處理器等)

作用域提供了基于原型鏈繼承其父作用域屬性的機制,就算是嵌套于獨立的應用組件中的作用域也可以訪問共享的數據模型(這個涉及到指令間嵌套時作用域的幾種模式)

作用域提供了 表達式 的執行環境,比如像 {{username}} 這個表達式,必須得是在一個擁有屬性這個屬性的作用域中執行才會有意義,也就是說,作用域中可能會像這樣 scope.username 或是 $scope.username,至于有沒有 $ 符號,看你是在哪里訪問作用域了

作用域作為數據模型使用

作用域是Web應用的控制器和視圖之間的粘結劑。在Angular中,最直觀的表現是:在自定義指令中,處在模版的 鏈接(linking) 階段時, 指令(directive)會設置一個 $watch 函數監聽著作用域中各表達式(注:這個過程是隱式的)。這個 $watch 允許指令在作用域中的屬性變化時收到通知,

進而讓指令能夠根據這個改變來對DOM進行重新渲染,以便更新已改變的屬性值(注:屬性值就是scope對象中的屬性,也就是數據模型)。

其實,不止上面所說的指令擁有指向作用域的引用,控制器中也有(注:可以理解為控制器與指令均能引用到與它們相對應的DOM結構所處的作用域)。

但是控制器與指令是相互分離的,而且它們與視圖之間也是分離的,這樣的分離,或者說耦合度低,可以大大提高對應用進行測試的工作效率。

注:其實可以很簡單地理解為有以下兩個鏈條關系:

控制器 --> 作用域 --> 視圖(DOM)

指令 --> 作用域 --> 視圖(DOM)

讓我們來看下面一個例子,可以說明作用域作為視圖與控制器的黏合劑:

源碼

Your name:

greet


function MyController($scope) {

$scope.username = 'World';

$scope.sayHello = function() {

$scope.greeting = 'Hello ' + $scope.username + '!';

};

}

效果

Your name:

greet

在上面這個例子中,我們有:

控制器:MyController,它引用了 $scope 并在其上注冊了兩個屬性和一個方法

$scope 對象:持有上面例子所需的數據模型,包括 username 屬性、greeting屬性(注:這是在sayHello()方法被調用時注冊的)和 sayHello() 方法

視圖:擁有一個輸入框、一個按鈕以及一個利用雙向綁定來顯示數據的內容塊

那么具體整個示例有這樣兩個流程,從控制器發起的角度來看就是:

控制器往作用域中寫屬性:給作用域中的 username 賦值,然后作用域通知視圖中的 input 數據變化了,input 因為通過 ng-model 實現了雙向綁定可以知道 username 的變化,進而在視圖中渲染出改變的值,這里是 World

控制器往作用域中寫方法給作用域中的 sayHello() 方法賦值,該方法被視圖中的 button 調用,因為 button 通過 ng-click 綁定了該方法,當用戶點擊按鈕時,sayHello() 被調用,這個方法讀取作用域中的 username 屬性,加上前綴字符串 Hello,然后賦值給在作用域中新創建的 greeting 屬性

整個示例的過程如果從視圖的角度看,那主要是以下三個部分:

input 中的渲染邏輯:展示了通過 ng-model 進行的作用域和 視圖中某表單元素的雙向綁定

根據 ng-model 中的 username 去作用域中取,如果已經有值,那么用這個默認值填充當前的輸入框

接受用戶輸入,并且將用戶輸入的字符串傳給 username,這時候作用域中的該屬性值實時更新為用戶輸入的值

button 中的邏輯

接受用戶單擊,調用作用域中的 sayHello() 方法

{{greeting}} 的渲染邏輯

在用戶未單擊按鈕時,不顯示內容

取值階段:在用戶單擊后,這個表達式會去scope中取 greeting 屬性,而這個作用域和控制器是同一個的(這個例子中),這時候,該作用域下 greeting 屬性已經有了,這時候這個屬性就被取回來了

計算階段:在當前作用域下去計算 greeting 表達式 ,然后渲染視圖,顯示 HelloWorld

經過以上的兩種角度分析示例過程,我們可以知道:作用域(scope)對象以及其屬性是視圖渲染的唯一數據來源。

從測試的角度來看,視圖與控制器分離的需求在于它允許測試人員可以單獨對應用的操作邏輯進行測試,而不必考慮頁面的渲染細節。

it('should say hello', function() {

var scopeMock = {};

var cntl = new MyController(scopeMock);

// 確保username被預先填充為World

expect(scopeMock.username).toEqual('World');

// 確保我們輸入了新的username后得到了正確的greeting值

scopeMock.username = 'angular';

scopeMock.sayHello();

expect(scopeMock.greeting).toEqual('Hello angular!');

});

作用域分層結構

如上所說,作用域的結構對應于DOM結構,那么最頂層,和DOM樹有根節點一樣,每個Angular應用有且僅有一個 root scope,當然啦,子級作用域就和DOM樹的子節點一樣,可以有多個的。

應用可以擁有多個作用域,比如 指令 會創建子級作用域(至于指令創建的作用域是有多種類型的,詳情參加指令相關文檔)。一般情況下,當新的作用域被創建時,它是以嵌入在父級作用域的子級的形式被創建的,這樣就形成了與其所關聯的DOM樹相對應的一個作用域的樹結構。(譯注:作用域的層級繼承是基于原型鏈的繼承,所以在下面的例子中會看到,讀屬性時會一直往上溯源,直到有未知)

作用域的分層的一個簡單例子是,假設現在HTML視圖中有一個表達式 {{name}} ,正如上面解釋過,Angular需要經歷取值和計算兩個階段才能最終在視圖渲染結果。那么這個取值的階段,其實就是根據作用域的這個層級結構(或樹狀結構)來進行的。

首先,Angular在該表達式當前所在的DOM節點所對應的作用域中去找有沒有 name 這個屬性

如果有,Angular返回取值,計算渲染;如果在當前作用域中沒有找到,那么Angular繼續往上一層的父級作用域中去找 name 屬性,直到找到為止,最后實在沒有,那就到達 $rootScope 了

上面一個簡單的例子展示了在作用域分層結構中找屬性,是基于原型繼承的模式。接下來這個demo用一個圖具體展示了作用域的層級結構,讓你可以有更直觀的了解。

源碼

Hello !

  • from
  • .show-scope-demo.ng-scope,

    .show-scope-demo .ng-scope {

    border: 1px solid red;

    margin: 3px;

    }

    function GreetCtrl($scope, $rootScope) {

    $scope.name = 'World';

    $rootScope.department = 'Angular';

    }

    function ListCtrl($scope) {

    $scope.names = ['Igor', 'Misko', 'Vojta'];

    }

    效果

    看到上面的框中,注意,Angular會自動為每個擁有作用域的DOM節點加上 ng-scope 類。上圖中,擁有紅色邊框樣式的節點,就意味著該節點擁有了自己的作用域,無論它是通過什么方式創建的(譯注:上面可以看到有通過控制器創建的新的作用域,也有通過指令如 ng-repeat 創建的)。上例中,ng-repeat 創建的子級作用域是極其必要的,因為每個

    中想要渲染輸出的 {{name}} 顯然是不同的值,那就需要為它們提供不同的作用域。同樣的,Angular在渲染 {{department}} 表達式時,先在當前和 相對應的作用域去找有沒有這個屬性,如果沒有,接著往上找,在這個例子中,直到找到 $rootScope 下時,才找到 department 屬性,然后將其取回,計算,渲染輸出。

    從DOM中抓取作用域

    作用域對象是與指令或控制器等Angular元素所在的DOM節點相關聯的,也就是說,其實DOM節點上是可以抓取到作用域這個對象的(當然,為了調試偶爾會用,一般不用)。

    而對于 $rootScope 在哪里抓呢?它藏在 ng-app 指令所在的那個DOM節點之中,請看更多關于 ng-app 指令。通常,ng-app 放在 標簽中, 當然,如果你的應用中只是視圖的某一部分想要用Angular控制,那你可以把它放在想要控制的元素的最外層。

    那來看看如何在調試的時候抓取作用域吧:

    右鍵選去你想審查的元素,調出debugger,通常F12即可,這樣你選中的元素會高亮顯示(譯注:文檔都看到這的人了,會需要這句提示么?原文檔這是在賣萌么)

    此時,調試器(debugger)允許你用變量 $0 來獲取當前選取的元素

    在console中執行 angular.element($0).scope() 或直接輸入 $scope 即可看到你想要查詢的當前DOM元素節點綁定的作用域了

    基于作用域的事件傳播

    作用域可以像DOM節點一樣,進行事件的傳播。主要是有兩個方法:

    broadcasted :從父級作用域廣播至子級 scope

    emitted :從子級作用域往上發射到父級作用域

    讓我們來看個例子:

    源碼

    Root作用域MyEvent count:

    • $emit('MyEvent')

      $broadcast('MyEvent')

      Middle作用域MyEvent count:

      • Leaf作用域MyEvent count:

    function EventController($scope) {

    $scope.count = 0;

    $scope.$on('MyEvent', function() {

    $scope.count++;

    });

    }

    效果

    譯注:上面例子很簡單,有幾個需要注意的是:

    $emit 和 $broadcast 是直接被寫在 html 模版中的,而不是寫在控制器的 JavaScript代碼中,因為這兩個方法是直接在 $scope 中就有的,

    同一個控制器 EventController 被用在了三個不同的DOM節點中(這是為了省事,通常不這樣寫的)

    上面的事件無非就是點擊兩個按鈕,分別出發廣播/冒泡(發射)事件,然后在各節點設置監聽,這里只要用 $scope.$on() 方法(注:如果在指令中,可能就是 scope.$on()),就可以進行監聽了

    作用域的生命周期

    作用域的執行上下文

    譯注:這個小節應該是在看完下個小節的基礎上再回過來看這個,所以建議先看下個小節:scope生命周期拆解。由于要遵從原文檔的大體順序,所以順序沒做改動。

    瀏覽器接收一個事件的標準的工作流程應該是:

    接收事件-->觸發回調-->回調執行結束返回-->瀏覽器重繪DOM-->瀏覽器返回等待下一個事件

    上面的過程中,如果一切都發生在Angular的執行上下文的話,那相安無事,Angular能夠知道數據模型發生的改變;但是如果當瀏覽器的控制權跑到原生的 JavaScript中去時(譯注:比如通過jQuery監聽事件之類的非Angular的回調等),那么應用執行的上下文就發生在Angular的上下文之外了,這樣就導致Angular無法知曉數據模型的任何改變。想要讓Angular重新掌權并知曉正在發生的數據模型的變化的話,那就需要通過使用 $apply 方法讓上下文執行環境重新進入到Angular的上下文中(注:用法 $scope.$apply())。只有執行上下文重新回到Angular中,那樣數據模型的改變才能被Angular所識別并作出相應操作(注:當然,如果執行上下文沒有發生改變,也就沒有必要顯式地去進行 $apply 操作)。舉個例子,像 ng-click 這個指令,監聽DOM事件時,表達式的計算就必須放在 $apply() 中(注:例子不夠完備,待補充)。

    在計算完表達式之后,$apply() 方法執行Angular的 $digest 階段。

    在 $digest 階段,scope 檢查所有通過 $watch() 監測的表達式(或別的數據)并將其與它們自己之前的值進行比較。這就是所謂的 臟值檢查(dirty checking)。

    另外,需要注意的是,$watch() 的監測是異步執行的。這就意味著當給一個作用域中的屬性被賦值時,如:$scope.username="angular",$watch() 方法不會馬上被調用,它會被延遲直到 digest() 階段跑完

    (注:至于 $digest 階段到底是干嘛的,你可以認為就是個緩沖階段,而且是必要的階段)。

    通過 $digest() 給我們提供的這個延遲是很有必要的,也正是應用程序常常想要的(注:出于性能的考慮),因為有這個延遲,我們可以等待幾個或多個數據模型的改變/更新攢到一塊,

    合并起來放到一個 $watch() 中去監測,而且這樣也能從一定程度上保證在一個 $wathc() 在監測期間沒有別的 $watch() 在執行。

    這樣,當前的 $watch() 可以返回給應用最準確的更新通知,進而刷新試圖或是進入一個新的 $digest() 階段。

    (譯注:這一段有點晦澀,可以看下面的一張圖結合著學習;還有就是可以把整個過程想象為為了提升效率,

    把多個同性質的數據放在同一個 $digest 輪循中處理能夠大大提高效率,就像zf辦事經常這樣,當然,它們的效率不高,ng則不同,效率相對高)

    scope生命周期拆解

    相信看了上面一段話,沒理解的還是很多人,因為標題雖說是講作用域的生命周期,但是一上來就跟我講的是關于Angular的執行上下文,怎么也沒聯系到一塊。說實話,翻譯這段,真心有點要命的感覺。當然,把它拆分成多個步驟來看,相信會更清晰,因為下面我們是真要講作用域的生命周期,讓我們來過一遍。

    創建期

    root scope 是在應用程序啟動時由 $injector 創建的。另外,在指令的模版鏈接階段(template

    linking),指令會創建一些新的子級 scope。

    注冊$watch

    在模版鏈接階段(template linking),指令會往作用域中注冊 監聽器(watch),而且不止一個。這些 $watch 用來監測數據模型的更新并將更新值傳給DOM。

    數據模型變化

    正如上面一節所提到的,要想讓數據模型的變化能夠很好的被Angular監測,需要讓它們在 scope.$apply() 里發生。

    當然,對于Angular本身的API來講,無論是在控制器中做同步操作,還是通過 $http 或者 $timeout 做的非同步操作,

    抑或是在Angular的服務中,是沒有必要手動去將數據模型變化的操作放到 $apply() 中去的,因為Angular已經隱式的為我們做了這一點。

    數據模型變化監測

    在把數據變化 $apply 進來之后,Angular開始進入 $digest 輪循(就是調用 $digest() 方法),首先是 rootscope 進入 $digest ,然后由其把各個監聽表達式或是函數的任務傳播分配給所有的子級作用域,那樣各個作用域就各司其職了,如果監聽到自己負責的數據模型有變化,馬上就調用 $watch 。(譯注:這里所說的從根scope往下分發是譯者自己的想法,如有錯誤,請糾正)

    銷毀作用域

    當子級作用域不再需要的時候,這時候創建它們的就會負責把它們回收或是銷毀(注:比如在指令中,創建是隱式的,銷毀可以不但可以是隱式的,也可以是顯式的,如 scope.$destroy())。銷毀是通過 scope.$destroy() 這個方法。銷毀之后,$digest() 方法就不會繼續往子級作用域傳播了,這樣也就可以讓垃圾回收系統把這一個作用域上用來存放數據模型的內存給回收利用了。

    作用域和指令

    在編譯(或說解析)階段, 編譯器在HTML解析器解析頁面遇到非傳統的或是自己不能識別的標簽或別的表達式時,Angular編譯器就將這些HTML解析器不懂的東西(其實就是指令)在當前的DOM環境下解析出來。通常,指令分為兩種,一種就是我們常說的指令,另外一種就是我們通常叫它Angular表達式的雙大括號形式,具體如下:

    監測型 指令 ,像雙大括號表達式 {{expression}} 。這種類型的指令需要在 $watch() 方法中注冊一個監聽處理器(譯注:隱式還是顯式的需要看執行上下文),來監聽控制器或是別的操作引起的表達式值改變,進而來更新視圖。

    監聽型 指令 ,像 ng-click , 這種是在HTML標簽屬性中直接寫好當 ng-click 發生時調用什么處理器,當DOM監聽到 ng-click 被觸發時,這個指令就會通過 $apply() 方法執行相關的表達式操作或是別的操作進而更新視圖。

    綜上,無論是哪種類型的指令,當外部事件(可能是用戶輸入,定時器,ajax等)發生時,相關的 表達式 必須要通過 $apply() 作用于相應的作用域,這樣所有的監聽器才能被正確更新,然后進行后續的相關操作。

    可以創建作用域的指令

    大多數情況下, 指令和作用域相互作用,但并不創建作用域的新實例。但是,有一些特殊的指令,如 ng-controller 和 ng-repeat 等,則會創建新的下級作用域,并且把這個新創建的作用域和相應的DOM元素相關聯。如前面說過的從DOM元素抓取作用域的方式(如果你還記得的話),就是調用 angular.element(aDomElement).scope() 方法。

    作用域與控制器

    作用域和控制器的交互大概有以下幾種情況:

    控制器通過作用域對模版暴露一些方法供其調用,詳情見 ng-controller

    控制器中定義的一些方法(譯注:行為或操作邏輯)可以改變注冊在作用域下的數據模型(也就是作用域的屬性)

    控制器在某些場合可能需要設置 監聽器 來監聽作用域中的數據模型(model)。這些監聽器在控制器的相關方法被調用時立即執行。

    作用域$watch 性能

    因為在Angular中對作用域進行臟值檢查($watch)實時跟蹤數據模型的變化是一個非常頻繁的操作,所以,進行臟值檢查的這個函數必須是高效的。一定要注意的是,用 $watch 進行臟值檢查時,一定不要做任何的DOM操作,因為DOM操作拖慢甚至是拖垮整體性能的能力比在 JavaScript對象上做屬性操作高好幾個數量級。

    與瀏覽器事件輪循整合

    下圖與示例描述了Angular如何與瀏覽器事件輪循進行交互。

    瀏覽器的事件輪循等待事件到來,事件可以是用戶交互,定時器事件,或是網絡事件(如 ajax 返回)

    事件發生,其回調被執行,回調的執行就使得應用程序的執行上下文進入到了 JavaScript 的上下文。然后在 JavaScript的上下文中執行,并修改相關的DOM結構

    一旦回調執行完畢,瀏覽器就離開 JavaScript的上下文回到瀏覽器上下文并基于DOM結構的改變重新渲染視圖

    講了那么多些,那么Angular是怎么在這里橫插一杠呢?看圖,Angular是插進了 JavaScript的上下文中,通過提供Angular自己的事件處理輪循來改變正常的JavaScript工作流。它其實是把JavaScript上下文很成了兩塊:一個是傳統的JavaScript執行上下文(圖中淺藍色區域),一個是Angular的執行上下文(圖中淡黃色區域)。

    只有在Angular上下文執行的操作才會受益于Angular的數據綁定,異常處理,屬性檢測,等等。當然,如果不在Angular的上下文中,你也可以使用 $apply() 來進入Angular的執行上下文。

    需要注意的是,$apply() 在Angular本身的很多地方(如控制器,服務等)都已經被隱式地調用了來處理事件輪循。

    顯示地使用 $apply() 只有在你從 JavaScript上下文或是從第三方類庫的回調中想要進入Angular時才需要。讓我們來看看具體的流程:

    進入Angular執行上下文的方法,調用 scope.$apply(stimulusFn) 。上面 $apply() 中的參數 stimulusFn 是你想要讓它進入Angular上下文的代碼

    進入 $apply() 之后,Angular執行 stimulusFn() ,而這個函數通常會改變應用程序的狀態(可能是數據,或是方法調用等)

    之后,Angular進入 $digest 輪循。這個輪循是由兩個較小的輪循構成,一個是處理 $evalAsync 隊列(異步計算的隊列),另一個是處理 $watch 列表。 $digest 輪循不斷迭代變更(在 $eval 和 $watch 之間變更)直到數據模型穩定,這個狀態其實就是 evalAsync 隊列為空且$watch 列表不再監測到變化為止。(譯注:其實這里就是所有外來的異步操作堆起來成為一個隊列,由$eval一個個計算,然后 $watch 看一下這個異步操作對應的數據模型是否還有改變,有改變,就繼續 $eval 這個異步操作,如果沒改變,那就拿異步操作隊列里的下個異步操作重復上述步驟,直到異步操作隊列為空以及 $watch 不再監測到任何數據模型變化為止)

    $evalAsync 隊列是用來安排那些待進入Angular$digest 的異步操作,這些操作往往是在瀏覽器的視圖渲染之前,且常常是通過 setTimeout(0) 觸發。但是用 setTimeout(0) 這個方法就不得不承受緩慢遲鈍的響應以及可能引起的閃屏(因為瀏覽器在每次事件發生后都會渲染一次)(譯注:這里個人覺得不要理解的太復雜,按照上面第三點理解就夠用了,這邊個人翻譯的也不是太好,后期配以例子完善)

    $watch 列表則是存放了一組經過 $eval 迭代之后可能會改變的Angular的表達式集合。如果數據模型變化被監測到,那么 $watch 函數被調用進而用新值更新DOM。

    一旦Angular的 $digest 輪循完成,那么應用程序的執行就會離開Angular及 JavaScript的上下文。然后瀏覽器重新渲染DOM來反映發生的變化

    接下來是傳統的 Helloworld 示例(就是本節的第一個例子)的流程剖析,這樣你應該就能明白整個例子是如何在用戶輸入時產生雙向綁定的。

    編譯階段:ng-model 和 input 指令 在 標簽中設置了一個 keydown 監聽器

    在{{greeting}} 插值(也就是表達式)這里設置了一個 $watch 來監測 username 的變化

    執行階段:在 輸入框中按下 'X' 鍵引起瀏覽器發出一個 keydown 事件

    input 指令捕捉到輸入值的改變調用 $apply("username = 'X';") 進入Angular的執行環境來更新應用的數據模型

    Angular將 username='X'; 作用在數據模型之上,這樣 scope.username 就被賦值為 'X' 了

    $watch 列表中監測到 username 有一個變化,然后通知 {{greeting}} 插值表達式,進而更新DOM

    執行離開Angular的上下文,進而 keydown 事件結束,然后執行也就退出了 JavaScript的上下文;這樣 $digest 完成

    瀏覽器用更新了的值重新渲染視圖

    總結

    以上是生活随笔為你收集整理的html中scope的作用,AngularJS 作用域(Scope)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。