john's tech blog

hope is coming


  • 首页

  • 标签

  • 归档

angular源码分析之运行流程

发表于 2019-03-12 | 更新于 2019-07-06

梳理下angular的运行流程:

JQLitePrototype.ready

JQLite.on
此处涉及到expandoStore 的问题
on里面主要是addHandler 放到eventFns里

等到onload后 再调用

angularInit
bootstrap
doBootstrap
createInjector
injectionArgs
invoke -> $RootScopeProvider

以表达式 文本插值来梳理:

collectDirectives -> addTextInterpolateDirective -> $interpolate

$interpolate函数中调用parse函数,parse函数里
addInterceptor(parsedExpression, interceptorFn)
parsedExpression 为解析后的表达式,interceptorFn 为 parseStringifyInterceptor

parsedExpression外面再封装一层 regularInterceptedExpression函数

regularInterceptedExpression函数的watchDelegate 为inputsWatchDelegate。

$interpolate函数返回的 interpolateFn实际是 调用 extend函数过后的函数:

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
//返回interpolationFn方法
return extend(function interpolationFn(context) {
var i = 0;
var ii = expressions.length;
var values = new Array(ii);

try {
//根据表达式来解析
for (; i < ii; i++) {
//调用parseFns得到value
values[i] = parseFns[i](context);
}

return compute(values);
} catch (err) {
$exceptionHandler($interpolateMinErr.interr(text, err));
}

}, {
// all of these properties are undocumented for now
exp: text, //just for compatibility with regular watchers created via $watch
expressions: expressions,
$$watchDelegate: function(scope, listener) {
var lastValue;
return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
var currValue = compute(values);
if (isFunction(listener)) {
listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
}
lastValue = currValue;
});
}
});

所以此时的调用文本指令的时候,此时scope已经为childScope:

1
2
3
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});

这里的interpolateFn 就是上面的function interpolationFn(context) 函数,

因为带有watchDelegate,所以直接调用watchDelegate返回:

1
2
3
4
5
6
7
8
9
10
11
12
//如果被解析过后的watch函数有watch的代理那就返回这个代理watch函数
//
//会创建一个watcher
//watcher里放入listener(类似spring里的监听器)
//数据变化时会触发listener
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
var get = $parse(watchExp);//watchExp为函数时直接返回watchExp

if (get.$$watchDelegate) {
return get.$$watchDelegate(this, listener, objectEquality, get, watchExp);
}
}

从而进入
scope.$watchGroup :

1
2
3
4
5
6
7
8
9
10
11
function $watchGroup: function(watchExpressions, listener) {

if (watchExpressions.length === 1) {
// Special case size of one
return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) {
newValues[0] = value;
oldValues[0] = oldValue;
listener(newValues, (value === oldValue) ? newValues : oldValues, scope);
});
}
}

此时watchExpressions就是 parseFns ,那继续进入watch,那此时的watchDelegate就是

inputsWatchDelegate。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) {
var inputExpressions = parsedExpression.inputs;
var lastResult;

if (inputExpressions.length === 1) {
var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails
inputExpressions = inputExpressions[0];
return scope.$watch(function expressionInputWatch(scope) {
var newInputValue = inputExpressions(scope);
if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) {
lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]);
oldInputValueOf = newInputValue && getValueOf(newInputValue);
}
return lastResult;
}, listener, objectEquality, prettyPrintExpression);
}
}

实际这个插值的watchers就只有一个,就是这个expressionInputWatch

scala中caseclass自定义的apply方法

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

今天在看scalaj-http源码的时候,看到了case class 里定义了apply函数,这个函数在初始化的

时候并不调用,需要再次初始化下才调用这个apply函数,源码定义如下:

1
2
3
4
5
6
7
8
9
10
case class StringBodyConnectFunc(data: String) extends Function2[HttpRequest, HttpURLConnection, Unit] {
//apply函数在第一次初始化的时候不会调用
def apply(req: HttpRequest, conn: HttpURLConnection): Unit = {
conn.setDoOutput(true)
conn.connect
conn.getOutputStream.write(data.getBytes(req.charset))
}

override def toString = "StringBodyConnectFunc(" + data + ")"
}

关于这个我们可以自己测试下,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
case class caseclass1(x:Int,y:String) {
// def apply() = {

// println(x)
// println("case apply")
// }
def apply() = {

caseclass1(5,"3")
}
}

//定义两个apply会报错 error: method apply is defined twice
object caseclass1 {
// def apply() = {

// println("object apply")
// }
}
val class2 = caseclass1(1,"ss")
val class1 = caseclass1(2,"33")

println(class2.x)
println(class1.x)

输出
1
2

angular源码剖析之Provider系列--CacheFactoryProvider

发表于 2018-03-01 | 更新于 2019-05-30

CacheFactoryProvider 简介

源码里是这么描述的:
Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to
them.
意思就是通过cacheFactory可以构造一个Cache对象来给予访问和执行权限。

这个Cache对象官方文档是这么说的:
A cache object used to store and retrieve data, primarily used by $http and the script directive to cache templates and other data.
用我自己的话来说就是 提供存储和访问缓存对象的服务,angular内部主要被$http,script指令用于
缓存template和其他数据。我们自己可以在Controller内部使用。

CacheFactoryProvider 用法

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
<!doctype html>
<html lang="en" ng-app="myapp">
<head>
<meta charset="UTF-8">
<meta name="Generator" content="EditPlus®">
<meta name="Author" content="">
<meta name="Keywords" content="">
<meta name="Description" content="">
<title>Document</title>
<script src="js/angular.js"></script>
</head>
<body>
<div ng-controller="MyController">
</div>
<script >
var app=angular.module('myapp',[]);
app.controller('MyController',function($scope,$cacheFactory){
var myCache = $cacheFactory("myCache",{capacity: 6});
//var myCache1 = $cacheFactory("myCache",{capacity: 6}); //会报错
myCache.put("name","john");
myCache.put("name1","wonder");
myCache.put("name","john");
});

app.controller('getCacheController',['$scope','$cacheFactory',
function($scope,$cacheFactory){
var cache = $cacheFactory.get('myCache');
var name = cache.get('name');
console.log(name); //打印john
}]);
</script>

</body>
</html>

看了上面这个一个简单的例子,读者可能会产生如下疑惑:

  1. 不是名为CacheFactoryProvider吗,怎么在代码里只看到cacheFactory呢?
  2. cacheFactory是怎么存储对象的?
    下面我们来依次解答这两个问题

$cacheFactory的注入

我们首先来看第一个问题,这个问题要牵涉到angular里面的依赖注入机制,我们前面的分析也讲过,
angular会在启动之前通过调用publishExternalAPI 函数先发布一些扩展API,同时定义ng
模块,在定义ng模块的时候就传入了注入provider的方法

1
2
3
4
5
6
7
8
angularModule('ng', ['ngLocale'], ['$provide',
//通过参数注入$provide
function ngModule($provide) {
///部分代码省略
$provide.provider({
$cacheFactory: $CacheFactoryProvider,
});
}])

$cacheFactory出现了,它是通过javascript的键值对象作为键传给provider方法。那么它是如何存储
对象的呢?首先我们看它的定义:

CacheFactoryProvider的定义

内部定义了依赖注入核心的$get方法,$get方法返回cacheFactory方法(也就是上面实例代码里的
$cacheFactory参数)。

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
58
59
60
61
62
63
64
65
66
67
function $CacheFactoryProvider() {
//定义$get方法供依赖调用
//controller中获取cacheFactory时会调用此方法
//这个$get方法也是获取provider的关键方法
this.$get = function() {
var caches = {};//闭包的一个运用

function cacheFactory(cacheId, options) {
//部分代码省略
//可以用if来判断
if (cacheId in caches) {//如果caches中已经存在cacheId
//实例代码里抛出的错误就在此处、
//统一调用minErr函数
throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!"
, cacheId);
}

var size = 0,
//把options 和{id:cacheId} 放入{} 中 不是深拷贝
stats = extend({}, options, {id: cacheId}),
data = createMap(),//通过Object.create(null) 创建个空对象
capacity = (options && options.capacity) || Number.MAX_VALUE,
lruHash = createMap(),
freshEnd = null,
staleEnd = null;
//返回caches中的一个对象
return caches[cacheId] = {
//省略部分代码
//存储里讲解
put:function(key,value){
},
get: function(key) {
},
remove: function(key) {
},
removeAll: function() {
},
destroy: function() {
},
info: function() {
}
}
//刷新节点次序
function refresh(entry) {
}
//
function link(nextEntry, prevEntry) {

}
}

//所有的缓存
cacheFactory.info = function() {
var info = {};
forEach(caches, function(cache, cacheId) {
info[cacheId] = cache.info();
});
return info;
};

cacheFactory.get = function(cacheId) {
return caches[cacheId];
};

return cacheFactory;
}
}

CacheFactoryProvider的存储

存储分为这几个核心方法:put,refresh,remove,link

put函数

value会放入data对象中,key会放入lruHash链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 put: function(key, value) {
if (isUndefined(value)) return;
//如果设定的capcity小于maxvalue
if (capacity < Number.MAX_VALUE) {
//lruHash 存了当前的key 还有可能是 p 和n (previous和next)
var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
//刷新各节点的次序
refresh(lruEntry);//把当前entry放入链表末尾
}
//如果key 在data里不存在 那么增加size
if (!(key in data)) size++;
data[key] = value;
//当大于capacity时 会清除最早加入的那个
if (size > capacity) {
this.remove(staleEnd.key);//移除淘汰节点stableEnd
}
return value;
}

get函数

Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object
获取存储在cache对象中的指定数据

1
2
3
4
5
6
7
8
9
10
11
12
get: function(key) {
if (capacity < Number.MAX_VALUE) {
var lruEntry = lruHash[key];
if (!lruEntry) return;
// 获取first的时候 因为staleEnd为first 所以会让staleEnd指向 second
// 内部会执行link 使得 second.p = null
// first.p = third third.n = first
//stableEnd为 second freshEnd为first
refresh(lruEntry);
}
return data[key];
}

remove函数

Removes an entry from the {@link $cacheFactory.Cache Cache} object.
从cache对象删除一个entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
remove: function(key) {
//如果capacity小于maxvalue
if (capacity < Number.MAX_VALUE) {
//先取出当前key的entry
var lruEntry = lruHash[key];
if (!lruEntry) return;
//第一次超过时 freshEnd 为third lryEntry为first
if (lruEntry == freshEnd) freshEnd = lruEntry.p;
//第一次超过时 staleEnd 为first lryEntry为first
//所以 会让 stalEnd 指向second 以便于下次移除时
if (lruEntry == staleEnd) staleEnd = lruEntry.n;
//把淘汰节点的一个节点选中
//第一次超过时 lryEntry.n为 second lryEntry.p 为null
//执行结果为 second.p = null
link(lruEntry.n,lruEntry.p);
//把当前key从lruHash中删除
delete lruHash[key];
}
if (!(key in data)) return;
delete data[key];
size--;
}

refresh函数

makes the entry the freshEnd of the LRU linked list。
把entry 放入链表的末尾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function refresh(entry) {
if (entry != freshEnd) {
if (!staleEnd) { //staleEnd为空那么就让他指向当前entry
staleEnd = entry;
} else if (staleEnd == entry) {
//如果淘汰节点等于当前节点
staleEnd = entry.n; //用于把 当前的下一个节点 用作淘汰节点
}
//放入第一个元素时 entry.n,entry.p都为undefined
link(entry.n, entry.p); //当前的上一个节点 和当前的下一个节点
link(entry, freshEnd); // 当前的节点 和 最新的末尾节点
freshEnd = entry;
freshEnd.n = null;
//第一次执行完 结果为: freshEnd = first staleEnd为first
//first.p=null first.n=null
//第二次执行完 结果为:freshEnd = second staleEnd为first
// first.p=null first.n= second
// scecond.p = first scecond.n = null
//第三次执行完 freshEnd = third staleEnd为first first.p=null
//first.n= second
// second.p = first second.n = null
// third.p = second third.n = null
}
}

link函数

bidirectionally(双向链表) links two entries of the LRU linked list
双向链接链表里的两个元素。

1
2
3
4
5
6
7
8
9
10
function link(nextEntry, prevEntry) {
//undefined 不等于undefined
if (nextEntry != prevEntry) {
//
if (nextEntry) nextEntry.p = prevEntry;
//p stands for previous, 'prev' didn't minify
if (prevEntry) prevEntry.n = nextEntry;
//n stands for next, 'next' didn't minify
}
}
参考资料:
  1. 【算法】—— LRU算法
  2. 缓存淘汰算法–LRU算法
  3. 漫画:什么是LRU算法?
  4. 头条面试题:如何实现 LRU 原理?

linux shell命令之sed

发表于 2018-02-28 | 更新于 2019-05-17

linux命令学习之sed

今天在看beego工具bee源码的时候,
看到它的makefile文件开头有这么一段命令

1
VERSION = $(shell grep 'const version' cmd/commands/version/version.go | sed -E 's/.*"(.+)"$$/v\1/')

动脑思考了下它的含义,总结如下:
调用shell命令grep 查找cmd/commands/version/version.go文件中的const version ,我们看到
这个文件中的const version 一行为const version = "1.9.1"
然后调用sed命令把这个文本替换为v1.9.1 ,最后赋值给VERSION变量
sed -e命令就是多点编辑命令,比如 sed -e ‘/v1/v2/‘就是把v1替换为v2

sed元字符(..) 匹配子串,保存匹配的字符,如s/(love)able/\1rs,loveable被替换成lovers。
相当于正则表达式

参考资料:
sed命令
Linux sed命令详解

scala中的合成函数(synthetic function)

发表于 2017-11-20 | 更新于 2019-05-07

今天看到akka http库中有这么一段函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//R <: Rendering 意思是 renderQuery 可以设置任何Rendering类的子类
def renderQuery[R <: Rendering](r: R, query: Query, charset: Charset,
keep: CharPredicate = `strict-query-char-np`): r.type = {
//函数内部定义的函数
def enc(s: String): Unit = encode(r, s, charset, keep, replaceSpaces = true)
@tailrec def append(q: Query): r.type =
q match {
case Query.Empty ⇒ r
case Query.Cons(key, value, tail) ⇒
if (q ne query) r ~~ '&'
enc(key)
if (value ne Query.EmptyValue) r ~~ '='
enc(value)
append(tail)
}
append(query)
}

我查了下ne 就是和eq 类似的,相当于not equal 和equal的关系。这个函数字面意思就是显示
查询参数的,看到里面的q ne query 这一句来了兴趣,查看stackoverflow上是这么写的:

A synthetic field, instead, is a field generated by the compiler to work around
the underlying JVM limitations, especially when dealing with inner anonymous
classes, a concept extraneous to the JVM

参考资料:
What’s the difference between A<:B and +B in Scala?
Scala的eq,ne,equals,==方法与Java异同
Synthetic Function “##” in scala

1…141516…47

John

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