跟我读AngularJs的源代码
你要相信,再牛逼的框架他的本质也是javascript代码,只要你会js代码你就能看懂!别怂!
AngularJs
是一个很简单的框架(其设计思想很前卫,涉及领域很广,使得它表现出很难的特性,但是你要相信,你使用起来还是很简单的。)
我这里分析的是AngularJS v1.5.7
,官网最新版的是2.x
(新版的2.x比1.x改进了很多很多,改天再细致分析一遍)
我们来分析一下angular的核心文件(),打开angular.js
文件,可以看到整个的代码是放到一个自动执行函数里的,类似如下:
()();//一个简单的js闭包稍微复杂一点:(function(“参数”){})(“参数”);//所有的代码放在了这匿名function中
可以看到大概代码量3万多行,是不是感觉有点崩溃,看起来还是有点费劲的。不用担心,你只要拉到js文件最底部,这里是初始化时调用的几个函数,从这里入口,你会很顺畅的理顺整个angular
的整个架构思想。如图:
在最底部,31310行代码处:
判断angular是否启动(初始化)过了,如果初始化过就提示错误,没有则return;
然后是绑定jQuery
和提供公共接口API
,可以看到注释很清晰。
//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
安装的。
几个重要方法
bindJQuery();
publishExternalAPI(angular);
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
20121行,bindJQuery,尝试绑定jQuery对象,如果没有则采用内置的jqLite
20123行,publishExternalAPI,初始化angular环境。
1820-1848行,把一些基础api挂载到angular上,如:extend,forEach,isFunction
等。
1850行,angularModule = setupModuleLoader(window);
此方法为模块加载器,在angular上添加了module方法,最后返回的实质上是:
angular.module = function module(name,require,configFn);
当我们angular.module('myApp')
,只传一个参数,为getter操作,返回moduleInstance
当我们angular.module('myApp',[])
时返回对象moduleInstance
var moduleInstance = {
// Private state
_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', 'register'),
controller: invokeLater('$controllerProvider', 'register'),
directive: invokeLater('$compileProvider', 'directive'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
}
因为这样,我们才可以链式操作 ,如: .factory().controller()
.
这里需要重点提到两个函数:ensure
和 invokeLater
。
通过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',['$window',function($window){}])
push进invokeQueue
数组中,注意此处只是入队操作,并未执行。在后面执行时,实际上市通过 第一个参数调用第二个参数方法名,把第三个参数当变量传入,即:args0].apply(args[0],args[2]);
具体后面会分析到。
20125行,domready后调用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*([\w\d_]+);?)?\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] : []);
}
}
遍历names,通过document.getElementById(name)
或者是 querySelectorAll(name)
检索到 element
后存入 elements
数组中,最后获取到appElement
以及module
。举个例子:我们一般会在文档开始的html标签上写 ng-app=”myApp”.通过以上方法,我们最后可以得到 名为myApp的module,后调用bootstrap(appElement,[module]);
bootstrap中需要重点关注 doBootstrap
方法
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 = ['myApp'];
modules = modules || [];
//添加$provide这个数组
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
//添加 ng这个 module ,注意:1857行 我们注册过ng 这个module,并在1854行 我们注册过 它的依赖模块'ngLocale',
//angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 我们注册过ngLocale这个module
modules.unshift('ng');
//调用createInjector(module) 此时:module为:
//['ng',['$provide',function(){}],'myApp'] 两个type为string,一个为array
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);
});
}]
);
return injector;
};
createInjector是重点,拿出来单独分析
function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(),
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() {
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider);
}));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
return instanceInjector;
/**
...省略若干
**/
主要是四个变量:
providerCache,providerInjector,instanceCache,instancheInjector
providerCache
初始化只有一个对象 providerCache = { $provide:{}}
,紧接着调用createInternalInjector
方法返回一个若干重量级api(annotate,get等)赋值给providerCache.$injector
以及provoderInjector.
则结果就变成这样了:
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
$injector:{
get:getService,
annotate:annotate,
instantiate:instantiate,
invoke:invoke,
has:has
}
}
而providerInjector
变成了这样:
providerInjector:{
nvoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
同样,instanceCache
和instanceInjector
变成:
instanceCache:{
$injector:{
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
}
instanceInjector = {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
两个重要方法:
loadModules,createInternalInjector
function loadModules(modulesToLoad){
//刚才说了,modulesToLoad长这样:['ng',['$provider',function(){}], 'myApp']
forEach(modulesToLoad, function(module) {
if (isString(module)) {
// module为字符串时进入此判断。
moduleFn = angularModule(module);
//迭代,把所有依赖模块的runBlocks都取出
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
//前面已经提到,每个module下有个_invokeQueue存了一堆controller,service之类东西,现在就可以遍历拿出来运行啦
for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
//还记得我刚才说了具体怎么调用的把:第一个参数调用名为第二个参数的方法,传入第三个参数
var invokeArgs = invokeQueue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}else if(isFunction(module)){
//这个我还没找到…………
}else if(isArray(module)){
//这里就是第二参数的情形了,用invoke方法执行后将结果存到runBlocks
runBlocks.push(providerInjector.invoke(module));
}
}
return runBlocks;
}
重量级函数createInternalInjector
闪亮登场,正是因为有这个函数,才能实现那么优雅的DI。
function createInternalInjector (){
function getService(serviceName) {};
function invoke(fn, self, locals){};
function instantiate(Type, locals) {};
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
//
}
};
}
这么重要的函数实际上是一个工厂,最后返回五个方法。下面一一分析:
annotate
,只获取依赖注入列表
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
传参有两种形式,1.annotate(fn(injectName))
以及 2.annotate([injectName,function(){}])
.分别进入代码中的两个判断,如果是只传了fn,且fn有参数,则利用
fn.toString
返回函数体本身,再通过正则取出injectName
数组添加到fn.$inject
上。 如果通过第二方式调用,取出所有$inject
数组
invoke
,通过annoate
取出依赖注入,将依赖注入为参数调用函数体。如:xx.invoke(function($window){});
function invoke(fn, self, locals){
var args = [],
$inject = annotate(fn),
length, i,
key;
for(i = 0, length = $inject.length; i < length; i++) {
key = $inject[i];
if (typeof key !== 'string') {
throw $injectorMinErr('itkn',
'Incorrect injection token! Expected service name as string, got {0}', key);
}
args.push(
locals && locals.hasOwnProperty(key)
? locals[key]
: getService(key)
);
//省略若干
//switch做了优化处理,罗列了一些常见的参数个数
switch (self ? -1 : args.length) {
case 0: return fn();
//省略若干
}
instantiate
此函数比较特殊,也比较有用,但一般不直接用,隐藏较深。
我们考虑这样一种情况,一般较创建的写法。
app.provider('myProvider',function(){
//do something
this.$get = function(){
return obj;
}
});
假如我们想要在angular中用一些设计模式,我们换一种写法:
app.provider('myProvider',myProvider);
function myProvider(){
BaseClass.apply(this,arguments);
}
unit.inherits(BaseClass,myProvider);
extend(myProvider,{
$get:function(){
return something
}
});
这样也可以实现我们需要的,而且可扩展。但如果我们没有了解过instantiate
这个方法,你看到此写法会觉得疑惑不解。
instantiate(Type,locals)
方法创建了一个空的构造函数,并继承自传入的Type
,然后实例化得到instance
,最后在调用invoke
函数。
万事俱备,只欠东风
做了这么多准备,各种provider
也有了,我们需要在页面上展示效果了
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
通过$apply
将作用域转入angular
作用域,所谓angular
作用域是指:angular
采用dirity-check
方式进行检测,达到双向绑定。
再利用compile
函数编译整个页面文档,识别出directive
,按照优先级排序,执行他们的compilie
函数,最后返回link function
的结合。通过scope
与模板连接起来,形成一个即时,双向绑定。这个过程后续再分析。
到此,执行流程也就都出来了。
上一篇: UVA673
下一篇: php统计近一周和近30天的用户数据