Angular簡介(大神可略過)
Angular是一個強大的前端框架,其強大之處主要是可以把靜態頁面與動態數據綁定起來。平時我們看到的網頁界面上面的數據都是固定,但如果我們要變化這些數據,例如我在一個文本框輸入,要實時改動一個文本,腫么破。這時候有兩種方法(我只想到兩種,求大神告知更多):
1.改變一下,就請求一下后端,例如php,然后后端重新返回一個更新好的頁面,當然這種方法很傻,改變一點小數據就請求后端,的確太傻(由於前端小白,我之前就用這種方式做了一個小網站,后來接觸到angular才發現自己太傻);
2.通過js改寫DOM(for 小白:這里的DOM可以理解為html,但官方稱呼叫document object model,小白我以前總是不知道這是啥),js最初的document類就可以干這事兒,后來出現了一個jquery(js的一個強大的庫),也可以方便的改寫DOM,對於這些前端就可以知道的數據就不用請求后端了。
jquery也只是一個庫,提供了一些簡單改變DOM的方法,對於簡單的小工程來說也夠了。但是對於比較大的工程,考慮的不僅是功能的實現,還包括可維護可擴展,這就需要MVC模式了(for小白:至於啥是MVC,可以看我介紹spring框架那篇博客里面)。如果只用jquery,view的邏輯會和c,m的邏輯混在一起,不便於維護,例如你在文本框里數據了一個東西,你得用寫代碼去獲取這個值,然后做處理,或者你的某個值改變了,你還得寫代碼去更新一下view,而angular就是提供這樣一個解決方案的框架(后面還會有介紹感覺angular的強大)。
Angular里面的html文件就是view,叫模板(template),當你的數據變化需要改變模板的時候,不用再js代碼里面去改變,你可以什么都不做,因為angular神奇的地方就是把模板與數據綁定(data binding),當數據改變的時候模板自動就變了,你的view變了(在文本框輸入東西了)也會自動反應到你的數據上面,這就是雙向綁定。在angular的理念里面,模板就是一副素描畫,數據就是顏色,你想做完這幅畫,只需要向模板填充你想要顏色就行了(也就是填充你的數據),例如下面這個例子,你的輸入自動顯示到界面上
http://www.runoob.com/try/try.php?filename=try_ng_intro
你只需要專注你的數據和模板就夠了,他們之間怎么填充,angular把這些做好了,也就是剝離了view層對contorller,mdoel層的影響,下面就是angular官方給出的區別
一般處理數據:
angular:
引用自:https://docs.angularjs.org/guide/databinding
簡單來說就是你用angular了數據和view自動雙向綁定,不用你再代碼中去更新,不用angular你還要自己寫代碼在view變了時候去更新數據,在數據變了的時候去更新view。
angular原理
angular是基於js的一個框架,首先需要了解一下js的工作原理。
JS原理
瀏覽器里面有一個事件隊列(event queue),用戶觸發啥事兒,或者網絡請求,延時操作(例如定時器之類),都是一個event,瀏覽器會輪訓這些事件,然后調用這些回調(這里的回調簡單來說可以理解為觸發一個函數),然后就進入JavaScript的環境中執行(JavaScript context),在這里面可以改變數據,操作DOM(也就是html結構),然后再退出JavaScript環境,又進入瀏覽器環境,然后瀏覽器根據之前的改動重新繪制界面,這就是個一個流程。
下圖為angular 官方解釋(引自https://docs.angularjs.org/guide/scope )
angular原理
angular的運行就是在JavaScript context里面自己實現了一套環境,叫做angular環境(angular context),非angular那部分環境叫經典環境(classic context),
在angular context里面也有一個隊列,這個隊列里面是watch列表,列表里面裝的就是那些被監聽的變量,包括那些進行數據綁定的變量(也就是和view進行綁定的那些)。如果用戶改變了一個綁定了數據的view,這時候會觸發一個angular函數$apply(也就是把這個event放入了event queue,然后輪訓到這個的時候就觸發了),然后把這個改變的值更新進綁定的那個變量,再開始調用一個digest的函數,digest就是用來輪訓這個watch列表,看這個列表中的指是否變動,如果有變動就變動改寫相應的DOM(不用angular就要自己寫這部分代碼,如果你有100個變量,你就要寫100個這種改動,而且如果以后有啥變動,還得自己去重構)。
關於angular原理機制的一些參考:
http://www.cnphp6.com/archives/64167
http://www.tuicool.com/articles/fAfiMv
這里還有兩點注意,
1.angular會至少輪訓兩遍watch列表,為啥?因為第一次輪訓可能在改寫DOM的時候可能會觸發其他watch列表里面的變量變化,這時候還會再輪訓,直到連續兩次輪訓的變量不再變化。所以如果你有兩個變量的變化是相互影響的,就是A變了觸發B變,B變了觸發A變,這樣會引起死循環,angular好像是在輪訓5次(或者是10次,具體我忘了),如果還發現值沒有穩定,就會報錯(我曾經就干過,界面突然卡死,整個瀏覽器都卡死了,好不容易打開控制台看,全部是angular輪訓報的錯,angular的輪訓直接卡死了整個瀏覽器)。
2.另外還有一點,關於效率問題,有人提出來angular這樣無差別輪訓可能會影響性能,但是angular的創始人給了解釋,人能在一個頁面上最多就能看200個元素,在一個web頁面上面不會有這么多的元素綁定數據,如果綁定這么多元素需要實時更新,那屬於網頁設計的問題(引自stackoverflow,具體網址找不到),所以並不用擔心輪訓的效率問題,如果真的有效率問題,說明網頁本身可能存在問題(在豆瓣上看到一篇帖子,一個人用angular測了500個ngModel綁定的頁面,很卡,所以對於不必要的綁定,最好不要綁)
angular組件
Controller
Controller是angular一個重要的組件,基本用angular一定會用到controller。Controller顧名思義,用來控制的,是MVC中的C,邏輯控制。在angular里面,controller是一個JavaScript的構造函數,這個函數有兩個作用,初始化scope,還有就是增加方法(add behavior)。
引自:https://docs.angularjs.org/guide/controller
這里稍微簡單解釋一下scope(后面會說一下),scope是一個對象(object)可以理解為是連接view和controller的一個橋梁,scope的屬性中有一些值,有一些方法(behavior),在html中可以直接訪問到,如下圖,ng-click里面的那些方法都是scope的一個屬性,還有顯示出來的那些值。在scope里面初始化之后,在view(也就是html中,angular官方叫做template)里面就就可以訪問到這些值,也可以觸發這些方法,這也就是angular數據雙向綁定的具體使用(很簡單吧,不用寫一堆jquery了)。
引自:https://docs.angularjs.org/guide/controller
所以controller的作用就在於上面說的,初始化scope和為scope增加方法,同時angular官方也給出了一些不建議使用的方式(如下圖),因為這樣操作基本上都有更好的方式
引自:https://docs.angularjs.org/guide/controller
Tips:
1. angular在1.2版本后多了一個controlleras的語法,這個語法允許為controller起個別名(有木有感覺像sql里面的as),如下圖
如果不用這個語法,需要在controller這個函數里面依賴注入(Dependency Injection,后面也會介紹到,如上面那個例子所示,在函數的參數里面寫個$scope)。如果用這個語法就不用了,兩者的不同就在於不用controller as這個語法,html通過訪問scope的屬性來訪問數據,所以要把給html訪問的數據寫進scope的屬性,如果用controller as,整個controller這個實例(例子中的demo)會作為scope的一個屬性,例如html要訪問一個data屬性的值
Controller as 訪問的是scope.demo.data
Controller 訪問的是scope.data
2. controller繼承,每個controller繼承其實是scope的繼承,可以簡單理解為JavaScript的繼承,具體可以看我另一篇博客(如果有時間發的話T^T),或者是angular官方文檔上面說的。在這里就是如果子controller里面沒有指定這個數據,就會用父類的,如果指定了就用子類的,但是要注意如果是改變model里面的值,有可能改父類的值(下面還會介紹這個概念)
Service
Service可以理解為MVC結構中的M層,來處理具體的業務邏輯,最理想的代碼就是在view里面觸發了controller中的函數,然后controller來調用model里面具體的處理,然后model返回給controller改scope的數據,反應在view上面。Service就是這個作用,在angular里面,service有兩個特點
1. 懶加載(lazy loading):只有在需要用的時候(也就是在其他service,filter,directive或者controller里面依賴注入的時候才會生成這個service實例)
2. 單例模式(singleton):service在angular里面是單例(singleton),只在第一次被注入的時候創建實例,然后存在cache里面,等需要的時候(也就是另外的依賴注入的時候),從cache里面取出。所以service的生命周期只要創建之后,除非app退出,否則一直都有這個實例。不能銷毀(我還沒找到一種手動銷毀的辦法,事實上在網上查了一些需要銷毀的例子,其實都可以用其他方法來做,不一定非要銷毀這個service實例)
http://stackoverflow.com/questions/32781488/how-to-destroy-an-angular-factory-instance
PS:如果在使用過程中需要多例的樣子,可以自己稍微改動一下,把service當做一個factory模式,返回各種需要的實例。可以參考下面的連接(兩個例子其實一樣,只是在service的寫法上有點不同而已,另外提示鏈接里面的網頁在embed標簽欄里面可以看到樣式)
http://jsfiddle.net/rbdmjLok/3/
http://jsfiddle.net/jeremylikness/rbdmjLok/
- 注冊service
在官方給出的developerguide里面主要介紹了兩種方式,factory模式和provider模式,但其實還有一種service模式,所以總共有三種:
1. factory:angular里面比較常用的一種方式,注冊一個function,這個function在生成實例的時候會被調用到,這個函數返回一個service實例(要自己寫return的),所以只要把自己需要的service寫成一個obj,在這個obj里面定義要的方法和值,然后再最后return一下這個obj就可以了,如下圖
2. service:service注冊就更簡單了,相當於對factory做了一層封裝。只要在service里面寫需要的方法和值就行了,不需要return,如下圖
3. provider:provider是angular里面注冊service最底層的方式,無論是service方式注冊還是factory注冊,其實底層都是用的provider這種方式。在provider里面有一個$get的屬性,這個屬性是一個函數,這個函數就是factory里面我們寫的那個函數,用service那種寫法在這里就是new 一個service的那個function賦給它(js里面function也可以看做一個對象的,所以等於直接new了一個對象給$get),angular就是通過在依賴注入(后面會介紹)的時候,調用這個函數獲得一個實例。
如果一個service只有在實例化它之前才知道一個配置,例如讀文件或者網絡返回的一些動態配置,這種就不能在代碼里面寫死,需要傳參數給service初始化(有點類似於Java,C++里面的帶參數的構造函數),這時候就需要在provider里面配置。所以provider就是在service初始化前對service做一些配置的組件(所以叫provider嘛),在provider里面留一個接口(也就是留一個函數),等獲取到需要的配置的時候調provider的這個接口,就可以設置service的參數。注意:這里的provider只是提供了這樣一種方法,可以把它理解為一種工具,config才是調用provider的東東。至於為啥不直接用provider調用,而還要加個config,我一開始想不通,后來問了我boss,他認為這只是為了好理解,讓人一看就知道是配置。后來我也想了一下,應該也是為了方便統一配置。如果直接調provider里面建,要么分開寫,要么還要自己寫一個function在里面配置,angular大概應該是為了提供一個統一並且方便理解的接口吧(個人理解)。具體例子如下(注意:在provider里面寫的名字,在用的時候會在名字后面加一個provider,例如例子中寫的User,但實際調用的那個provider是UserProvider)
在使用以上三種哪種方式,官方文檔似乎沒有給出啥太明顯的建議,個人是這樣認為的,service更傾向於是一種服務,factory傾向於一個工具,provider是需要對這個service做一些配置。下面的博客寫得很好,里面也有介紹啥時候用哪種模式,附上一些參考。
https://my.oschina.net/tanweijie/blog/295067(上面幾張圖引用於此)
https://docs.angularjs.org/guide/services
http://stackoverflow.com/questions/15666048/angularjs-service-vs-provider-vs-factory?rq=1
scope
scope是連接controller和view的橋梁,angular官方是這樣描述的:Scopeis the glue between application controller and the view(如上面的例子可以看出)
- scope對外api:
scope主要提供三個對外API(官方文檔中developer guide里面只提及了兩個)
1.watch:
監聽model是否發生了變化,注意這里的watch提供三種api監聽
(1)(scope.$watch(watchExpression, listener)) :只監聽對應的值或者reference是否變化,如果變化就觸發注冊的回調函數(也就是那個listener)
(2)(scope.$watchCollection(watchExpression, listener)) :監聽對應的值或者reference以及集合里是否發生變化(例如集合增加或者減少,但是不包括集合里面的值變化)
(3) (scope.$watch (watchExpression, listener, true)):監聽對應的值或者reference以及集合里是否發生變化並且還包括里面的值是否發生變化,下圖可以比較清晰的看出其中的區別
2.apply:
在angular context外發布model變化的消息(PS:如果在angular context外變化angular是不會更新界面的,例如用setTimeOut這種方式來更新model,因為setTimeOut只是把一個event放入了隊列里面,不會馬上執行,等到執行注冊timeout的這個function的時候,如果是完全和angular無關的,也就是沒有用到angular的一些內置命令,這是不會觸發進入angular context的,所以這時候的運行完全就是在angular context外,所以即使更新model的數據,也不會在view上面顯示出來,所以要在外面更新一般要自己調用一下apply,具體例子可以參考下面給的那個博客。一般在ng開頭的命令中和angular自帶的一些service里面都會自動調用apply,所以我們不需要去調用)
3.digest:
這個在angular官方文檔中沒有列出來,但其實也是可以直接調用的,官方應該是不推薦這樣做。調用apply就會調用digest,digest會輪訓那些watches(注冊了監聽的那些值的列表),如果發現值變化了會調用watch注冊的那個function來進行一些處理,可以理解為apply->digest->watch
參考自:http://www.cnphp6.com/archives/64167
- scope種類
scope分為兩種,一種是child scope,一種是isolatescope,前者是按照類似DOM結構的繼承關系,后者是完全獨立的(一般用在directive中,因為directive一般是脫離上下文,能夠單獨使用的,例如要做一個通用的列表,在用的時候只需要傳個列表值進來就可以了,這種和上下文無關,所以一般是獨立的,就類似於Android里面的adapter一樣)
- scope繼承
對於child scope的繼承,就和JavaScript的繼承差不多,簡單來說就是如果子scope中沒有的屬性,會去父scope去找,一層一層去找.
如果這時候想賦值,不會改到父scope中的屬性,例如parentScope.a= 1,如果childScope中沒有指定a那么childScope.a也是1,但是如果這時候賦值childScope.a = 2,這時候parentScope.a還是1,為啥,因為那個賦值語句對於JavaScript不是一個改變值的語句,是為childScope創建了一個a的屬性值等於2,所以父parentScop不受影響。但是如果屬性是model(也就是對象)就不一樣了,以為訪問model的時候傳的reference(這里和C++是不一樣的,Java和JavaScript里面都是把類作為reference傳,C++是通過拷貝構造函數拷貝一份,除非修改拷貝構造函數,否則默認是傳值),例如:parentScope.a.value = 1,如果childScope沒有指定,那么childScope.a.value也是1,這時候賦值childScope.a.value = 2,那么這時候父parentScope.a.value也是2。為啥,因為childScope.a是訪問parentScope的屬性(如果childScope里面沒有指定,注意這個前提),由於a是個model(對象),所以訪問的是地址(reference),這時候a.value=2就是對這個地址的值進行了改寫,所以parentScope也會被改變。當然如果先把childScope.a=newA,這樣childScope指向的就不是parentScope的了,這時候再改a的值就不會影響到parentScope了。建議可以在自己在console里面試試(JavaScript中function其實就是類,我的另外一篇博客也介紹了關於JavaScript和angular的繼承關系)
參考自:
https://github.com/angular/angular.js/wiki/Understanding-Scopes
https://docs.angularjs.org/guide/scope
- 追蹤scope
這是angular官方給出的怎么在view中調試scope,也就是看scope當前的一些值
其實還有另一個方法,也就是我們在項目中用到的一個方法,設一個全局變量,然后再每個controller里面都把scope賦值給這個全局變量,這樣可以在console里面從這個全局變量里面看到想追蹤的scope的值了。但是注意:如果這個全局變量只是為了調試,不要在代碼中使用這個全局變量,也就是不要讀取,因為這個存在只是作為調試使用的,是隨時會去掉的一個東西,如果有代碼邏輯依賴這個全局變量的值,在去掉之后會導致錯誤的。所以不要讓代碼依賴一個隨時會去掉的變量。
- Scope事件分發
這個在項目中我們基本沒用過,但angular提供了這個機制,emit和broadcast(做Android的同學應該對這個單詞比較有感覺吧,但其實這類似於Android里面的事件傳遞機制event dispatch,例如touchEvent和clickEvent這類,但angular這個似乎不存在消費,因為項目沒用這個,所以也沒仔細考證,求大神告知)
emit:
釋放事件,當前scope和父scope都可以收到這個事件,如果在對應的scope里面有注冊這個scope的回調,就會調用這個回調函數。
Broadcast:
發布事件,當前scope和子scope都會收到這個事件,如果在對應的scope里面有注冊這個scope的回調,就會調用這個回調函數。
具體例子可參考
https://docs.angularjs.org/guide/scopeScope Events Propagation部分
DependencyInjection(依賴注入)
依賴注入是在很多編程語言和框架中都會提及的一個東西。其實也很好理解,首先什么是依賴,A模塊(例如類,方法),需要用到B模塊中的東西,這時候就說A對B有依賴,例如A類里面有個add的方法,在B類里面需要用到這個這個add的方法,就是B對A有依賴。這時候就需要注入(其實注入也可以理解為初始化這種意思),在Java里面可能就需要new 一個A,在angular里面,就直接用函數參數這種形式來寫,但是要先在其他地方定義這個依賴的類,就是要定義一個service(如上面提到的service那部分),然后再把這個service作為一個函數參數傳進來。這樣就相當於new了這樣一個對象。具體可以參考上面service部分的例子或者angular 官方的例子
依賴注入寫法在angular中有三種:
1.Inline ArrayAnnotation:
在中括號里面用單引號寫上,並且在function的參數里面寫上,而且要注意順序一致
2.$inject PropertyAnnotation
用$inject來寫,同時也要注意參數順序一致
3.ImplicitAnnotation
只在function的參數里面寫,最簡單的一種寫法,但是也是angular官方不推薦的。因為這種寫法在代碼混淆中會出問題,當然也有一個工具解決這個問題,這里就不提及了,詳見angular官方文檔https://docs.angularjs.org/guide/di
另外angular還提供一種嚴苛模式(Android里面其實也有一種嚴苛模式,但是和angular這個不同,Android的同學不要弄混了),不允許Implicit Annotation,一旦用了Implicit Annotation就會報錯,這里也就不介紹了,詳見angular官方文檔同上。另外關於解決依賴的問題,其實有三種方式(如下圖),但angular認為前兩種方式不好,因為前兩種要自己編碼,比較麻煩,特別是在單元測試的時候,所以才用第三種(spring框架也是用的這種方式),angular里面主要就是靠$injector來創建和追蹤依賴,所以減輕了開發者的負擔。更多詳情參考angular官方文檔,這里就不提及了。
template
angular里面的template其實就是html,在angular中,可以用下面四種方式來控制模板(html)的顯示(如圖),都比較簡單,看看例子就知道了,這里就不提及了。angular建議如果是簡單的app,可以把所有html寫在同一個html文件中,然后用directive這些來控制(一般就是index.html),如果比較復雜一點的app,可以把不同的view(也就是html)放在同一個page里面,但是把各自view定義在不同的html文件中,然后通過引用的方式在加載在這個page中(我們的項目就是通過這種方式來做的,其實也可以認為是一個index.html,但是里面的很多view都來自於其他html文件)
參考自: