john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

angularjs源码分析--依赖注入器的产生

发表于 2017-02-20 | 更新于 2020-03-20

angularjs依赖注入介绍

依赖注入在当今很多框架中都是必不可少要提供的功能,比如Java世界中强大的Spring框架。

我们在使用angularjs时会大量用到它内部依赖注入提供的能力,比如在controller里我们可以直接
使用controller函数的参数对象而无需关注这些对象怎么传入,这就是依赖注入的魔力。

下面就开始分篇介绍angularjs是怎么一步步注入依赖的。

createInjector函数

createInjector可以说是angularjs实现依赖注入的核心函数,最终这个函数返回的是一个instanceInjector 实例注入器,就可以利用这个实例注入器来注入一些需要的资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createInjector(modulesToLoad, strictDi) {

//省略部分代码
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
//protoInstanceInjector就是通过createInternalInjector函数来获取
var instanceInjector = protoInstanceInjector.get('$injector');//上面定义的providerCache.$jnjector
instanceInjector.strictDi = strictDi;
//if(fn) 因为有的方法invoke没有返回值
//invoke的时候 通过injectionArgs方法把参数对象注入
//runBlocks是数组
forEach(runBlocks, function(fn) { if (fn) instanceInjector.invoke(fn); });

return instanceInjector;
}

createInternalInjector函数

首先讲到angularjs依赖注入很关键的一个函数createInternalInjector,顾名思义,这个方法的目的就是创建内部注入器。

函数传入了一个factory工厂方法参数,这个factory工厂设计模式我们在angular内部可以看到很多地方被运用,我们要明白一个简单的道理 :工厂设计模式的好处就是管理了内部逻辑,使用者只管调用,工厂自然地会帮你生产出你想要的东西。

我们还可以观察到该方法内部会创建一个内部getService方法,然后以其他形式返回供外部使用,这种设计在javascript中很常见,就是所谓的闭包 ,在angularjs框架内部也是随处可见。

getService方法内部会对cache参数做读写,可以维护这个外部函数传入的cache参数。
createInternalInjector函数最终会返回一个注入器对象供外部使用,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*
创建内部注入器
*/

function createInternalInjector(cache, factory) {

//cache和factory参数在getService内部使用
//angularjs内部闭包使用很频繁
//没有cache里的serviceName的时候 ,再调用factory方法。
function getService(serviceName, caller) {
//http://blog.csdn.net/webdesman/article/details/20040815
//对象是否有自己的属性 不在原型链上扩展的
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
try {
//unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
path.unshift(serviceName);
cache[serviceName] = INSTANTIATING;
//如果没有属性 那就调用factory函数 传入serviceName和caller
//caller也是函数
return cache[serviceName] = factory(serviceName, caller);
} catch (err) {
if (cache[serviceName] === INSTANTIATING) {
delete cache[serviceName];
}
throw err;
} finally {
//shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值
path.shift();
}
}
}
//省略部分代码

return {
//省略部分代码
get: getService,
};
}

createInternalInjector方法的内部使用,最终返回给providerInjector(提供者注入器),protoInstanceInjector(原型对象注入器)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//providerCache从此有了$injector属性
providerInjector = (providerCache.$injector =createInternalInjector(providerCache, function(serviceName, caller) {
if (angular.isString(caller)) {
path.push(caller);
}
//$injectorMinErr 已经是个函数 返回的是个Error对象
//unpr 是个code
//Unknown。。 是具体的模板消息 {0} 是格式化的参数
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
protoInstanceInjector =createInternalInjector(instanceCache, function(serviceName, caller) {

var provider = providerInjector.get(serviceName + providerSuffix, caller);

//invoke函数 fn,self,locals,serviceName
//调用provider的$get函数放入instanceCache中。
return instanceInjector.invoke(
provider.$get, provider, undefined, serviceName);
//$get 就返回下面的valueFn
})

我们看到在创建原型对象注入器的时候工厂方法内部会有对instanceInjector(对象注入器的使用),
这个instanceInjector是通过 protoInstanceInjector 来获取的。

protoInstanceInjector注入器的工厂方法内有很重要的两行代码,首先通过提供者注入器获取serviceName加上providerSuffix对应的provider,然后通过instanceInjector对象实例注入器调用invoke方法,传入提供者的$get属性等,返回instance放入instanceCache中。

我们之后会分析下这边的invoke方法和provider的$get方法。

json4s_problem

发表于 2017-02-06 | 更新于 2019-05-07

json4s是个什么东东

首先去官网,看到标题就是One AST to rule them all。

AST:抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。

At this moment there are at least 6 json libraries for scala, not counting the java json libraries. All these libraries have a very similar AST. This project aims to provide a single AST to be used by other scala json libraries.
也就是说这个项目是要提供一个抽象语法树给其他scala的json库使用

At this moment the approach taken to working with the AST has been taken from lift-json and the native package is in fact lift-json but outside of the lift project.
当前可以与这个AST一起使用的是从lift-json借过来的。

Jackson

In addition to the native parser there is also an implementation that uses jackson for parsing to the AST. The jackson module includes most of the jackson-module-scala functionality and the ability to use it with the lift-json AST.

可以用jackson去解析AST.

angular源码剖析之绑定jQuery

发表于 2017-01-19 | 更新于 2019-05-17

angular1.5.8 绑定jquery库

jq函数

先看jq方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var jq = function() {
if (isDefined(jq.name_)) return jq.name_;
var el;
var i, ii = ngAttrPrefixes.length, prefix, name;
for (i = 0; i < ii; ++i) {
//line 1513 var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
prefix = ngAttrPrefixes[i];
//https://developer.mozilla.org/zh-CN/docs/Web/API/Document/querySelector
if (el = window.document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) {
//获取ng-jq属性
name = el.getAttribute(prefix + 'jq');
break;//只会获取一次
}
}

return (jq.name_ = name);
};

绑定jquery库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
var bindJQueryFired = false;
function bindJQuery() {
var originalCleanData;

if (bindJQueryFired) {
return;
}
//http://div.io/topic/1154
// bind to jQuery if present;
var jqName = jq(); // 调用jq方法
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
!jqName ? undefined : // use jqLite
window[jqName]; // use jQuery specified by `ngJq`

// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
//要支持on off 方法
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
// versions. It will not work for sure with jQuery <1.7, though.
if (jQuery && jQuery.fn.on) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});

//monkey patch (猴子补丁)
//用来在运行时动态修改已有的代码,而不需要修改原始代码。
//http://blog.csdn.net/fwenzhou/article/details/8742838
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery.cleanData;
jQuery.cleanData = function(elems) {
var events;
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
//最终调用originalCleanData方法 就是jquery.cleanData函数
originalCleanData(elems);
};
} else {
jqLite = JQLite;//使用内置的JQLite 库
}

angular.element = jqLite;

// Prevent double-proxying.
//阻止双代理
bindJQueryFired = true;
}

内置jQLite

我们来看内置的JQLite库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//line 2968
function JQLite(element) {
if (element instanceof JQLite) {
return element;
}

var argIsString;

if (isString(element)) {
element = trim(element);
argIsString = true;
}
if (!(this instanceof JQLite)) {
if (argIsString && element.charAt(0) != '<') {
throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
}
return new JQLite(element);
}

if (argIsString) {
jqLiteAddNodes(this, jqLiteParseHTML(element));
} else {
jqLiteAddNodes(this, element);
}
}

参考资料:
何使用angular.js中的jqlite
Angular内嵌jqlite语法大全

angular源码分析之bind函数

发表于 2017-01-14 | 更新于 2019-05-07

angular.bind函数分析

解释:返回一个调用self的函数fn(self代表fn里的this).可以给fn提供参数args.这个功能也被称为局部操作,以区别功能.
格式:angular.bind(self,fn,args);
self:object 对象; fn的上下文对象,在fn中可以用this调用
fn:function; 绑定的方法

1
2
3
4
5
6
7
8
9

var obj = { name: "Any" };
var fn = function (Adj) {
console.log(this.name + "is a boy!!! And he is " + Adj + " !!!");
};
var f = angular.bind(obj, fn, "handsome");
f();//Any is a boy!!! And he is handsome!!!
var t = angular.bind(obj, fn);
t("ugly");// Any is a boy!!! And he is ugly!!!

上源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

function concat(array1, array2, index) {
return array1.concat(slice.call(array2, index));
}

function sliceArgs(args, startIndex) {
return slice.call(args, startIndex || 0);
}

function bind(self, fn) {
var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
//判断是否是函数 且不能是正则类型
if (isFunction(fn) && !(fn instanceof RegExp)) {
return curryArgs.length
? function() {
return arguments.length
? fn.apply(self, concat(curryArgs, arguments, 0))
: fn.apply(self, curryArgs);
}
: function() {
return arguments.length
? fn.apply(self, arguments)
: fn.call(self);
};
} else {
// In IE, native methods are not functions so they cannot be bound (note: they don't need to be).
return fn;
}
}

angular源码分析之typedArray

发表于 2017-01-08 | 更新于 2019-05-07

angular1.5.8中对于TypedArray的判断

1
2
3
4
5
6
7
8

var TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;

function isNumber(value) {return typeof value === 'number';}

function isTypedArray(value) {
return value && isNumber(value.length) && TYPED_ARRAY_REGEXP.test(toString.call(value));
}

比如用如下代码测试:

1
2
3
var _typedArray= new Uint8Array([-23]);

console.log(isTypedArray(_typedArray));

输出为true

参考资料

Javascript TypedArray 解惑:Uint8Array 与 Uint8ClampedArray 的区别
负数的二进制表示方法

1…313233…47

John

232 日志
43 标签
GitHub Twitter
欢迎关注我的公众号:沉迷Spring
© 2023 johnwonder
由 Hexo 强力驱动 v3.2.0
|
主题 – NexT.Pisces v7.1.1
|
0%