angular

作者: 南航 | 来源:发表于2017-08-11 15:48 被阅读284次

1.angular 基本概念


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

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

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

  2. angular 的特点

    • 提供端对端的解决方案

        构建一个 CRUD(add retrieve update delete) 应用的全部内容:`数据绑定,表单验证,路由,深度链接,组件重用,依赖注入`
      
        测试方案: `单元测试, 端对端测试,模拟和自动化测试`
      
        具有各种种子应用作为模板和起点
      
    • 特点

      angular 主要考虑构建 CRUD 应用,并不是所有的应用都适合使用 angular 来构建
      例如游戏,图形编辑界面就不适合使用 angular
      
  3. 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( 节点 ) 来手动装载节点

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 来查找和关联属性, 其分为 编译 和 链接 两个阶段

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

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

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

4. angular 的视图 ( 动态的 )

视图.png

5. angular 核心

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

angular 执行流程.png
1. 启动程序

concepts-startup.png

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

  1. 浏览器解析HTML,然后将其解析成为 DOM
  2. 浏览器载入 angularJS
  3. angular 等待 DOMContentLoaded event 事件触发
  4. angular 找到 ng-app 指令,作为应用程序的边界或者根作用域
  5. 使用 ng-app 中的模块来逐个配置注入器( $injector )
  6. 注入器 ( $injector ) 是用于创建 “编译服务($compile service)” 和 “根作用域( $rootScope )”。
  7. 编译服务的作用: 首先将 DOM 和 根作用域进行链接
  8. 编译服务将指令( ng-model ... ng-init...等 ) 和 作用域的变量进行一一关联。
  9. 通过变量替换,将构件好的视图展现在页面上

注意上面 编译服务的作用:两个阶段:编译阶段链接阶段

** 注意点: **

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

   $compile 将得到的所有的根 DOM 和 $rootScope 进行关联
2. 执行时期 ( 主要是事件回调,响应操作等触发执行 )

concepts-runtime.png

** 执行时期主要工作内容是 事件要被正确的执行和渲染 **

  • 关于执行时期的重点概念

    1. 只有在angular 的执行的上下文环境中才能享受到angular 提供的各种数据绑定,异常处理,资源管理和服务等等。eg: 使用 angular 的 $setTimeOut 完成延时后可以自动更新页面视图
    2. 可以使用 $apply() 来从普通的JavaScript 进入 angularJs的上下文环境。只有在使用自定义的事件或者使用第三方类库时,才需要执行 $apply。
  • 执行时期的流程:

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

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

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

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

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

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

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

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


  1. $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

*** $watch 参数详解 ***

  1. 使用 $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>
    

    特点:

    1. 只适合数据从 父模块传递到子模块
    2. 子模块的同名方法会覆盖父模块的 方法/变量
    3. 不能进行同级组件之间的数据传递。除非显示更改 $rootscope
    4. 层级较多时 维护比较麻烦
  3. 作用域通讯 + $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 都可以成为设计的基本手法
  1. 使用 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'];
    

.

相关文章

网友评论

    本文标题:angular

    本文链接:https://www.haomeiwen.com/subject/cotuqxtx.html