john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

angularjs1.5.8源码解析之value服务

发表于 2020-03-29

angularjs中模块的value()方法可以对模块数据模型进行扩展,使得不同controller可以进行通信,
充当全局变量的作用。使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 var app=angular.module('app',[]);
app.value('val',{});

app.controller('con',['$scope','val',function($scope,ssr){
$scope.msg='';
$scope.submit=function(temp)
{

//向全局变量存入msg
ssr.save=temp;
}

}])

app.controller('con2',['$scope','val',function($scope,ssr){
$scope.showMsg='';
$scope.download=function()
{

//获取全局变量
$scope.showMsg=val.save;
}

}])

跟我们之前分析过的decorator一样,value方法内部实际上也是调用$provide.value函数,我们先看看
moduleInstance的value方法:

1
2
3
4
5
6
7
8
9
10
11
var moduleInstance = {
value: invokeLater('$provide', 'value'),
}

function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}

可以清楚的看到,调用

1
2
3
4
5
6
7
loadModules的时候再调用$provide.value方法

```js
//用个valueFn包装下,返回内部带有val的方法valueRef
function value(name, val) { return factory(name, valueFn(val), false); }

function valueFn(value) {return function valueRef() {return value;};}

factory方法我们之前分析过,内部会把

provider, undefined, serviceName);```的时候实际上就会调用```valueRef```函数把存放的对象取出来。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们有时候会调用```$rootElement```这个对象,那找遍了angular内部并没有显示的声明$rootElementProvider方法,那么它到底存储在哪呢?
它就是在angular启动的时候调用```doBootstrap```的时候压入modules数组顶部然后调用的$provide.value把找到的ng-app元素用jqLite包装成jqLite对象,
然后放入全局变量$rootElement中,代码如下:

```js
//传入的modules 比如html中定义的ng-app="myApp"
//那么为["myApp"]
//正常的话 这时 modules已经初始化
modules = modules || [];
//unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。
modules.unshift(['$provide', function($provide) {
//调用$provide方法
//设置$rootElement
$provide.value('$rootElement', element);
}]);

下篇我们分析下angular的启动函数doBootStrap或者jqLite对象中的扩展方法或者一些常用函数。

angularjs1.5.8源码解析之extend函数

发表于 2020-03-28

angularjs中很多常用函数在我们平时的js开发中很有帮助,可以极大的帮助我们提高开发效率,
今天我们继续来学习下它里面的一个频繁使用的函数extend.

extend函数

文档中是这样描述的:Extends the destination object dst by copying own enumerable properties from the src object(s) to dst。
翻译下就是通过拷贝src对象的枚举属性到dst对象来实现扩展dst对象。

1
2
3
4
5
6
function extend(dst) {
//slice 返回一个新的数组,包含从 start 到 end (如果指定end,不包括该元素)的 arrayObject 中的元素。
//end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。
//如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
return baseExtend(dst, slice.call(arguments, 1), false);
}

其实该函数的核心是内部baseExtend函数:

baseExtend函数

deep参数为true代表是深拷贝,深拷贝简单点说就是通过递归拷贝把所有内部对象克隆复制。

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
function baseExtend(dst, objs, deep) {
var h = dst.$$hashKey;

for (var i = 0, ii = objs.length; i < ii; ++i) {
var obj = objs[i];
//obj 不是对象 且不是函数 那就跳过
if (!isObject(obj) && !isFunction(obj)) continue;
var keys = Object.keys(obj);
for (var j = 0, jj = keys.length; j < jj; j++) {
var key = keys[j];
var src = obj[key];

//如果深拷贝 且当前对象是object类型
if (deep && isObject(src)) {
//如果是日期
if (isDate(src)) {
dst[key] = new Date(src.valueOf());
} else if (isRegExp(src)) {
//如果是正则
dst[key] = new RegExp(src);
} else if (src.nodeName) {
//如果是dom节点
dst[key] = src.cloneNode(true);
} else if (isElement(src)) {
//如果是dom元素或者 jQuery对象
dst[key] = src.clone();
} else {
//先把dst[key]赋空对象或空数组
if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {};
//递归调用
baseExtend(dst[key], [src], true);
}
} else {
dst[key] = src;
}
}
}

return dst;
}

angularjs另外一个根extend对应的merge函数就是baseExtend深拷贝的实现。

merge函数

1
2
3
function merge(dst) {
return baseExtend(dst, slice.call(arguments, 1), true);
}

angular1.5.8源码解析之jqLiteBuildFragment函数

发表于 2020-03-26 | 更新于 2020-03-27

这次我们继续分析上篇中提到的jqLiteBuildFragment函数,顾名思义,它主要的作用就是根据我们传入的html字符串创建相应的html片段。

jqLiteBuildFragment

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
function jqLiteBuildFragment(html, context) {
var tmp, tag, wrap,
fragment = context.createDocumentFragment(),
nodes = [], i;

if (jqLiteIsTextNode(html)) {
// Convert non-html into a text node
//如果是文本那就创建文本
nodes.push(context.createTextNode(html));
} else {
// Convert html into DOM nodes
tmp = fragment.appendChild(context.createElement("div"));
tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
wrap = wrapMap[tag] || wrapMap._default;

//不是属于xhtml标记的 像<div/> 的就变成<div></div>
tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];

// Descend through wrappers to the right content
//下降包装器到正确的内容
i = wrap[0];
while (i--) {
tmp = tmp.lastChild;
}
//
nodes = concat(nodes, tmp.childNodes);

tmp = fragment.firstChild;
tmp.textContent = "";
}
// Remove wrapper from fragment
//移除包装器
fragment.textContent = "";
fragment.innerHTML = ""; // Clear inner HTML
//遍历nodes
forEach(nodes, function(node) {
fragment.appendChild(node);
});

return fragment;
}

首先angularjs是如何判断它是文本节点呢?

jqLiteIsTextNode

1
2
3
4
 var HTML_REGEXP = /<|&#?\w+;/;
function jqLiteIsTextNode(html) {
return !HTML_REGEXP.test(html);
}

通过一个HTML_REGEXP正则表达式,就判断是不是文本节点了,正则意思就是没有<或者且不带&#或&标记的就是文本标记了,&ss;和&#ss;都属于html

接下来如果获取标记名称呢?通过一个TAG_NAME_REGEXP正则

1
var TAG_NAME_REGEXP = /<([\w:-]+)/;

意思就是带有如<div这样的标记就取出div来。然后通过预先定义好的包装映射集合获取到包装器

1
2
3
4
5
6
7
8
9
var wrapMap = {
'option': [1, '<select multiple="multiple">', '</select>'],

'thead': [1, '<table>', '</table>'],
'col': [2, '<table><colgroup>', '</colgroup></table>'],
'tr': [2, '<table><tbody>', '</tbody></table>'],
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
'_default': [0, "", ""]
};

这个wrap其实都是应该有父节点的html元素,接下来就个重要的XHTML_TAG_REGEXP正则,可以把我们的
xhtml标记给替换掉

1
2
3
var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi;

var _rh = html.replace(XHTML_TAG_REGEXP, "<$1></$2>");

可以把类似

替换为
这个样子。

最后就是通过包装器预先设置好的层级获取到最后我们传入的html节点,然后遍历节点列表,append到fragment上

这里也巧妙的封装了contact和slice函数

1
2
3
4
var slice = [].slice; //数组的slice函数
function concat(array1, array2, index) {
return array1.concat(slice.call(array2, index));
}

我们从这里可以学到平时在js里怎么创建自己的html片段了。下篇我们来分析下jqLite的扩展函数。

ClassPathXmlApplicationContext中针对configLocation配置路径的解析

发表于 2020-03-22 | 更新于 2020-03-24

在使用ClassPathXmlApplicationContext 传入配置文件时,解析配置文件的流程如下:

  1. AbstractRefreshableConfigApplicationContext.resolvePath

  2. getEnvironment() -> createEnvironment() -> StandardEnvironment

  3. 实例化StandardEnvironment 时 调用customizePropertySources函数

  4. customizePropertySources时加入MapPropertySource ,SystemEnvironmentPropertySource

  5. 调用StandardEnvironment父类AbstractEnvironment.resolveRequiredPlaceholders

  6. 内部调用PropertySourcesPropertyResolver父类AbstractPropertyResolver的
    resolveRequiredPlaceholders,此时propertySources传入PropertySourcesPropertyResolver

  7. 调用doResolvePlaceholders 传入this::getPropertyAsRawString,实际就是
    ,PropertySourcesPropertyResolver的getProperty方法作为函数式接口PlaceholderResolver的 参数

阅读随记:

  1. CopyOnWriteArrayList 内部indexOf是根据传入的对象的equals方法来比较
  2. PropertySource.named返回的ComparisonPropertySource 只会比较两个对象的name
  3. jdk里AbstractCollection的toString方法

angularjs源码分析--创建service

发表于 2020-03-21 | 更新于 2020-03-23

angular中的service服务方法很多地方都会可以用到,内部其实也是依赖angularjs强大的依赖注入能力。
我们先看昨天文章模块创建使用的案例:

angular.module

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建myModule模块
var myModule = angular.module('myModule', []);
myModule.service('myService', function() {
this.my = 0;
});
//创建注入器,把service放入providerCache
var injector = angular.injector(["myModule"]);

function explicit(serviceA) {alert(serviceA.my);};
explicit.$inject = ['myService'];

//获取providerCache,调用构造函数,弹出0.
injector.invoke(explicit);

service

关于angular模块的创建,我们上一篇文章已经讲过,我们就看下模块的service方法,在新建模块后这个模块
实例就拥有了service函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var invokeQueue = [];

var moduleInstance = {
_invokeQueue: invokeQueue,
service: invokeLaterAndSetModuleName('$provide', 'service');
}

function invokeLaterAndSetModuleName(provider, method) {
//实际的service方法 ,通过闭包把$provide 和service 放入返回函数中
return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]);
//比如service 就是 provider= $provide method = service
return moduleInstance;
};
}

那么我们就可以知道在调用service的方法其实就是把$provide,service,还有参数(这边就是arguments)放入invokeQueue中,
从名字看出来这是个调用队列,那就有个疑问了,这个invokeQueue是干啥的呢? 什么时候会调用这个invokeQueue呢?
那就是createInjector函数中的loadModules函数。

loadModules

loadModules函数会在createInjector的时候去调用,看看它对service起的作用

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
function createInjector(modulesToLoad){

var runBlocks = loadModules(modulesToLoad);

//模块加载
function loadModules(modulesToLoad) {
assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
var runBlocks = [], moduleFn;
//遍历modulesToLoad数组
forEach(modulesToLoad, function(module) {
if (loadedModules.get(module)) return;
loadedModules.put(module, true);

//调用invokeQueue
function runInvokeQueue(queue) {
var i, ii;
for (i = 0, ii = queue.length; i < ii; i++) {
var invokeArgs = queue[i], //invokeArgs也是个数组
//获取provider
provider = providerInjector.get(invokeArgs[0]);
//调用provider中的方法
//比如injectorProvider 调用invoke方法 传入函数
//函数的参数可以注入
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}

try {
if (isString(module)) {
//获取module
//moduleFn就是 moduleInstance
moduleFn = angularModule(module);
//moduleFn.requires 比如['ngLocale']
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
runInvokeQueue(moduleFn._invokeQueue);
//像module.service时加入的service ,那么需要把这些service加入prividerCache
runInvokeQueue(moduleFn._configBlocks);
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
}
} catch (e) {
if (isArray(module)) {
module = module[module.length - 1];
}

throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
module, e.stack || e.message || e);
}
});
return runBlocks;
}
}

我们看到关键一句代码

1
2
3
4
5
6
7
8
9
10
11
12

传入我们创建service时push进去的数组元素,最关键的就是这两句代码.

```js
function runInvokeQueue(){
provider = providerInjector.get(invokeArgs[0]);
//调用provider中的方法
//比如injectorProvider 调用invoke方法 传入函数
//函数的参数可以注入
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);

}

invokeArgs[0]就是$provide,invokeArgs[1]就是service,那么实际调用的就是$provide.service函数,参数就是['myService', function() { this.my = 0; }],关于$provide.service函数我们下篇文章再分析。

1…678…47

John

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