设为首页 收藏本站
查看: 764|回复: 0

[经验分享] 我的angularjs源码学习之旅1——初识angularjs

[复制链接]
发表于 2017-2-24 06:16:09 | 显示全部楼层 |阅读模式
  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对象是
DSC0000.png

  初始化完成以后是



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添加模块的函数)
DSC0001.png

  这个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如下
DSC0002.png

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

  其中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);}
     DSC0004.png
  结合第二步的config函数来看即把('$injector','invoke', ['$provide',function ngModule($provide) {...}])塞入到configBlocks中
DSC0005.png

  5)返回处理后的moduleInstance对象,这个对象就是modules.ng
  3.调用angular.module("ngLocale", [], ["$provide", function($provide) {...}])再次添加一个ngLocale模块。
  先前modules只有一个ng模块,现在变成了两个模块。
DSC0006.png

  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(/</,'&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结构如下
DSC0007.png

  正真最重要的函数: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的结构是
DSC0008.png

  instanceCache的结构是
DSC0009.png

  我们的实例代码最终走到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,
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的技术点。因为本人也是边看边跟踪流程,有不对的地方望大牛指出。
  如果觉得本文不错,请点击右下方【推荐】!

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-346290-1-1.html 上篇帖子: Ionic环境配置及android打包 下篇帖子: C“中断” 与 JS“异步回调” 横向对比
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表