zoukankan      html  css  js  c++  java
  • AngularJS框架研究(一)

    和几年前相比,现在的Javascript开发方式有了翻天覆地的变化,一些极具创意的开发工具的出现,让前端开发更加成熟,Angular框架就是一个例子。最近在看Angular的源代码,发现并不是件容易的事。JS开发已经并不单纯局限在敲JS代码, 从代码的编写,到集成单元测试,再到发布,每个环节都已经专业化,很多新鲜的开发工具都需要去了解。
     
    比如和CSS相关的SCSS,将编码引入到CSS中(作者肯定是一个超级懒惰的程序员),使得CSS的开发和维护效率以几何级增长;又比如时下火热的Node.js,把JavaScript移植到服务器端,可以编写Web应用、游戏服务器等等。实际Node.js并不限于服务器端开发,还可以用来编写本地脚本,实现自动化部署脚本。看看现在开源的JavaScript库,基本上都引入了基于Node.js的Grunt来实现脚本的自动化发布。
     
    像我这样,接触Javascript已经很多年了,但去看Angular源码时,还是有种无处下手的尴尬。下载Angular的源码,一进去就荤菜了,一大堆的文件。经过一番探索后,总算有了点头绪。Angular是一个颇庞大的框架,发布版本(单个js文件)有1万五前多行代码(包含注释),维护这样一个文件显然不是人力所能及也,所以和很多JS库一样,在开发时将文件按照功能拆分为多个子文件,这样便于维护。发布时,使用Grunt脚本来组合以及压缩。
     
    package.json和Gruntfile.js是Grunt的两个配置文件,定义了各种发布任务。AngularJS使用了bower来管理依赖的JS库,在bower.json中定义了相关信息,依赖的库有jQuery, bootstrap, closure等,不过这些库都只是在跑单元测试时才用到。有一堆以karma前缀开头的文件,这是单元测试工具karma相关的东东。如果只是看源码,可以不用理会这些非js文件,直接看src目录即可。
     
    在src目录下,有三个文件:Angular.js, angular.prefix, angular.suffix。从文件名可以看出,这是程序主体。prefix是文件头部,suffix是文件尾。suffix的代码如下:
      //try to bind to jquery now so that one can write angular.element().read()
      //but we will rebind on bootstrap again.
      bindJQuery();
    
      publishExternalAPI(angular);
    
      jqLite(document).ready(function() {
        angularInit(document, bootstrap);
      });
    
    })(window, document);
    

    红色部分标出的正是AngularJS的入口。AngularJS内置了jQuery的轻量版本jqLite,具体代码见src/jqLite.js。bindJquery函数会尝试去绑定jQuery库,如果没有找到,就用内置的jqLite。DOM加载完毕后,执行angularInit函数,作准备工作。

    function angularInit(element, bootstrap) {
      var elements = [element],
          appElement,
          module,
          names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
          NG_APP_CLASS_REGEXP = /sng[:-]app(:s*([wd_]+);?)?s/;
    
      function append(element) {
        element && elements.push(element);
      }
    
      forEach(names, function(name) {
        names[name] = true;
        append(document.getElementById(name));
        name = name.replace(':', '\:');
        if (element.querySelectorAll) {
          forEach(element.querySelectorAll('.' + name), append);
          forEach(element.querySelectorAll('.' + name + '\:'), append);
          forEach(element.querySelectorAll('[' + name + ']'), append);
        }
      });
    
      forEach(elements, function(element) {
        if (!appElement) {
          var className = ' ' + element.className + ' ';
          var match = NG_APP_CLASS_REGEXP.exec(className);
          if (match) {
            appElement = element;
            module = (match[2] || '').replace(/s+/g, ',');
          } else {
            forEach(element.attributes, function(attr) {
              if (!appElement && names[attr.name]) {
                appElement = element;
                module = attr.value;
              }
            });
          }
        }
      });
      if (appElement) {
        bootstrap(appElement, module ? [module] : []);
      }
    }
    

    angularInit函数主要用来寻找主程序入口。如果在DOM中找到了ng-app标记,则调用bootstrap开始初始化框架。如果没有定义app标记,则需要手动调用angular.bootstrap来初始化。app标记一般在html节点,也可以放置在任意的节点上,app节点所在的DOM树都会被AngularJS框架遍历解析。

    ng-app属性如果有值,即自定义module,也会被解析出来,前提是我们必须先创建module,用来管理全局的injector行为和对象。如果没有值,则会创建默认的module。

    function bootstrap(element, modules) {
      var doBootstrap = function() {
        element = jqLite(element);
    
        if (element.injector()) {
          var tag = (element[0] === document) ? 'document' : startingTag(element);
          throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
        }
    
        modules = modules || [];
        modules.unshift(['$provide', function($provide) {
          $provide.value('$rootElement', element);
        }]);
        modules.unshift('ng');
        var injector = createInjector(modules);
        injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
           function(scope, element, compile, injector, animate) {
            scope.$apply(function() {
              element.data('$injector', injector);
              compile(element)(scope);
            });
            animate.enabled(true);
          }]
        );
        return injector;
      };
    
      var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
    
      if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
        return doBootstrap();
      }
    
      window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
      angular.resumeBootstrap = function(extraModules) {
        forEach(extraModules, function(module) {
          modules.push(module);
        });
        doBootstrap();
      };
    }
    

    按照官方文档描述,bootstrap步骤分为三步:首先加载module,然后创建全局injector对象,最后执行compile动作,对应的正是上面的代码。

    AngularJS会为每个应用程序创建唯一的injector对象,它可以看作是一个对象池,依靠键值来存取实例,比如存放数据的model,和后端交互的service等,而实例的创建则由module来决定。

    在AngularJS框架中,应用程序被看作是由多个module组成的一个结合体,而一个module,往往是相似功能块的组合。在一个大型应用程序中,我们习惯将程序切分为多个模块并行开发,这也是AngularJS的推荐做法。在module中,可以定义和View层打交道的Controller,和后台交互的Service,也可以自定义依赖注入行为,解析特殊的DOM数据。

    从上面的代码可以看到,初始化时会加载内置的模块,比如“ng”。在ng模块中定义了AngularJS的核心功能,包括解析DOM树中的以“ng”为前缀的自定义节点, 比如ng-model,ng-class,ng-repeat等等。

    compile,编译,顾名思义,就是将DOM中的ng标记和其他自定义标记解析为真正的View, Model和Controller等。

    --------------------

    到这里为止,对AngularJS的运行机制还处于一知半解,不得不吐槽下,想要弄懂AngularJS的运行机制实在不是件容易的事。

    最后再补充一点:有一款名为"Batarang"的Chrome扩展插件一定不能错过,AngularJS开发调试必备。用过后,才知道官网上一些截图是怎么来的了~

  • 相关阅读:
    [Leetcode][动态规划] 零钱兑换
    [Linux][函数]flock函数的用法
    [Leetcode][动态规划] 买卖股票的最佳时机IV
    [C++] 类的设计(2)——拷贝控制(1)
    [C++] C++中的常用库
    Python 爬虫学习笔记2
    Python 爬虫学习笔记1
    Tableau学习笔记:join
    可视化文摘:multifaceted data and story
    可视化学习笔记8:一般设计技巧
  • 原文地址:https://www.cnblogs.com/walktree/p/angularJS_info_1.html
Copyright © 2011-2022 走看看