angular知识体系

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

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

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

angular 的特点

提供端对端的解决方案

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

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

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

特点

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

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

angular 的标榜概念

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

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

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

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

将 DOM 操作和应用逻辑解耦

将测试和开发同等看待

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

解放DOM 操作,

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

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

angular 的初始化信息

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

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

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

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

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

2. angular 的指令

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

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

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

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

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

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

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

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

.......

3. angular 的 编译器( compiler )

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

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

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

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

4. angular 的视图 ( 动态的 )

视图.png

5. angular 核心

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

angular 执行流程.png

1. 启动程序

concepts-startup.png

** 启动阶段主要工作是建立指令关联关系和渲染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 循环中的所有表达式和函数都会被检测,用于观察模型的变化。

模型的变化检测机制就是 angular 的 脏检查机制

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 列表是在编译的时候就生成完毕

  • {{person.name}} - {{person.age}}

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

    *** $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 是一个对象,则无法判断对象内部值的改变,需要使用第三个参数来进行判断对象内部深层次的值是否改变。**

    $digest

    $digest 循环过程 会 包含两个小循环,$evalAsync 和 $watch 队列循环。

    $digest 会涉及到脏检查机制,反复询问 $watch 队列是否有数据改变

    {{ name }}

    Change the name

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

    我们按下按钮

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

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

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

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

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

    $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++;

    });

    })

    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 )

    使用 $rootscope

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

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

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

    $scope.change = function() {

    $scope.test = new Date();

    };

    $scope.getOrig = function() {

    return $rootScope.test;

    };

    })

    作用域继承 的 模块通讯

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

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

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

    Do

    特点:

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

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

    不能进行同级组件之间的数据传递。除非显示更改 $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() { ..... })

    })

    消息机制

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

    $boardcast 将事件广播给所有的子组件,

    $on 用于注册事件函数,

    $emit 用于事件向上冒泡传递

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

    ** 所以 在应用的通讯数据达到很大体量 **

    $rootscope $scope + watch 都可以成为设计的基本手法

    使用 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));

    });

    //  整个页面只有这里需要根据数据模型的变化而 改变视图 UI

    Total Price: {{totalPrice}}

    • {{product.name}} - {{product.price}}

      Add

      Remove

      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'];

      作者:南航

      链接:http://www.jianshu.com/p/959cb6bb7036

      來源:简书

      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    • 推荐阅读更多精彩内容

      • Angular面试题 一、ng-show/ng-hide与ng-if的区别? 第一点区别是,ng-if在后面表达式...
        w_zhuan阅读 4,377评论 0 26
      • 1、angularjs的几大特性是什么? 双向数据绑定、依赖注入、模板、指令、MVC/MVVM 2、列举几种常见的...
        15235736666阅读 751评论 0 10
      • AngularJS是什么 AngularJS的官方文档这样介绍它: 完全使用JavaScript编写的客户端技术。...
        oWSQo阅读 892评论 0 10
      • 1. 倾听不仅仅听声音,也要听无声的东西。 2. 何谓污泥,于身心不洁的,皆是污泥。 3. 因为爱你,所以我无所畏...
        达尔蓝色海风阅读 49评论 0 3
      • 书里讲,世上有三类人-----男人,女人和已婚男人。想想是很有道理的,单从我们家的那位来看,完全可证明这一点。 从...
        锦瑟_db50阅读 37评论 1 1