您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 订阅
  捐助
angular知识体系
 
   次浏览      
 2019-8-2 
 
编辑推荐:
本文来自简书,什么是 Angular,我们为什么要学习它啊?那么今天就让我们认识一下什么是Angular,Angular是一种用于创建单一应用程序界面的前端框架,它有许多核心功能例如数据绑定,服务,指令以及依赖注入等等

一.angular 基本概念

1.类库( 提供类方法 ) 和框架

类库提供一系列的函数和方法的合集,能够加快你写代码的速度。但是主导逻辑的还是自己的代码。常用的类库 eg: jquery

框架 特殊的已经实现了 web 的应用。只需要按照其逻辑填充你的业务逻辑就能得到完整的应用

2.angular 的特点

提供端对端的解决方案

构建一个 CRUD(add retrieve update delete) 应用的全部内容:`数据绑定,表单验证,路由,深度链接,组件重用,依赖注入`

测试方案: `单元测试, 端对端测试,模拟和自动化测试`

具有各种种子应用作为模板和起点

特点

angular 主要考虑构建 CRUD 应用,并不是所有的应用都适合使用 angular 来构建

例如游戏,图形编辑界面就不适合使用 angular

angular 的标榜概念

angular 认为声明式的代码比命令式的代码更加符合 构建 (视图 + 软件)逻辑的代码

声明式的语言 :提前将所有的操作内置,使用时只需要按照规定声明该操作,语言或者机器本身可以进行构建应用

声明式的语言介绍:HTML 就是声明式的结构,比如需要某个元素居中,不需要告诉浏览器具体的行为(需要找到元素的中间位置,将元素放在那里),只需要添加一个 align='center' 的属性给新元素的可以了。这就是声明式的语言

声明式的语言也有不好的地方,就是所有可以使用的操作已经提前内置,所以他不能识别新的语法结构,比如你想让元素居左 1/3 处就很难处理将 DOM 操作和应用逻辑解耦

将测试和开发同等看待

大幅度减少应用中需要使用的各种 回调的逻辑,摆脱大量的回调逻辑

解放DOM 操作,

对页面的UI操作可控,例如大量的DOM事件

angular 已经有了许多搭建好的基础服务框架

4.angular 的初始化信息

angular 会在 DOMContentLoaded 事件触发时执行, 通过 ng-app 指令 寻找你的应用的根作用域

1. 首先载入和指令相关的模块

2. 穿件应用的 注入器(injector)

3. 将 ng-app 作为根节点编译 DOM 。

也可以使用 angular.bootstrap( 节点 ) 来手动装载节点

二. angular 的指令

指令的定义:由一个新的属性,元素名称,css类名等带来DOM 样式或者行为的改变。

指令( angular 的行为扩展 ):HTML 编译器,能够识别新的 HTML 语法,可以将行为动作关联到HTML或者其属性上面,设置可以创造自定义行为的元素,可复用。

注意指令是在最开始的时候被载入页面的

指令本质上就是一个代用功能的函数 ** return 一个函数 **,类比于 react 的自定义组件

** angular API 有几个大的的分类 **

ng.function ( 功能函数,类比于jquery 的方法函数 )

** ng.directive( angular 的重大模块,eg: ng-model 等 ) **

** ng.provider ( 依赖注入功能 )**

.......

三. angular 的 编译器( compiler )

编译器通过遍历 DOM 来查找和关联属性, 其分为 编译 和 链接 两个阶段

编译:遍历所有的 DOM 收集指令,生成一个 链接函数集合

链接:将指令和作用域绑定,生成一个动态的视图。

作用域模型的改变会反映到视图上,视图的操作会反映到底作用域模型( 中间通过链接函数得以实现 )

四. angular 的视图 ( 动态的 )

五. angular 核心

启动程序 + 执行期 + 作用域 + 控制器 ( 应用的行为 ) + 模型 ( 应用的数据 ) + 视图 + 指令 + 过滤器 + 注入器 + 模块 + 命名空间

angular 执行流程.png

1. 启动程序

** 启动阶段主要工作是建立指令关联关系和渲染DOM **

浏览器解析HTML,然后将其解析成为 DOM

浏览器载入 angularJS

angular 等待 DOMContentLoaded event 事件触发

angular 找到 ng-app 指令,作为应用程序的边界或者根作用域

使用 ng-app 中的模块来逐个配置注入器( $injector )

注入器 ( $injector ) 是用于创建 “编译服务($compile service)” 和 “根作用域( $rootScope )”。

编译服务的作用: 首先将 DOM 和 根作用域进行链接

编译服务将指令( ng-model ... ng-init...等 ) 和 作用域的变量进行一一关联。

通过变量替换,将构件好的视图展现在页面上注意上面 编译服务的作用:两个阶段:编译阶段 和 链接阶段

** 注意点: **

ng-app 作为根应用指令,首先将注入器配置在根模块上面。( 这一步与 DOM 无关 )

$injector 创建了 $compile 和 $rootScope

$compile 将得到的所有的根 DOM 和 $rootScope 进行关联

2. 执行时期 ( 主要是事件回调,响应操作等触发执行 )

concepts-runtime.png

** 执行时期主要工作内容是 事件要被正确的执行和渲染 **关于执行时期的重点概念

只有在angular 的执行的上下文环境中才能享受到angular 提供的各种数据绑定,异常处理,资源管理和服务等等。eg: 使用 angular 的 $setTimeOut 完成延时后可以自动更新页面视图

可以使用 $apply() 来从普通的JavaScript 进入 angularJs的上下文环境。只有在使用自定义的事件或者使用第三方类库时,才需要执行 $apply。

执行时期的流程:

通过调用 scope.$apply( fn ) 进入angular 的上下文环境。fn 为需要在上下文中执行的函数

angular 执行 fn, 此函数改变应用的状态

angular 进入 $digest 循环,$digest 由两个小循环组成($evalAsync 队列和$watch列表,如上图 ), 该循环一直迭代,直到模型稳定.

一个大循环由两个小循环构成。

模型稳定的标志是:$evalAsync 队列为空,$watch 列表中再无任何改变。$evalAsync 通常用于管理视图渲染前需要在当前框架外面执行的操作

$watch是个表达式的集合,若检测到有更改,$watch 函数就会调用,将新的值更新到 DOM 中

一旦 angular 的 $digest 结束循环,整个执行就会离开 angular 和 JavaScript 的上下文环境,

最后一步,浏览器更新界面视图重新渲染。

3. 作用域

mvc.png

将模型整理好传递给视图,将浏览器的动作和事件传递给控制器

1. 作为中介存在 ( 链接数据模型和数据视图 )

2. 作用域拥有层级结构,此层级结构和 DOM 的层级结构相互对应

3. 每一个作用域都有独立的上下文环境

作用域的特点:

1. 作用域提供 API ( $watch 来观察模型的变化 )
2. 作用域提供 API ( $apply ) 将任何模型的改变从 angular 领域 通过系统映射到视图上
3. 作用域通过共享模型成员的方法嵌套到应用组件上面,一个作用域从父作用域继承属性

4. 作用域提供表达式执行的上下文环境

 

作用域的事件传递:

作用域的事件传递和 DOM 的事件传递类似,事件可以广播给子作用域,也可以传递给父作用域。

 

作用域的声明周期

1. 创建: 根作用域在应用被 $injector 启动的时候被创建,在模板链接阶段,有些执行会自动创建新的作用域 ( eg:ng-repeat )

2. 观察者注册:模板链接阶段,指令会在作用域上注册观察者,观察者用于将 DOM 的改变传递给 DOM

3. 模型改版: 只有在 scope.$apply() 中变化的数据才能被准确反映到模型上
angular 本身的 API 会自动应用 apply,eg: $http $timeout 不需要额外的 $apply

4. 变化的观测:在 $apply 的最后,angular 会在根作用域执行一个 $digest 循环,将所有的变化传递给子作用域,只要在 $digest 循环中的所有表达式和函数都会被检测,用于观察模型的变化。

 

4. 控制器

控制器用于构造视图的控制代码,主要作用就是构造数据模型。

控制器的特点 ( 控制器应该和视图做到分离 )

1. 控制器是由 JavaScript 书写,控制器不应该包含任何 HTML 代码。
2. 视图使用 HTML 书写的,视图不应该包含任何 JavaScript 代码。
3. 控制器和视图没有直接的关系。所以可以使用一个控制器对应多个视图。

 

控制器的三个作用( 书写,分清 c 和 v 的区别 )

1. 在应用中设置模型的初始状态,
2. 将整理好的模型和函数交给作用域( scope )
3. 监听模型的变化并响应事件或者动作( 事件响应函数 )

5. 模型

模型就是和模板结合生成视图

模板就是单纯的 HTML 代码,

模型的特点:

模型必须使用作用域来引用

模型可以是任何 JavaScript 类型的数据

 

mvc.png

说明:

控制器和 视图没有直接的关系

mvc 的核心是由作用域承担起来的

一个控制器可以对应多个模型

6. angular 的 watch, apply, digest

$watch

$watch 队列是在 UI 上使用了一个指令时,就自动生成一条 $watch,所有的 $watch 组合成为一个 $watch 列表,$watch 列表是在编译的时候就生成完毕

<ul>
<li ng-repeat="person in people">
{{person.name}} - {{person.age}}
</li>
</ul>

 

对于上述 ng-repeat 若有 10 个 people 会生成 10 * 2 +1 个 $watch

1.*** $watch 参数详解 ***使用 $watch 函数可以进行 自定义的 操作监听,更改 视图

$scope.$watch('name', function(newValue, oldValue) {
if (newValue === oldValue) { return; } // AKA first run
$scope.updated++;
});

$watch 的 第二个参数是一个 函数,用于 监听 前面的变量是否更改。

$scope.$watch('user', function(newValue, oldValue) {
if (newValue === oldValue) { return; }
$scope.updated++;
}, true);

$watch 的 第三个参数是 boolear 类型的值,
** 作用:**
** newValue 和 oldValue 默认是比较 新旧值的引用,若 user 是一个对象,则无法判断对象内部值的改变,需要使用第三个参数来进行判断对象内部深层次的值是否改变。**

 

2.$digest

$digest 循环过程 会 包含两个小循环,$evalAsync 和 $watch 队列循环。$digest 会涉及到脏检查机制,反复询问 $watch 队列是否有数据改变

 

{{ name }}
<button ng-click="changeFoo()">Change the name</button>

// 这里有 一个 $watch( name 会生成 $watch , 而 ng-click 不会生成 $watch, 因为函数 是不会变的。

我们按下按钮

浏览器接收到一个事件,进入angular context(后面会解释为什么)。

$digest循环开始执行,查询每个$watch是否变化。

由于监视$scope.name的$watch报告了变化,它会强制再执行一次$digest循环。

新的$digest循环没有检测到变化。

浏览器拿回控制权,更新与$scope.name新值相应部分的DOM

3.$apply 用于进入 angular 的 上下文环境, 只是一个angular 提供的一个 api 没有 循环等

$apply 会自动触发 $digest 循环

angular 自己的事件动作会自动触发 $apply

非 angular 环境需要手动触发 $apply

有时自己添加了额外的 操作或动作,需要手动 $apply() 执行 $digest 循环( 典型:访问服务器以后的回调操作 )

element.bind('click', function() {
scope.foo++;
scope.bar++;

scope.$apply(); // 手动触发,进行一次 $digest 循环
});

// 第二种
element.bind('click', function() { //
scope.$apply(function() { 使用 $apply 函数进行 $digest 循环
scope.foo++;
scope.bar++;
});
})

 

4.angular 执行时期 再解释 ( 主要是 $apply $digest $watch 这三个方法的流程 )

concepts-runtime.png

1. 页面触发 DOM 事件,回调等。
2. 应用程序自动调用 $apply() 方法进入 angular 上下文,触发 $digest 循环开始,
3. $digest 循环 遍历 $watch 列表集合,脏检查 数据模型的改变,循环过程
4. 检查完毕,更新 数据模型,( 更改变量或者其他动作操作 )
5. 作用域根据数据模型渲染 UI视图。
6. 继续监听。

 

7. angular 的 通讯

angular 模块之间的通讯方式

1. 作用域传递( 父子模块通讯 )$parent $child
2. 作用域数据传递 + $watch ( 类似于指令的数据传递的 = )
3. 事件广播 ( $emit $on $boardcast )

 

1.使用 $rootscope

将 $rootscope 作为依赖注入项,在子组件中使用 $rootscope

使用场景: 对于一处改变,需要多处通知更改的变量,频繁的调用可以使用 $rootscope( 对于登录使用的用户名称,在一个地方使用,需要多处显示,并且更改后需要多处更改 )

myAppModule.controller('myCtrl', function($scope, $rootScope) {

$scope.change = function() {

$scope.test = new Date();

};

$scope.getOrig = function() {
return $rootScope.test;
};

 

})

2.作用域继承 的 模块通讯

作用域继承是在子模块中可以直接使用父模块方法/变量的 通讯方式

** 只适合数据从 父模块传递到子模块 **

** 在指令的数据传递中 通常使用这种模式进行通讯 **

<div ng-controller="Parent">
<div ng-controller="Child">
<div ng-controller="ChildOfChild">
<button ng-click="someParentFunctionInScope()">Do</button>
</div>
</div>
</div>

 

特点:

只适合数据从 父模块传递到子模块

子模块的同名方法会覆盖父模块的 方法/变量

不能进行同级组件之间的数据传递。除非显示更改 $rootscope

层级较多时 维护比较麻烦

作用域通讯 + $watch

作用域的数据只能从 父作用域传递到子作用域,** 使用 $watch() 可以监控子作用于数据的改变,类似于 指令数据传递的 = ( 等于号 ) **

.controller("Parent", function($scope){
$scope.VM = {a: "a", b: "b"};
$scope.$watch("VM.a", function(newVal, oldVal){
// code
});
})

// 需要用到 $parent 等
.controller('Child', function($scope){
$scope.$parent.$watch('$scope.VM.a', function() { ..... })
})

 

4.消息机制

scope 提供了冒泡和隧道机制,$on, $emit, $boardcast

$boardcast 将事件广播给所有的子组件,
$on 用于注册事件函数,
$emit 用于事件向上冒泡传递

优缺点: 相比于 $emit,$boardcast 需要向所有的子组件广播组件的改变,会消耗更多的资源

** 所以 在应用的通讯数据达到很大体量 **
$rootscope $scope + watch 都可以成为设计的基本手法

 

5.使用 Service 进行通讯

因为 angular 中所有的 Service 都是单例的,使用 Service 能够比 时间隧道机制在逻辑上更加清晰。

简单抽取基本的数据

var myApp = angular.module('myApp', []);
myApp.factory('Data', function() {
return { message: "I'm data from a service" }
})

function FirstCtrl($scope, Data) {
$scope.data = Data;
}

function SecondCtrl($scope, Data) {
$scope.data = Data;
}

 

进阶版一: 使用 $watch 来监测数据变化

angular.module('Store', [])
// 提供基本数据模型初始数据的 Service
.factory('Products', function() {
return {
query: function() {
return [{ name: 'Lightsaber', price: 1299.99}, { name: 'Jet Pack', price: 9999.99}, { name: 'Speeder', price: 24999.99}];
}
};
})
// 提供数据模型的 Service
.factory('Order', function() {
var add = function(item, qty) {
item.qty = qty;
this.items.push(item);
};

var remove = function(item) {
if (this.items.indexOf(item) > -1) {
this.items.splice(this.items.indexOf(item), 1);
}
};

var total = function() {
return this.items.reduce(function(memo, item) {
return memo + (item.qty * item.price);
}, 0);
};

return { // 返回完整的数据模型
items: [],
addToOrder: add,
removeFromOrder: remove,
totalPrice: total
};
}).controller('OrderCtrl', function(Products, Order) {
this.products = Products.query();
this.items = Order.items;

this.addToOrder = function(item) {
Order.addToOrder(item, 1);
};

this.removeFromOrder = function(item) {
Order.removeFromOrder(item);
};

this.totalPrice = function() {
return Order.total();
};
}).controller('CartCtrl', function($scope, Order) {
$scope.items = Order.items;

$scope.$watchCollection('items', function() {
$scope.totalPrice = Order.totalPrice().toFixed(2);
}.bind(this));
});

<nav>
// 整个页面只有这里需要根据数据模型的变化而 改变视图 UI
<div ng-controller="CartCtrl">Total Price: {{totalPrice}} </div>
</nav>
<div ng-controller="OrderCtrl as order">
<ul>
<li ng-repeat="product in order.products">
<h3>{{product.name}} - {{product.price}}</h3>
<button ng-click="order.addToOrder(product)">Add</button>
<button ng-click="order.removeFromOrder(product)">Remove</button>
</li>
</ul>
</div>

 

angularjs的$watch、$watchGroup、$watchCollection 使用方式 区别

angular 几种数据通讯机制

var myModule = angular.module('myModule', []);
myModule.factory('mySharedService', function($rootScope) {
var sharedService = {};

sharedService.message = '';

sharedService.prepForBroadcast = function(msg) {
this.message = msg;
this.broadcastItem(); // 执行 广播
};

sharedService.broadcastItem = function() {
$rootScope.$broadcast('handleBroadcast');
};

return sharedService;
});

function ControllerZero($scope, sharedService) {
$scope.handleClick = function(msg) {
sharedService.prepForBroadcast(msg); // 调用包含广播的函数
};

$scope.$on('handleBroadcast', function() {
$scope.message = sharedService.message;
});
}

function ControllerOne($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'ONE: ' + sharedService.message;
});
}

function ControllerTwo($scope, sharedService) {
$scope.$on('handleBroadcast', function() {
$scope.message = 'TWO: ' + sharedService.message;
});
}

ControllerZero.$inject = ['$scope', 'mySharedService'];

ControllerOne.$inject = ['$scope', 'mySharedService'];

ControllerTwo.$inject = ['$scope', 'mySharedService'];

 

 
   
次浏览       
相关文章

深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
相关文档

重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
相关课程

基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程