我的angularjs源码学习之旅1——初识angularjs
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 #8683
throw ngMinErr(
'btstrpd',
"App Already Bootstrapped with this Element '{0}'",
tag.replace(/</,'<').replace(/>/,'>'));
} 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方法执行后将结果保存存到runBlocks
runBlocks.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的技术点。因为本人也是边看边跟踪流程,有不对的地方望大牛指出。
如果觉得本文不错,请点击右下方【推荐】!
我的angularjs源码学习之旅1——初识angularjs的更多相关文章
- 我的angularjs源码学习之旅2——依赖注入
依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...
- 我的angularjs源码学习之旅3——脏检测与数据双向绑定
前言 为了后面描述方便,我们将保存模块的对象modules叫做模块缓存.我们跟踪的例子如下 <div ng-app="myApp" ng-controller='myCtrl ...
- Qt Creator 源码学习笔记01,初识QTC
阅读本文大概需要 4 分钟 Qt Creator 是一款开源的轻量级 IDE,整个架构代码全部使用 C++/Qt 开发而成,非常适合用来学习C++和Qt 知识,这也是我们更加深入学习Qt最好的方式,学 ...
- Tomcat源码学习
Tomcat源码学习(一) 转自:http://carllgc.blog.ccidnet.com/blog-htm-do-showone-uid-4092-type-blog-itemid-26309 ...
- Redis源码学习:字符串
Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...
- Qt Creator 源码学习笔记04,多插件实现原理分析
阅读本文大概需要 8 分钟 插件听上去很高大上,实际上就是一个个动态库,动态库在不同平台下后缀名不一样,比如在 Windows下以.dll结尾,Linux 下以.so结尾 开发插件其实就是开发一个动态 ...
- MVVM大比拼之AngularJS源码精析
MVVM大比拼之AngularJS源码精析 简介 AngularJS的学习资源已经非常非常多了,AngularJS基础请直接看官网文档.这里推荐几个深度学习的资料: AngularJS学习笔记 作者: ...
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
随机推荐
- 拯救无法启动的虚拟机文件.vmdk中的数据
FROM: http://blog.csdn.net/npy_lp/article/details/7686583 从事Linux开发的软件工程师几乎都使用过虚拟机软件,如VMware worksta ...
- Eclipse自动生成作者、日期注释等功能设置
我们在使用Eclipse 编写Java代码时,自动生成的注释信息都是按照预先设置好的格式生成的. 修改作者.日期注释格式:打开Windows->Preferences->Java-> ...
- c#多线程介绍(上)
转载原文:这里是链接内容 转载原文:这里写链接内容 转载原文:这里写链接内容 (重要事情说三遍) 引言 本文主要从线程的基础用法,CLR线程池当中工作者线程与I/O线程的开发,并行操作PLINQ等多个 ...
- C#动态编译引擎-CS-Script
什么是CS-Script? CS-Script是一种以CLR(公共语言运行库)为基础的脚本系统,它使用ECMA标准的C#作为编程语言,它面向微软的CLR运行库(.net 2.0/3.0/3.5/4.0 ...
- WPF - 属性系统 (1 of 4)
本来我希望这一系列文章能够深入讲解WPF属性系统的实现以及XAML编译器是如何使用这些依赖项属性的,并在最后分析WPF属性系统的实际实现代码.但是在编写的过程中发现对WPF属性系统代码的讲解要求之前的 ...
- Guava monitor
Guava的com.google.util.concurrent类库提供了相对于jdk java.util.concurrent包更加方便实用的并发类,Monitor类就是其中一个.Monitor类在 ...
- Hadoop学习笔记—6.Hadoop Eclipse插件的使用
开篇:Hadoop是一个强大的并行软件开发框架,它可以让任务在分布式集群上并行处理,从而提高执行效率.但是,它也有一些缺点,如编码.调试Hadoop程序的难度较大,这样的缺点直接导致开发人员入门门槛高 ...
- C++的性能C#的产能?! - .Net Native 系列向导
之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...
- Qcon会议之所见所想
作为普通码农一枚,Qcon是俺参与过的最高级的技术大会了.大会共历时三天,因为俺第二天就得赶火车休个五一大长假,所以只参加了第一天4/25号的会议(其他俩天自然有其他同事会去观摩),不过第一天的会议有 ...
- 说说Java程序和数据库交互的乱码解决
本文就本人遇到的问题进行讲解 1.通过jdbc直连方式,连接Mysql数据库,从程序向数据库中写入数据出现的乱码解决方案. 当通过程序向Student表中写入一条数据时,写入数据库的内容会产生乱码. ...