• AngularJS 1.x 思维索引


      我们在这里不讨论Angular2和Angular4,因为其完全重写,其实已经不叫AngularJS了。

      AngularJS的缺陷:

      • 性能问题:通过检查脏值进行数据更新,当数据不断增加时,检查的效率就不断降低。页面加载速度也会变慢。
      • 落后于当前web发展理念(如组件式的开发)
      • 对手机端的支持不是太友好        

    1. 执行时机

      angularJS定义是一个立即执行的匿名函数,那么其执行时机为引入angularJS的位置决定。

    (function(window) {
        ...
    })(window);

    2. 源码结构

      基于1.6.9

      源码的上部分基本在定义方法,其主要执行开始在源码的最后,主要函数如下:  

    bindJQuery();
    
    publishExternalAPI(angular);
    
    jqLite(function() {
        angularInit(window.document, bootstrap);
      });
    

      2.1 bindJQuery:

        尝试绑定jQuery对象,如果没有则采用内置的jqLite

      2.2 publishExternalAPI:

      •  绑定一些公共的方法到angular,如copy,bind等。
    function publishExternalAPI(angular) {
        extend(angular, {      
          'bootstrap': bootstrap,
          'copy': copy,
          'extend': extend,      
          'bind': bind,
          ...      
        });
        angularModule = setupModuleLoader(window);
        angularModule('ng', ['ngLocale'], ['$provide',
        function ngModule($provide) {
            ...
        }
        ...
    }
    
      •  setupModuleLoader:在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用angular.module方法。

      2.3 angularInit:

        找到带angular项目标识的元素,然后调用bootstrap方法。  

    function angularInit(element, bootstrap) {
        var appElement,
            module,
            config = {};
        forEach(ngAttrPrefixes, function(prefix) {
          var name = prefix + 'app';  
          if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
            appElement = element;
            module = element.getAttribute(name);
          }
        });
       ...
        if (appElement) {
          ...      
          bootstrap(appElement, module ? [module] : [], config);
        }
      }
    

        bootstrap函数主要功能:创建注入器和用注入器进行调用

    function bootstrap(element, modules, config) {
        ...
        var injector = createInjector(modules, config.strictDi);
        injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
           function bootstrapApply(scope, element, compile, injector) {
            scope.$apply(function() {
              element.data('$injector', injector);
              compile(element)(scope);
            });
          }]
        );
        ...
    }
    

        createInjector:其内部代码复杂,DI及provider在此不做展开。在这里模块加载完成后,运行模块,生成实例。

        injector.invoke:生成编译实例,通过编译实例去编译项目起始页,编译的核心是生成指令对应的link函数,有点类似后端的编译,先词法分析,用lex,然后语法分析,用parse,最后链接,生成link函数。具体分析如下

      2.4 RootScope核心

        在publishExternalAPI函数中我们定义了Provider,这个provider实现了AngularJS的核心方法。

          $rootScope: $RootScopeProvider

        接下来看看该provider的源码,下一章我们将分析其核心实现细节。

    function $RootScopeProvider() {
        var TTL = 10;
        this.digestTtl = function(value) {
         ...
        };
      
        this.$get = ['$exceptionHandler', '$parse', '$browser',
            function($exceptionHandler, $parse, $browser) {
      
          function Scope() {
             ...
          }  
      
          Scope.prototype = {
            constructor: Scope,
            $new: function,
            $watch: function,
            $watchGroup: function,
            $watchCollection: function,
            $digest: function,
            $destroy: function,
            $eval: function,
            $evalAsync: function,
            $$postDigest: function,
            $apply: function,
            $applyAsync: function,
            $on: function,
            $emit: function,
            $broadcast: function,         
          };
      
          var $rootScope = new Scope();
          }
        ];
      }
    

      

    3. 核心实现

      3.1 监控对象属性

        $watch和$digest是相辅相成的。两者一起,构成了Angular作用域的核心:数据变化的响应。

      3.2 $watch

          作用为在Scope上添加一个监听器。当Scope上发生变更时,监听器会收到提示。监听器包括下面二个函数:

        • 一个监控函数,用于指定所关注的那部分数据。
        • 一个监听函数,用于在数据变更的时候接受提示。

          通常来说这里的监听器是监控一个表达式。监控表达式是一个字符串,比如说“{{user.firstName}}”,通常在数据绑定,指令的属性,或者JavaScript代码中指定,它被Angular解析和编译成一个监控函数。

          Angular框架中,双美元符前缀$$表示这个变量被当作私有的来考虑,不应当在外部代码中调用。

          $watch源码如下:

      $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {    
       var fn = isFunction(listener) ? listener : noop;
         var scope = this,
            array = scope.$$watchers,
            watcher = {
              fn: fn,
              last: initWatchVal,
              get: get,
              exp: prettyPrintExpression || watchExp,
              eq: !!objectEquality
            }; 
     
        array.unshift(watcher);
            
        return function deregisterWatch() {
          var index = arrayRemove(array, watcher);
          if (index >= 0) {
            incrementWatchersCount(scope, -1);
            if (index < array.$$digestWatchIndex) {
              array.$$digestWatchIndex--;
            }
          }
          lastDirtyWatch = null;
        };
      },

         该函数定义在原型上,为所有scope实例共用。

         该方法主要接受两个函数作参数(表达式和监听函数),把它们存储在$$watchers数组中array.unshift(watcher);

          Wather数组中的监听器将会被下面的digest函数调用。

      3.3 $digest

         Digest函数是angularjs的核心方法之一,进行脏值检查,代码如下: 

    do { // "traverse the scopes" loop
      if ((watchers = current.$$watchers)) {
        watchers.$$digestWatchIndex = watchers.length;
        while (watchers.$$digestWatchIndex--) {
          try {
            watch = watchers[watchers.$$digestWatchIndex];
            // Most common watches are on primitives, in which case we can short
            // circuit it with === operator, only when === fails do we use .equals
            if (watch) {
              get = watch.get;
              if ((value = get(current)) !== (last = watch.last) &&
                !(watch.eq
                  ? equals(value, last)
                  : (isNumberNaN(value) && isNumberNaN(last)))) {
                dirty = true;
                lastDirtyWatch = watch;
                watch.last = watch.eq ? copy(value, null) : value;
                fn = watch.fn;
                fn(value, ((last === initWatchVal) ? value : last), current);
                if (ttl < 5) {
                  logIdx = 4 - ttl;
                  if (!watchLog[logIdx]) watchLog[logIdx] = [];
                  watchLog[logIdx].push({
                    msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
                    newVal: value,
                    oldVal: last
                  });
                }
              } else if (watch === lastDirtyWatch) {
                // If the most recently dirty watcher is now clean, short circuit since the remaining watchers
                // have already been tested.
                dirty = false;
                break traverseScopesLoop;
              }
            }
          } catch (e) {
            $exceptionHandler(e);
          }
        }
      }
      // Insanity Warning: scope depth-first traversal
      // yes, this code is a bit crazy, but it works and we have tests to prove it!
      // this piece should be kept in sync with the traversal in $broadcast
      if (!(next = ((current.$$watchersCount && current.$$childHead) ||
        (current !== target && current.$$nextSibling)))) {
        while (current !== target && !(next = current.$$nextSibling)) {
          current = current.$parent;
        }
      }
    } while ((current = next));

      

        Loop所有的watcher,通过get方法取得当前值,与 记录的上次值(watch.last)比较,如果不同则值为脏值。

        更新watch.last为当前新值,调用watcher上的监听函数,并把新值作为参数。

        注意:在监听函数中有可能会再次更改scope中的值,在最后一部分有个疯狂的深度优先遍历。这里会有个问题,如果一直有脏值怎么办,注意代码中有个TTL最大try的次数。

        以上实现了实现了Angular作用域的本质:添加监听器,在digest里运行它们。    

        特性:

      • 在作用域上添加数据本身并不会有性能折扣。如果没有监听器在监控某个属性,它在不在作用域上都无所谓。Angular并不会遍历作用域的属性,它遍历的是监听器。

      • $digest里会调用每个监控函数,因此,最好关注监听器的数量,还有每个独立的监控函数或者表达式的性能。

      3.4 $apply

        作用:集成外部代码与digest循环

        $apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发digest循环。    

          $apply: function(expr) {
            try {
              beginPhase('$apply');
              try {
                return this.$eval(expr);
              } finally {
                clearPhase();
              }
            } catch (e) {
              $exceptionHandler(e);
            } finally {
              try {
                $rootScope.$digest();
              } catch (e) {
                $exceptionHandler(e);
                // eslint-disable-next-line no-unsafe-finally
                throw e;
              }
            }
          },

       apply的源码非常简单,如上所示,通过eval调用指定的方法。并且保证执行后调用一次digest以进行脏值检查。

         apply使用时机:DOM事件、setTimeout、XHR或其他第三方的库,特别是异步方法对scope中数据的改变不会被watcher监控到,需要显示调用apply告诉angularjs。 

     以上就是angularJS核心思想及其源码实现,有点懒没有展开的说,也有可能理解不够透彻,毕竟是过时的框架了,作为前端编程思想进行学习而已。

    refers:

    https://www.cnblogs.com/leo_wl/p/3446075.html

    https://www.cnblogs.com/wuya16/p/3769032.html

    https://blog.csdn.net/u013510614/article/details/50703811

    https://blog.csdn.net/u013510614/article/details/50703813

    https://blog.csdn.net/u013510614/article/details/50703815

    https://blog.csdn.net/cteng/article/details/72823190

    https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md

  • 相关阅读:
    Linux安装ElasticSearch,Elasticsearch安装辅助插件,IK分词器安装,使用IK分词器
    springBoot高级:自动配置分析,事件监听,启动流程分析,监控,部署
    dubbo:分布式系统中的相关概念,服务提供者,服务消费者,dubbo高级特性
    Maven高级:分模块开发与设计,聚合,集成,属性,版本管理,多环境开发配置和跳过测试,私服搭建
    springMVC:校验框架:多规则校验,嵌套校验,分组校验;ssm整合技术
    springMVC:异步调用,异步请求跨域访问,拦截器,异常处理,实用技术
    Web全段重点整理
    spring事务:事务控制方式,使用AOP控制事务,七种事务传播行为,声明事务,模板对象,模板对象原理分析
    Java基础技术基础面试【笔记】
    高级知识点:多线程,资源管理【笔记】
  • 原文地址:https://www.cnblogs.com/full-stack-engineer/p/8824523.html
Copyright © 2020-2023  润新知