跟我读AngularJs的源代码

前端之家收集整理的这篇文章主要介绍了跟我读AngularJs的源代码前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

你要相信,再牛逼的框架他的本质也是javascript代码,只要你会js代码你就能看懂!别怂!

AngularJs是一个很简单的框架(其设计思想很前卫,涉及领域很广,使得它表现出很难的特性,但是你要相信,你使用起来还是很简单的。)

我这里分析的是AngularJS v1.5.7,官网最新版的是2.x(新版的2.x比1.x改进了很多很多,改天再细致分析一遍)

我们来分析一下angular的核心文件(),打开angular.js文件,可以看到整个的代码是放到一个自动执行函数里的,类似如下:

  1. ()();//一个简单的js闭包稍微复杂一点:(function(“参数”){})(“参数”);//所有的代码放在了这匿名function

可以看到大概代码量3万多行,是不是感觉有点崩溃,看起来还是有点费劲的。不用担心,你只要拉到js文件底部,这里是初始化时调用的几个函数,从这里入口,你会很顺畅的理顺整个angular的整个架构思想。如图:

在最底部,31310行代码处:

判断angular是否启动(初始化)过了,如果初始化过就提示错误,没有则return;

然后是绑定jQuery和提供公共接口API,可以看到注释很清晰。

  1. //try to bind to jquery now so that one can write jqLite(document).ready()

具体的实现你可以点击方法看一下。就是如果你引入了jQuery库就调用你引用的,没有引用则使用angular内部的一个轻量级的jQuery(jqLite)。

原文注释:// Use jQuery if it exists with proper functionality,otherwise default to us.

接下来就是初始化model等等操作。你可以打开浏览器打个断点调试一下,享受阅读全球最优秀前端工程师的代码的乐趣。

最后教你一个最简单的方式,因为js是解释型的弱类型语言,也可以说是很没有原则的语言,怎么玩都行,你可以把任意的js对象打印到浏览器页面,利用浏览器的开发者选项查看该js对象的属性,样貌。比单纯的看代码强多了。

用浏览器把angular对象打印出来是这样的:var a = angular;

我这里用的是google的chrome浏览器,可以看到angular对象的每一个熟悉,然后你想了解哪个属性点击它展开就行了。是不是很方便快捷。

总之你记住一句话,根本没有什么难得,难者不会,会者不难,互联网开发者,全球都是平等的,只要你想,你就可以做到跟google的工程师一样优秀!


angularjs源码分析之:angularjs执行流程

angularjs用了快一个月了,最难的不是代码本身,而是学会怎么用angular的思路思考问题。其中涉及到很多概念,比如:directive,controller,service,compile,link,scope,isolate scope,双向绑定,mvvm等。最近准备把这些都慢慢搞懂,分析源码并贴到博客园,如有分析不对的地方,还望各位包容并指正。

angularjs源码分析之:angularjs执行流程

先上个大图,有个大概印象,注:angularjs的版本为:1.2.1,通过bower install angularjs安装的。

几个重要方法

  1. bindJQuery();
  2.  
  3. publishExternalAPI(angular);
  4.  
  5. jqLite(document).ready(function() {
  6. angularInit(document,bootstrap);
  7. });

20121行,bindJQuery,尝试绑定jQuery对象,如果没有则采用内置的jqLite
20123行,publishExternalAPI,初始化angular环境。
1820-1848行,把一些基础api挂载到angular上,如:extend,forEach,isFunction等。

1850行,angularModule = setupModuleLoader(window);方法为模块加载器,在angular上添加了module方法,最后返回的实质上是:

  1. angular.module = function module(name,require,configFn);

当我们angular.module('myApp'),只传一个参数,为getter操作,返回moduleInstance

当我们angular.module('myApp',[]) 时返回对象moduleInstance

  1. var moduleInstance = {
  2. // Private state
  3. _invokeQueue: invokeQueue,_runBlocks: runBlocks,requires: requires,name: name,provider: invokeLater('$provide','provider'),factory: invokeLater('$provide','factory'),service: invokeLater('$provide','service'),value: invokeLater('$provide','value'),constant: invokeLater('$provide','constant','unshift'),animation: invokeLater('$animateProvider','register'),filter: invokeLater('$filterProvider',controller: invokeLater('$controllerProvider',directive: invokeLater('$compileProvider','directive'),config: config,run: function(block) {
  4. runBlocks.push(block);
  5. return this;
  6. }
  7. }

因为这样,我们才可以链式操作 ,如: .factory().controller().

这里需要重点提到两个函数ensureinvokeLater

通过ensure(obj,nam,factory)可以实现:先检索obj.name,如果有就是getter操作,否则为setter操作。

此机制完成了在windows对象上声明angular属性,在angular对象上声明module属性

通过invokeLater可以返回一个闭包,当调用config,provider(即moduleInstance返回的那些api)调用时,实质上就是调用这个闭包,拿

app.provider('myprovider',['$window',function($window){ //code}]) 举例,此api调用后,会将:

('$provide','provider',function($window){}]) push进invokeQueue数组中,注意此处只是入队操作,并未执行。在后面执行时,实际上市通过 第一个参数调用第二个参数方法名,把第三个参数当变量传入,即:args0].apply(args[0],args[2]);具体后面会分析到。

20125行,domready后调用angularInit

  1. function angularInit(element,bootstrap) {
  2. var elements = [element],appElement,module,names = ['ng:app','ng-app','x-ng-app','data-ng-app'],NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;
  3.  
  4. function append(element) {
  5. element && elements.push(element);
  6. }
  7.  
  8. forEach(names,function(name) {
  9. names[name] = true;
  10. append(document.getElementById(name));
  11. name = name.replace(':','\\:');
  12. if (element.querySelectorAll) {
  13. forEach(element.querySelectorAll('.' + name),append);
  14. forEach(element.querySelectorAll('.' + name + '\\:'),append);
  15. forEach(element.querySelectorAll('[' + name + ']'),append);
  16. }
  17. });
  18.  
  19. forEach(elements,function(element) {
  20. if (!appElement) {
  21. var className = ' ' + element.className + ' ';
  22. var match = NG_APP_CLASS_REGEXP.exec(className);
  23. if (match) {
  24. appElement = element;
  25. module = (match[2] || '').replace(/\s+/g,',');
  26. } else {
  27. forEach(element.attributes,function(attr) {
  28. if (!appElement && names[attr.name]) {
  29. appElement = element;
  30. module = attr.value;
  31. }
  32. });
  33. }
  34. }
  35. });
  36. if (appElement) {
  37. bootstrap(appElement,module ? [module] : []);
  38. }
  39. }

遍历names,通过document.getElementById(name) 或者是 querySelectorAll(name)检索到 element后存入 elements数组中,最后获取appElement以及module。举个例子:我们一般会在文档开始的html标签上写 ng-app=”myApp”.通过以上方法,我们最后可以得到 名为myApp的module,后调用bootstrap(appElement,[module]);

bootstrap中需要重点关注 doBootstrap方法

  1. var doBootstrap = function() {
  2. element = jqLite(element);
  3.  
  4. if (element.injector()) {
  5. var tag = (element[0] === document) ? 'document' : startingTag(element);
  6. throw ngMinErr('btstrpd',"App Already Bootstrapped with this Element '{0}'",tag);
  7. }
  8. //通过上面分析我们知道此时 modules 暂时是这样的: modules = ['myApp'];
  9. modules = modules || [];
  10. //添加$provide这个数组
  11. modules.unshift(['$provide',function($provide) {
  12. $provide.value('$rootElement',element);
  13. }]);
  14. //添加 ng这个 module,注意:1857行 我们注册过ng 这个module,并在1854行 我们注册过 它的依赖模块'ngLocale'
  15. //angularModule('ngLocale',[]).provider('$locale',$LocaleProvider); 我们注册过ngLocale这个module
  16. modules.unshift('ng');
  17. //调用createInjector(module) 此时:module为:
  18. //['ng',['$provide',function(){}],'myApp'] 两个typestring,一个为array
  19. var injector = createInjector(modules);
  20. injector.invoke(['$rootScope','$rootElement','$compile','$injector','$animate',function(scope,element,compile,injector,animate) {
  21. scope.$apply(function() {
  22. element.data('$injector',injector);
  23. compile(element)(scope);
  24. });
  25. }]
  26. );
  27. return injector;
  28. };

createInjector是重点,拿出来单独分析

  1. function createInjector(modulesToLoad) {
  2. var INSTANTIATING = {},providerSuffix = 'Provider',path = [],loadedModules = new HashMap(),providerCache = {
  3. $provide: {
  4. provider: supportObject(provider),factory: supportObject(factory),service: supportObject(service),value: supportObject(value),constant: supportObject(constant),decorator: decorator
  5. }
  6. },providerInjector = (providerCache.$injector =
  7. createInternalInjector(providerCache,function() {
  8. throw $injectorMinErr('unpr',"Unknown provider: {0}",path.join(' <- '));
  9. })),instanceCache = {},instanceInjector = (instanceCache.$injector =
  10. createInternalInjector(instanceCache,function(servicename) {
  11. var provider = providerInjector.get(servicename + providerSuffix);
  12. return instanceInjector.invoke(provider.$get,provider);
  13. }));
  14.  
  15.  
  16. forEach(loadModules(modulesToLoad),function(fn) { instanceInjector.invoke(fn || noop); });
  17.  
  18. return instanceInjector;
  19. /** ...省略若干 **/

主要是四个变量:
providerCache,providerInjector,instanceCache,instancheInjector

providerCache初始化只有一个对象 providerCache = { $provide:{}},紧接着调用createInternalInjector 方法返回一个若干重量级api(annotate,get等)赋值给providerCache.$injector 以及provoderInjector.则结果就变成这样了:

  1. providerCache = {
  2. $provide: {
  3. provider: supportObject(provider),value: supportObject(value),constant: supportObject(constant),$injector:{
  4. get:getService,annotate:annotate,instantiate:instantiate,invoke:invoke,has:has
  5. }
  6. }

providerInjector变成了这样:

  1. providerInjector{ nvoke: invoke,instantiate: instantiate,get: getService,annotate: annotate,has: has }

同样,instanceCacheinstanceInjector变成:

  1. instanceCache:{
  2. $injector:{
  3. invoke: invoke,annotate: annotate,has: has
  4. }
  5. }
  6.  
  7.  
  8. instanceInjector = {
  9. invoke: invoke,has: has
  10. }

两个重要方法
loadModules,createInternalInjector

  1. function loadModules(modulesToLoad){
  2. //刚才说了,modulesToLoad长这样:['ng',['$provider','myApp']
  3. forEach(modulesToLoad,function(module) {
  4. if (isString(module)) {
  5. // module为字符串时进入此判断。
  6.        moduleFn = angularModule(module);
  7. //迭代,把所有依赖模块的runBlocks都取出
  8. runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
  9. //前面已经提到,每个module下有个_invokeQueue存了一堆controller,service之类东西,现在就可以遍历拿出来运行啦
  10. for(invokeQueue = moduleFn._invokeQueue,i = 0,ii = invokeQueue.length; i < ii; i++) {
  11. //还记得我刚才说了具体怎么调用的把:第一个参数调用名为第二个参数的方法,传入第三个参数
  12. var invokeArgs = invokeQueue[i],provider = providerInjector.get(invokeArgs[0]);
  13. provider[invokeArgs[1]].apply(provider,invokeArgs[2]);
  14. }
  15. }else if(isFunction(module)){
  16. //这个我还没找到…………
  17. }else if(isArray(module)){
  18. //这里就是第二参数的情形了,用invoke方法执行后将结果存到runBlocks
  19. runBlocks.push(providerInjector.invoke(module));
  20. }
  21. }
  22. return runBlocks;
  23. }

重量级函数createInternalInjector 闪亮登场,正是因为有这个函数,才能实现那么优雅的DI。

  1. function createInternalInjector (){
  2.  
  3. function getService(serviceName) {};
  4.  
  5. function invoke(fn,self,locals){};
  6.  
  7. function instantiate(Type,locals) {};
  8.  
  9. return {
  10. invoke: invoke,get: getService,has: function(name) {
  11. //
  12. }
  13. };
  14. }

这么重要的函数实际上是一个工厂,最后返回五个方法。下面一一分析:

annotate,只获取依赖注入列表

  1. function annotate(fn) {
  2. var $inject,fnText,argDecl,last;
  3.  
  4. if (typeof fn == 'function') {
  5. if (!($inject = fn.$inject)) {
  6. $inject = [];
  7. if (fn.length) {
  8. fnText = fn.toString().replace(STRIP_COMMENTS,'');
  9. argDecl = fnText.match(FN_ARGS);
  10. forEach(argDecl[1].split(FN_ARG_SPLIT),function(arg){
  11. arg.replace(FN_ARG,function(all,underscore,name){
  12. $inject.push(name);
  13. });
  14. });
  15. }
  16. fn.$inject = $inject;
  17. }
  18. } else if (isArray(fn)) {
  19. last = fn.length - 1;
  20. assertArgFn(fn[last],'fn');
  21. $inject = fn.slice(0,last);
  22. } else {
  23. assertArgFn(fn,'fn',true);
  24. }
  25. return $inject;
  26. }

传参有两种形式,1.annotate(fn(injectName)) 以及 2.annotate([injectName,function(){}]).分别进入代码中的两个判断,如果是只传了fn,且fn有参数,则利用

fn.toString返回函数体本身,再通过正则取出injectName数组添加fn.$inject上。 如果通过第二方式调用,取出所有$inject数组

invoke,通过annoate取出依赖注入,将依赖注入为参数调用函数体。如:xx.invoke(function($window){});

  1. function invoke(fn,locals){
  2. var args = [],$inject = annotate(fn),length,i,key;
  3.  
  4. for(i = 0,length = $inject.length; i < length; i++) {
  5. key = $inject[i];
  6. if (typeof key !== 'string') {
  7. throw $injectorMinErr('itkn','Incorrect injection token! Expected service name as string,got {0}',key);
  8. }
  9. args.push(
  10. locals && locals.hasOwnProperty(key)
  11. ? locals[key]
  12. : getService(key)
  13. );
  14. //省略若干
  15. //switch做了优化处理,罗列了一些常见的参数个数
  16.  
  17. switch (self ? -1 : args.length) {
  18. case 0: return fn();
  19. //省略若干
  20. }

instantiate函数比较特殊,也比较有用,但一般不直接用,隐藏较深。
我们考虑这样一种情况,一般较创建的写法。

  1. app.provider('myProvider',function(){
  2. //do something
  3. this.$get = function(){
  4. return obj;
  5. }
  6. });

假如我们想要在angular中用一些设计模式,我们换一种写法:

  1. app.provider('myProvider',myProvider);
  2. function myProvider(){
  3. BaseClass.apply(this,arguments);
  4. }
  5. unit.inherits(BaseClass,myProvider);
  6. extend(myProvider,{
  7. $get:function(){
  8. return something
  9. }
  10. });

这样也可以实现我们需要的,而且可扩展。但如果我们没有了解过instantiate这个方法,你看到此写法会觉得疑惑不解。

instantiate(Type,locals)方法创建了一个空的构造函数,并继承自传入的Type,然后实例化得到instance,最后在调用invoke函数

万事俱备,只欠东风

做了这么多准备,各种provider也有了,我们需要在页面上展示效果

  1. injector.invoke(['$rootScope',animate) {
  2. scope.$apply(function() {
  3. element.data('$injector',injector);
  4. compile(element)(scope);
  5. });
  6. }]
  7. );

通过$apply将作用域转入angular作用域,所谓angular作用域是指:angular采用dirity-check方式进行检测,达到双向绑定。

再利用compile函数编译整个页面文档,识别出directive,按照优先级排序,执行他们的compilie函数,最后返回link function的结合。通过scope与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。

到此,执行流程也就都出来了。

猜你在找的Angularjs相关文章