AngularJS_简介、特性及基本使用_及其工作原理
转自:通过<script>标签引入到 HTML 中,那么此时 Angular 就做为一个普通的 DOM 节点等待浏览器解析
当浏览器解析到这个节点时,发现它是一个 js 文件,那么浏览器会停止解析剩余的 DOM 节点,开始执行这个js(即angular.js)
同时 Angular 会设置一个事件监听器来监听浏览器的 DOMContentLoaded 事件
当 Angular 监听到 DOMContentLoaded 事件时,就会启动 Angular 应用
1. 初始化阶段
Angular 开始启动后,它会查找 ng-app 指令
然后初始化一系列必要的组件(即 $injector、$compile 服务以及 $rootScope),接着重新开始解析 DOM 树
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}} 进行替换,于是乎,页面显示数据
- angular 和浏览器进行交互,粗略来讲分为 3 个阶段:
1. 浏览器的事件回路一直等待着事件的触发,事件包括用户的交互操作、定时事件或者网络事件(如服务器的响应等)
2. 一旦有事件触发,就会进入到 Javascript 的 context 中,一般通过回调函数来修改 DOM
3. 等到回调函数执行完毕之后,浏览器又根据新的 DOM 来渲染新的页面
- AngularJS 修改了一般的 Javascript 工作流,并且提供了它自己的事件处理机制
这样就把 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 初始化所有对象)
- API: ng/function/angular.module()____一个参数是获取,多个参数是创建
可以在全局位置创建、注册、获取 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>
- ng-bind 指令 解决数据闪屏:由于用户网速不好,第一次打开页面会看到 {{表达式}}
浏览器引擎从上往下解析
因为浏览器还没有解析到引入的 angular.js,所以页面还没有生效,导致了 数据闪屏
<div>
<p>{{123}}</p>
优化为:
<p ng-bind="123"></p>
</div>
- ng-show 布尔类型,如果为 true 则 显示
- ng-hide 布尔类型,如果为 true 则 隐藏
<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 杨样式 --------------------------------------------------
- ng-style 动态引用 js 指定的的样式对象 {"color":"blue", "backgrouond": "red"};
- ng-mouseenter
- ng-mouseleave
- ng-class 动态引用定义的样式 {"aClass": true, bClass: true};
字数统计,实时显示
-
<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>