轉自:angularJS 的工做原理javascript
轉自:經過<script>標籤引入到 HTML 中,那麼此時 Angular 就作爲一個普通的 DOM 節點等待瀏覽器解析css
當瀏覽器解析到這個節點時,發現它是一個 js 文件,那麼瀏覽器會中止解析剩餘的 DOM 節點,開始執行這個js(即angular.js)html
同時 Angular 會設置一個事件監聽器來監聽瀏覽器的 DOMContentLoaded 事件前端
當 Angular 監聽到 DOMContentLoaded 事件時,就會啓動 Angular 應用java
1. 初始化階段angularjs
Angular 開始啓動後,它會查找 ng-app 指令ajax
而後初始化一系列必要的組件(即 $injector、$compile 服務以及 $rootScope),接着從新開始解析 DOM 樹express
2. 編譯、連接階段後端
$compile 服務經過遍歷 DOM 樹的方式查找有聲明指令的 DOM 元素數組
當碰到帶有一個或多個指令的 DOM 元素時,它會排序這些指令(基於指令的priority優先級)
而後使用 $injector 服務查找 和 收集指令的 compile 函數並執行它
每一個節點的編譯方法運行以後,$compile 服務就會調用連接函數。這個連接函數爲綁定了封閉做用域的指令設置監控。這一行爲會建立實時視圖
最後,在$compile 服務完成後,AngularJS 運行時就準備好了
3. 運行階段
Angular提供了本身的事件循環
指令自身會註冊事件監聽器,所以當事件被觸發時,指令函數就會運行在 AngularJS 的 $digest 循環中
$digest 循環會等待 $watch 表達式列表
當檢測到模型變化後,就會調用 $watch 函數,而後再次查看 $watch 列表以確保沒有模型被改變
一旦 $digest 循環穩定下來,而且檢測到沒有潛在的變化了,執行過程就會離開 Angular 上下文而且一般會回到瀏覽器中,DOM 將會被渲染到這裏
當你用瀏覽器去訪問 index.html 的時候,angularJS 作了如下事情去渲染頁面:
1. 加載 html,而後解析成 DOM
2. 加載 angular.js 腳本
3. AngularJS 等待 DOMContentLoaded 事件的觸發
4. AngularJS 尋找 ng-app 指令,根據這個指令肯定應用程序的邊界
5. 使用 ng-app 中指定的模塊配置$injector
6. 使用 $injector 建立 $compile 服務 和 $rootScope
7. 使用 $compile 服務編譯 DOM 並把它連接到 $rootScope 上
8. ng-init 指令對 scope 裏面的變量 xxx 進行賦值
9. 對錶達式 {{xxx}} 進行替換,因而乎,頁面顯示數據
1. 瀏覽器的事件迴路一直等待着事件的觸發,事件包括用戶的交互操做、定時事件或者網絡事件(如服務器的響應等)
2. 一旦有事件觸發,就會進入到 Javascript 的 context 中,通常經過回調函數來修改 DOM
3. 等到回調函數執行完畢以後,瀏覽器又根據新的 DOM 來渲染新的頁面
這樣就把 Javascript 的 context 分隔成兩部分
一部分是原生的 Javascript 的 context
另外一部分是 AngularJS 的 context
只有處在 AngularJS 的 context 中的操做才能享受到 Angular 的 data-binding、exception handling、property watching 等服務
可是對於外來者(如原生的 Javascript 操做、自定義的事件回調、第三方的庫等)Angular 也不是一律不接見
可使用 AngularJS 提供的 $apply() 函數,將這些外來者包進 AngularJS 的 context 中,讓 Angular 感知到他們產生的變化
1. 首先,瀏覽器會一直處於監聽狀態,一旦有事件被觸發,就會被加到一個 event queue 中,event queue 中的事件會一個一個的執行
2. event queue 中的事件若是是被 $apply() 包起來的話,就會進入到 AngularJS 的 context 中,這裏的 fn() 是咱們但願在 AngularJS 的 context 中執行的函數
3. AngularJS 將執行 fn() 函數,一般狀況下,這個函數會改變應用的某些狀態
4. 而後 AngularJS 會進入到由兩個小循環組成的 $digest 循環中
一個循環是用來處理 $evalAsync 隊列的
用來 schedule 一些須要在渲染視圖以前處理的操做,一般經過 setTimeout(0) 實現
速度會比較慢,可能會出現視圖抖動的問題
一個循環是處理 $watch 列表的
是一些表達式的集合,一旦有改變發生,那麼 $watch 函數就會被調用
$digest 循環會一直迭代知道 $evalAsync 隊列爲空而且 $watch 列表也爲空的時候,即 model 再也不有任何變化。
5. 一旦 AngularJS 的 $digest 循環結束,整個執行就會離開 AngularJS 和 Javascrip 的 context,緊接着瀏覽器就會把數據改變後的視圖從新渲染出來
這段代碼有了一個 input 來接收用戶的輸入
在用瀏覽器去訪問這個 html 文件的時候,input上的 ng-model 指令會給 input 綁上keydown事件
而且會給 name 變量創建一個 $watch 來接收變量值改變的通知。
在交互階段主要會發生如下一系列事件:
1. 當用戶按下鍵盤上的某一個鍵的時候(好比說A),觸發 input 上的 keydown 事件
2. input 上的指令察覺到 input 裏值的變化,調用 $apply(「name=‘A'」) 更新處於 AngularJS 的 context 中的 model
3. AngularJS 將'A'賦值給 name
4. $digest 循環開始,$watch 列表檢測到 name 值的變化,而後通知 {{name}} 表達式,更新 DOM
5. 退出 AngularJS 的 context,而後退出 Javascript 的 context 中的 keydown 事件
6. 瀏覽器從新渲染視圖
Google 推出的開源的前端 JS 結構化框架,主體是 頁面中的動態數據,與內存的讀取
相較於 jQuery
jQuery 是前端函數庫,封裝簡化 DOM 操做
應用:
構建單頁面 SPA Web 應用____Single Page Application
將全部的活動侷限於一個 html 頁面 (即便頁面跳轉了,也是在本頁面跳轉)
當頁面中有部分數據發生了變化,不會刷新整個頁面,而是局部刷新
利用的就是 ajax 技術,路由
Web App 應用
後臺管理應用: 阿里雲、土豆後臺、惟品會... ...
特性:
雙向數據綁定
聲明式依賴注入
解耦應用邏輯, 數據模型和視圖
完善的頁面指令
定製表單驗證
Ajax 封裝
老版本 angular-1.2.xx
新版本 angular-1.5.xx
輸入框的內容,實時顯示到下方:
$(function(){ $('input').keyup(function(){ // 不能使用 change,在失去焦點時觸發 $('span').html(this.value); }); }); /*************** angular-1.2***************/
<body ng-app> 是 <body ng-app=""> 的簡寫____指向建立模塊的名字
angularJS 沒必要寫一行 js 代碼,便可實現,且速度更快
使用:(ng 是核心模塊,其餘都是功能擴展模塊)
1. 引入 angular.js
<script src='./js/angular-1.2.29/angular.js'></script>
2. ng-app 指令,一般 <body ng-app>____使用 插件 ng-inspector 進行數據查看
① 告訴 angular 核心,管理 當前標籤 所包含的整個區域
② 而且自動建立 $rootScope 根做用域對象
3. 在管理的標籤區域內使用 angularJS
ng-model 將當前輸入框的值 與 xx 關聯(屬性名: 屬性值),而且做爲當前做用域對象 $rootScope 的屬性
{{表達式}} 顯示數據,從 當前做用域對象 $rootScope 的指定屬性名上取
一般有一個返回值,能夠放到任何須要的位置,
剖析:
數據流向
① 頁面上初始化時無數據____ng-model 是雙向數據綁定
首先是從頁面流向內存,再從內存流向頁面的 angular 表達式 和 angular 指令
② 頁面上初始化時有數據____ng-init="username='SunWuKong'"
ng-init 是單向數據綁定,從頁面 view 流向 model 內存
{{表達式}} 也是單向數據綁定,從內存 model 流向 view 頁面
下載: ng-inspector 插件打開的就是 angularJS 的 內存____最大的對象就是 $rootScope
數據綁定:
數據從一個地方 A 移動(傳遞)到另外一個地方 B____這個過程由框架去完成
雙向數據綁定(視圖 view <----> model 模型)---- (網頁 <----> 內存)____頁面主要就是 angular 指令 和 angular 表達式
數據能夠從 view 流向 model,也能夠從 model 流向 view
使用 ng-model 使得 頁面 的數據存儲到 內存
單向數據綁定
View-->Model : ng-init
Model-->View : {{表達式}} 去內存中獲取數據,渲染到頁面
依賴注入
依賴對象: 完成某個特定功能必需要某個對象才能實現____好比 定義子對象,必須形參 $scope
依賴注入: 依賴對象 以形參的形式 被注入進來使用____聲明式依賴注入 (對應的一個叫 命令式依賴注入'遍歷指定數組每一個元素')
命令式 更注重過程 (解答題)
聲明式 更注重結果,是對命令式的局部包裝 (選擇題)
又因爲代碼壓縮,會改變 形參 的命名,因此必須使用 ["形參", function(形參){...}] 顯示聲明依賴注入
M: Model, 即模型, 儲存數據的容器, 提供操做數據的方法
在 angular 中爲 scope
V: View, 即視圖, 顯示 Model 的數據, 將數據同步到 Model, 與用戶交互
在 angular 中爲 頁面,包括: html/css/directive/expression
C: Controller, 即控制器, 初始化 Model 數據, 爲 Model 添加行爲方法
在 angular 中爲 angular 的 ng-controller
在 MVVM 中 angular 的 controller 再也不是架構的核心
angular 的 controller 只是起輔助做用,用來輔助 $scope 對象,即 VM 層
M: Model, 即數據模型
在 angular 中爲 scope 中的各個數據對象
V: View, 即視圖
在 angular 中爲 頁面
VM: ViewModel, 即視圖模型
在 angular 中爲 scope 對象
根做用域對象
一個 js 實例對象,ng-app 指令會默認建立一個 根做用域對象 $rootSScope
做用域對象
根做用域對象的 屬性和方法 與頁面中的 指令/表達式 是關聯的
控制器對象
用於控制 angularJS 應用數據的 實例對象
ng-controller 指定控制器構造函數____形參必須是 $scope____依賴注入
angular 會自動 new 這個構造函數建立控制器對象
同時 還會建立一個新的做用域對象 $scope , 它是 $rootScope 的 (繼承關係)子對象____$scope____做用域對象
(不可能用 ng-init 初始化全部對象)
能夠在全局位置建立、註冊、獲取 Angular 模塊
全部模塊都必須使用這個機制註冊 才能在應用中生效
使用:
1. 建立模塊對象 <body ng-app="myApp">____ng-app 指向建立模塊的名字
var myModule = angular.module("myApp", []);
2. 生成做用域對象____取代 自定義構造函數
myModule.controller("myController1", function($scope){
$scope.empName = "SunWuKong";
});
myModule.controller("myController2", function($scope){
$scope.empName = "ZhuBaJie";
});
--------------------- 優化 ----------------------鏈式調用 (返回值爲 做用域對象)
angular.module("myApp", []).controller("myController1", function($scope){
$scope.empName = "SunWuKong";
}).controller("myController2", function($scope){ // 隱式聲明依賴注入
$scope.empName = "ZhuBaJie";
});
注意:
js 代碼壓縮時,會改變形參,致使 angular 沒法解析
解決: 顯示聲明依賴注入
angular.module("myApp", []).controller("myController1", ['$scope', function($scope){
$scope.empName = "SunWuKong";
}]).controller("myController2", ['$scope', function($scope){
$scope.empName = "ZhuBaJie";
}]);
單項數據綁定
若是是變量 會在做用域鏈中尋找變量 {{abc.split("").reverse().join("")}}____ 字符串反轉
還能夠是 {{123}}、{{'abc'}}、{{true}}
{{null}}、{{undefine}}、{{NaN}}、{{Infinity}} 會被解析成 空串 "",不顯示任何內容
一個項目下來,首先是 UI 設計頁面效果
UI 從 14 年開始火,比 美工 高一個檔次
而後前端工程師分析 UI 設計圖,進行靜態 html 頁面設計
接下來是,數據的動態展示,與後端交互
而後是用戶的交互,數據的展示
angularJS 開發第一步就是 新建 ng-app="myApp"
ng-controller 實現子做用域對象 $scope
ng-model 實現雙向數據綁定
{{表達式}} 從內存顯示數據到頁面
<button ng-click="getTotalPrice()">計算</button>
<div>
<h2>人員信息列表</h2>
<ul>
<li ng-repeat="person in persons">
{{$index}} ---- {{person.username}} ---- {{person.age}}
{{$first}} ---- 第一個 返回 true
{{$last}} ---- 最後一個 返回 true
{{$odd}} ---- 奇數返回 true,從 1 開始計算
{{$even}} ---- 偶數返回 true,從 1 開始
</li>
</ul>
</div>
瀏覽器引擎從上往下解析
由於瀏覽器尚未解析到引入的 angular.js,因此頁面尚未生效,致使了 數據閃屏
<div>
<p>{{123}}</p>
優化爲:
<p ng-bind="123"></p>
</div>
<div>
<button ng-click="switchLike()">切換喜歡</button>
<p ng-show="isLike">我喜歡劉亦菲</p>
<p ng-hide="isLike">劉亦菲喜歡我</p>
</div>
------------------- javascript ------------------
$scope.isLike = true;
$scope.switchLike = function(){
$scope.isLike = !$scope.isLike ;
};
---------------------------------------- 控制 css 楊樣式 --------------------------------------------------
字數統計,實時顯示
<html> <head> <meta charset="UTF-8"> <style type="text/css"> textarea { resize: none; } </style> </head> <body ng-app="myApp"> <div ng-controller="MyCtrl" > <h2>個性簽名:</h2> <textarea cols="30" rows="10" ng-model="words"></textarea> <div> <button ng-click="saveWords()">保存</button> <button ng-click="readWords()">讀取</button> <button ng-click="clearWords()">刪除</button> </div> <p>剩餘字數: <span ng-bind="getCount()"></span></p> </div> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.5.10/angular.js"></script> <script type="text/javascript"> var limitLength = 100; // 字數限制 100 字 //window.onDOMContentLoaded = function(){ // 會報錯 ---- 看上面的工做原理 angular.module("myApp", []).controller("MyCtrl", ["$scope", function($scope){ $scope.words = "哈哈"; $scope.getCount = function(){ $scope.wordsLength = limitLength - $scope.words.length; if($scope.words.length > 100){ // 超出字數限制,讓用戶輸入失效 $scope.words = $scope.words.slice(0, limitLength); }; return $scope.wordsLength; }; $scope.saveWords = function(){ sessionStorage.setItem("session_key", JSON.stringify($scope.words)); console.log("已經保存到 sessionStorage "); }; $scope.readWords = function(){ $scope.words = JSON.parse(sessionStorage.getItem("session_key")) || ""; // 若是讀不到,則返回 null console.log("已經讀取 sessionStorage "); }; $scope.clearWords = function(){ sessionStorage.removeItem("session_key"); $scope.words = ""; }; }]); //}; </script> </body> </html>
數據的動態展現
<html> <head> <meta charset="UTF-8"> <style type="text/css"> textarea { resize: none; } </style> </head> <body ng-app="myApp"> <div ng-controller="MyCtrl" > <h2>Hero </h2> <div> <input type="text" ng-model="heroName"/> <button ng-click="addHero()">添加</button> </div> <div ng-repeat="each in heros"> <input type="checkbox" ng-model="each.isChecked" /><span ng-bind="each.name"></span> </div> <button ng-click="removeHreo()">刪除所選對象</button> </div> <script type="text/javascript" src="https://cdn.bootcss.com/angular.js/1.5.10/angular.js"></script> <script type="text/javascript"> // window.onDOMContentLoaded = function(){ // 會報錯 ---- 看上面的工做原理 angular.module("myApp", []).controller("MyCtrl", ["$scope", function($scope){ $scope.heros = [ {"name": "Groot", "isChecked": false}, {"name": "玩錘子的", "isChecked": false}, {"name": "浩克", "isChecked": false}, {"name": "洛基", "isChecked": false} ]; $scope.addHero = function(){ var isExit = false; $scope.heros.forEach(function(each, index){ if(each.name === $scope.heroName){ isExit = true; $scope.heroName = ""; }; }); if($scope.heroName && !isExit){ $scope.heros.unshift({"name": $scope.heroName, "isChecked": false}); $scope.heroName = ""; }; }; /**** // 方式1 ---- 刪除 數組中不符合條件的元素 $scope.removeHreo = function(){ $scope.heros.forEach(function(each, index){ if(each.isChecked){ $scope.heros.splice(index, 1); // 從 index 開始刪,刪 1 個 $scope.removeHreo(); }; }); }; ****/ // 方式2 ---- 保留 數組中符合條件的元素 $scope.removeHreo = function(){ var tempArr = $scope.heros; $scope.heros = []; tempArr.forEach(function(each, index){ if(!each.isChecked){ $scope.heros.push(each); }; }); }; }]); // }; </script> </body> </html>