angular诞生有好几年光景了,有Google公司的支持版本更新还是比较快,从一开始就是一个热门技术,但是本人近期才开始接触到。只能感慨自己学习起点有点晚了。只能是加倍努力赶上技术前线。

  因为有分析jQuery源码学到很多东西的原因,所以本人对新技术还是抱有追根问底的习惯,希望能从本质上理解他们。前两天刚刚完成nodejs编写的一个小网站,给俺媳妇用的,所以就没有挂到外网上,只能本机启动自己用。开发完成后有点小收获小感悟就在这里唠叨几句。

  第一个要唠叨的是关于抛异常。对前端来说,前端抛异常很多时候是不用去处理用户也感觉不出来的;而后端一抛异常如果没有异常处理机制,那就是整个程序直接挂掉了。从这个上面来说本人感觉后端的代码必须稳定、健壮,所以给个人的感觉是后端程序员更加严肃,而前端程序员更加的活泼,当然这里并不是说前端出异常就不去处理。

  第二个是加密的问题。对于前端来说加密往往是后端的事,往往传输给后端的都是明文密码,本人对此也很难以理解,按说前端也应该加密才对,至少哪些个截获我们发送的信息的人需要一定代价才能破解我们的明文密码。但实际运用中往往都没有前端加密这个环节,尽管大部分网站都声称,不会存储用户的明文密码。但这并没有证据,也许私下里仍在悄悄储存。如果在前端加密,网站就无法拿到用户的明文密码了。也许正是这一点,很多网站不愿意使用前端加密。现在用nodejs了,那么前端人去做后端程序也应当对密码加密才对。关于加密可以参考这篇文章对抗拖库 —— Web 前端慢加密

  第三个是和数据库打交道。对于简单的系统来说还好,至少不会花太多的时间去学习数据库查询,但是如果是比较大的系统的话,那么需要花更多的时间去学习和优化数据库查询了,这是一个很让前端人头疼的事。

  第四是关于模板引擎的事。作为前端人员来说最不希望的是html代码中插入一些业务代码。比如nodejs比较推荐的ejs妥妥的jsp风格。本人是不赞成这种写法的,给后端开发人员用还可以。html就应该是纯html,没有任何业务逻辑,特别是下面这种情况:html代码和逻辑代码完全杂在一起了。

     <% if (names.length) { %>  <ul>  <% names.forEach(function(name){ %>  <li><%= name %></li>  <% }) %>  </ul>  <% } %>

  对比angular:有逻辑,但是逻辑只是标签的属性而已,给人的感觉与纯html一样,看着舒服很多。这也是前端人员比较能接受的方式。

<ul><li ng-repeat="x in names">{{x}} {{lastname}}
</ul>

  好了,唠叨了半天。学习nodejs也就到一段落,毕竟本人也没想真的做全栈式工程师,专攻前端是本人的理想。

  先前通过菜鸟教程学习了angular的基本知识(本人英语不太好,要是看英语教程那叫一个头大)。本人有几个好奇。

  1.MVC/MVP/MVVM这三个东东到底是什么东西?本人一直都是一知半解

  2.如下的代码,input是怎么和{{name}}联动的?框架是怎么保存Hello {{name}}的,必须要保存吧,不然我改变了input内容框架怎么知道去刷新h1。

<div ng-app=""><p>名字 : <input type="text" ng-model="name"></p><h1>Hello {{name}}</h1>
</div>

  3.如下的代码中,函数中怎么知道我是依赖的$scope,怎么实现的依赖注入?

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {$scope.firstName = "John";$scope.lastName = "Doe";
});
</script>

  最后就罗列出了一堆的名称:MVVM、自动化双向数据绑定、依赖注入、脏检测等等。

  

  分析一个源码最先要跟踪的就是他的执行流程,这是第一步。我们的实例代码是

  <div ng-app="myApp" ng-controller='myCtrl'><input type="text" ng-model='name'/><span style='width: 100px;height: 20px;    margin-left: 300px;'>{{name}}</span></div><script>var app = angular.module('myApp', []);app.controller('myCtrl', function($scope) {$scope.name = 1;});</script>

  本人跟踪angular执行流程如下。

1.bindJQuery();尝试绑定jQuery,如果没有jQuery则使用内置的JQLite

2.publishExternalAPI(angular);初始化angular的各种外部api。可以看一下初始化之前的angular对象是

  

  初始化完成以后是

angular = {$$csp: function(),$$minErr: minErr(module, ErrorConstructor),$interpolateMinErr: function(),bind: bind(self, fn),bootstrap: bootstrap(element, modules, config),callbacks: Object,copy: copy(source, destination, stackSource, stackDest),element: JQLite(element),equals: equals(o1, o2),extend: extend(dst),forEach: forEach(obj, iterator, context),fromJson: fromJson(json),getTestability: getTestability(rootElement),identity: identity($),injector: createInjector(modulesToLoad, strictDi),isArray: isArray(),isDate: isDate(value),isDefined: isDefined(value),isElement: isElement(node),isFunction: isFunction(value),isNumber: isNumber(value),isObject: isObject(value),isString: isString(value),isUndefined: isUndefined(value),lowercase: (string),merge: merge(dst),module: module(name, requires, configFn),noop: noop(),reloadWithDebugInfo: reloadWithDebugInfo(),toJson: toJson(obj, pretty),uppercase: (string),version: Object,
}

  可以看到给angular添加了很多方法和属性。

  其中用到 angularModule = setupModuleLoader(window);是用来给angular上添加module方法(angular添加模块的函数)

  

  这个module方法有一个外部变量var modules = {};这个变量的作用马上就能看到。  

  angularModule('ng', ['ngLocale'], ['$provide',function ngModule($provide) {...}]);

  执行结果会得到以后将会得到(这里面这个modules即是angular.module函数的那个外部变量)

modules.ng = moduleInstance = {_invokeQueue: [], _configBlocks:[["$injector","invoke",["$provide",ngModule($provide)]]], _runBlocks: [], animation: funciton(recipeName, factoryFunction),config: function(),constant: function(),controller: function(recipeName, factoryFunction),decorator: function(recipeName, factoryFunction),directive: function(recipeName, factoryFunction),factory: function(recipeName, factoryFunction),filter: function(recipeName, factoryFunction),name: "ng",provider: function(recipeName, factoryFunction),requires: ["ngLocale"],run: function(block),service: function(recipeName, factoryFunction),value: function()
}

  拆解一下这个函数的内部执行步骤和结果:

  1)先定义了三个数组invokeQueue = [];var configBlocks = [];var runBlocks = [];

  顾名思义invokeQueue是执行队列;configBlocks是配置块,马上我们就会对这个配置块赋值; runBlocks是运行了的块。

  2)执行var config = invokeLater('$injector', 'invoke', 'push', configBlocks);得到的config如下

  

  3)对象moduleInstance初始化,初始化中主要调用了两个方法invokeLater和invokeLaterAndSetModuleName,结果为

  

  其中config属性对应的函数就是第二步的config。

  可以看到执行里面的函数大都是在往invokeQueue队列里面塞执行数据,每一个执行数据包括三个元素:provider/method/arguments。后面正真执行的时候调用方式是provider[method].apply(provider, arguments)。

  run函数把block放入到runBlock中。里面有个requires属性,表示要依赖的模块,比如当前name为“ng”时requires为["ngLocale"]。

  小点:moduleInstance的大多数方法属性最后又返回了moduleInstance对象,和jQuery类似,这样实现链式调用。

  4)执行if (configFn) {config(configFn);}

  

  结合第二步的config函数来看即把('$injector','invoke', ['$provide',function ngModule($provide) {...}])塞入到configBlocks中

  

  5)返回处理后的moduleInstance对象,这个对象就是modules.ng

3.调用angular.module("ngLocale", [], ["$provide", function($provide) {...}])再次添加一个ngLocale模块。

  先前modules只有一个ng模块,现在变成了两个模块。

  

4.最后是等待文档加载完成以后进行angular初始化,这里面的初始化主要是识别html中的指令、

jqLite(document).ready(function() {angularInit(document, bootstrap);});

  angularInit中处理是比较简单的,查找符合格式的"ng-"/"data-ng-"/"ng:"/"x-ng-" + "app"标签,作为使用angular的的标志。平常我们都使用ng-app,

ng-app 指令用于告诉 AngularJS 应用当前这个元素是根元素。所有 AngularJS 应用都必须要要一个根元素。HTML 文档中只允许有一个 ng-app 指令,如果有多个 ng-app 指令,则只有第一个会被使用。

  找到根元素,代入bootstrap中执行

//config中包含依赖注入是否是严格注入的标志;module是模块名称,也就是ng-app指定的名称,appElement是angular应用的根元素的DOM对象
bootstrap(appElement, module ? [module] : [], config);

  比较重要的是bootstrap中调用

var doBootstrap = function() {element = jqLite(element);if (element.injector()) {var tag = (element[0] === document) ? 'document' : startingTag(element);//Encode angle brackets to prevent input from being sanitized to empty string #8683throw ngMinErr('btstrpd',"App Already Bootstrapped with this Element '{0}'",tag.replace(/</,'&lt;').replace(/>/,'&gt;'));}modules = modules || [];modules.unshift(['$provide', function($provide) {$provide.value('$rootElement', element);}]);if (config.debugInfoEnabled) {// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.modules.push(['$compileProvider', function($compileProvider) {$compileProvider.debugInfoEnabled(true);}]);}modules.unshift('ng');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);});}]);return injector;};

  在初始化注入函数createInjector之前,modules结构如下

  

  正真最重要的函数:createInjector(初始化依赖注入)。createInjector需要单独拿出来说

 5.createInjector  

function createInjector(modulesToLoad, strictDi) {strictDi = (strictDi === true);var INSTANTIATING = {},providerSuffix = 'Provider',path = [],loadedModules = new HashMap([], true),providerCache = {$provide: {provider: supportObject(provider),factory: supportObject(factory),service: supportObject(service),value: supportObject(value),constant: supportObject(constant),decorator: decorator}},providerInjector = (providerCache.$injector =createInternalInjector(providerCache, function(serviceName, caller) {if (angular.isString(caller)) {path.push(caller);}throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));})),instanceCache = {},instanceInjector = (instanceCache.$injector =createInternalInjector(instanceCache, function(serviceName, caller) {var provider = providerInjector.get(serviceName + providerSuffix, caller);return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);}));forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });return instanceInjector;  ...

  返回的是instanceInjector。里面重要的几个变量的关系是providerInjector = providerCache.$injector;instanceInjector = instanceCache.$injector。

  而providerCache的结构是

  

  instanceCache的结构是

    

  我们的实例代码最终走到createInjector函数中的

forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  此时modulesToLoad为 ["ng", [ "$provide",function ($provide){...}], "myApp"]。

  function loadModules(modulesToLoad) {assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');var runBlocks = [], moduleFn;forEach(modulesToLoad, function(module) {//loadedModules = new HashMap([], true),这是一个哈希存储结构,将modulesToLoad里面的元素都存到hash表中if (loadedModules.get(module)) return;loadedModules.put(module, true);function runInvokeQueue(queue) {var i, ii;for (i = 0, ii = queue.length; i < ii; i++) {var invokeArgs = queue[i],provider = providerInjector.get(invokeArgs[0]); //这里便是之前说的provider[method].apply(provider, arguments)的调用provider[invokeArgs[1]].apply(provider, invokeArgs[2]);}}try {if (isString(module)) {//当module为字符串//angularModule即angular.module,调用后返回moduleInstance对象          moduleFn = angularModule(module);//把所有依赖模块的runBlocks都取出          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); //将执行西面的两个队列runInvokeQueue(moduleFn._invokeQueue);runInvokeQueue(moduleFn._configBlocks);} else if (isFunction(module)) {runBlocks.push(providerInjector.invoke(module));} else if (isArray(module)) {//第二参数 [ "$provide",function ($provide){...}],invoke方法执行后将结果保存存到runBlocksrunBlocks.push(providerInjector.invoke(module));} else {assertArgFn(module, 'module');}} catch (e) {...}});return runBlocks;}

  还有一个比较重要的函数createInternalInjector,顾名思义即用来创建依赖注入的。下一章接着分析angular实现的依赖注入。

  创建了依赖注入对象injector,接下来就马上用起来了

6.执行injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',       function bootstrapApply(scope, element, compile, injector) {...}])。

  这个函数执行实现了数据的脏检测,使数据双向绑定。后面会分析他的实现方式。

  好了,通过这6步,页面初始化即完成。里面有很多细节无法分析到位,本人觉得也没必要细究,毕竟还不是angular的技术创新点,大体了解一些angular执行流程即可,后面的分析才会分析angular的技术点。因为本人也是边看边跟踪流程,有不对的地方望大牛指出。

  

  如果觉得本文不错,请点击右下方【推荐】!

转载于:https://www.cnblogs.com/chuaWeb/p/angular-process.html

我的angularjs源码学习之旅1——初识angularjs相关推荐

  1. 我的angularjs源码学习之旅2——依赖注入

    依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...

  2. JDK源码学习之Arraylist与LinkedList

    ArrayList和LinkedList是我们在开发过程中常用的两种集合类,本文将从底层源码实现对其进行简单介绍. 下图是Java集合类所涉及的类图. 一.ArrayList 从上面的集合类图可以看出 ...

  3. VUE源码学习第一篇--前言

    一.目的 前端技术的发展,现在以vue,react,angular为代表的MVVM模式以成为主流,这三个框架大有三分天下之势.react和angular有facebook与谷歌背书,而vue是以一己之 ...

  4. Tomcat源码学习(一)

    Tomcat源码学习(一) 已有 9159 次阅读 2008-3-13 03:10 |个人分类:Tomcat|系统分类:开发 http://blog.ccidnet.com/home.php?mod= ...

  5. angularjs源码笔记(3)--injector

    2019独角兽企业重金招聘Python工程师标准>>> 简介 injector是用来做参数自动注入的,例如 function fn ($http, $scope, aService) ...

  6. vue源码学习--vue源码学习入门

    本文为开始学习vue源码的思路整理.在拿到vue项目源码的之后看到那些项目中的文件夹,会很困惑,不知道每个文件夹内的世界,怎么变换,怎样的魔力,最后产生了vue框架.学习源码也无从学起.我解决了这些困 ...

  7. angularjs源码阅读-1-模块加载器

    angularjs源码-setupModuleLoader 背景和开始 publishExternalAPI引入setupModuleLoader setupModuleLoader下面的逻辑 ens ...

  8. Soul API 网关源码学习《二》

    基于examples下面的 http服务进行源码解析 前言 上一篇文章Soul API 网关源码解析<一> 中简单介绍了一下网关的含义,同时介绍了两种微服务开发常用的网关:Zuul 1.x ...

  9. mybatis源码学习1--学习源码的目的

    在开始分析mybatis源码之前,需要定一个目标,也就是我们不是为了读源码而去读,一定是带着问题去读,在读的时候去寻找到答案,然后再读码的同时整理总结,学习一些高级的编码方式和技巧. 首先我们知道my ...

最新文章

  1. OpenCV制作自己的线性滤镜
  2. 自动刷新某个指定网页
  3. centos7安装tomcat_手把手教你,使用 Nginx 搭配 Tomcat 实现负载均衡!
  4. Castle ActiveRecord学习实践(4):实现One-Many关系的映射
  5. 利用scp 远程上传下载文件/文件夹
  6. 送给计算机老师的话,送给老师的话
  7. 阶段5 3.微服务项目【学成在线】_day04 页面静态化_13-页面静态化-数据模型-轮播图DataUrl接口...
  8. Arduino - 连接RS232串口设备(通过RS232转TTL转接口)
  9. 基于matlab仿真实例,基于MATLAB的新能源汽车仿真实例
  10. linux常见通配符的含义,linux通配符含义
  11. html上下两个箭头符号怎么打出来,上下两个半箭头符号怎么打啊?高手进来看下,有图示的!...
  12. Python语言程序设计(嵩天老师)-期末考核2.1-快乐数字
  13. canvas 圆角矩形填充_canva绘制圆角矩形
  14. 如何安装最新版本的office(preview预览版)、更新
  15. android 9.0 10.0 上报鼠标左键右键给app调用
  16. 信息收集之寻找真实ip
  17. 大数据分析的技术有哪些?
  18. 二、如何写好学术/学位论文
  19. Pandas数据分析 - 去重 - duplicated() drop_duplicates() 用法
  20. vue 给iframe设置src_vue 中引入iframe,动态设置其src,遇到的一些小问题总结

热门文章

  1. Eclipse Pydev 技巧汇编
  2. centos/linux 安装node.js
  3. 农历算法-ASP.NET(C#)(转)
  4. 一组匹配中国大陆手机号的正则表达式
  5. 洛谷P1122最大子树和题解
  6. Ubuntu使用——11(thinkPHP 5在Ubuntu上学习总结)
  7. tomcat和apache的区别
  8. linux如何安装django
  9. Excel 使用VBA 使表格的值被修改后填充颜色标注
  10. Codeforces686C【dfs】