Angularjs 源码分析2
2020-12-13 05:57
标签:des blog class c code tar 今天这个rootscope可是angularjs里面比较活跃的一个provider,大家可以理解为一个模型M或者VM,它主要负责与控制器或者指令进行数据交互.
今天使用的源码跟上次分析的一样也是1.2.X系列,只不过这次用的是未压缩合并版的,方便大家阅读,可以在这里下载 说起这个$get属性,是每个系统provider都有的,主要是先保存要实例化的函数体,等待instanceinjector.invoke的时候来调用,因为$get的代码比较多,所以先上要讲的那部分,大家可以注意到了,在$get上面有一个digestTtl方法
这个是用来修改系统默认的dirty
check次数的,默认是10次,通过在config里引用rootscopeprovider,可以调用这个方法传递不同的值来修改ttl(short for
Time To Live) 下面来看下$get中的scope构造函数
可以看到在构造函数里定义了很多属性,我们来一一说明一下 通过这段代码,可以看出,系统默认会创建根作用域,并作为$rootScopeprovider实例返回.
创建子级作用域是通过$new方法,我们来看看.
通过分析上面的代码,可以得出 isolate标识来创建独立作用域,这个在创建指令,并且scope属性定义的情况下,会触发这种情况,还有几种别的特殊情况,假如是独立作用域的话,会多一个$root属性,这个默认是指向rootscope的 如果不是独立的作用域,则会生成一个内部的构造函数,把此构造函数的prototype指向当前scope实例 通用的操作就是,设置当前作用域的$$childTail,$$childTail.$$nextSibling,$$childHead,this.$$childTail为生成的子级作用域;设置子级域的$parent为当前作用域,$$prevSibling为当前作用域最后一个子级作用域 说完了创建作用域,再来说说$watch函数,这个比较关键
$watch函数有三个参数,第一个是监控参数,可以是字符串或者函数,第二个是监听函数,第三个是代表是否深度监听,注意看这个代码
这个compileToFn函数其实是调用$parse实例来分析监控参数,然后返回一个函数,这个会在dirty
check里用到,用来获取监控表达式的值,这个$parseprovider也是angularjs中用的比较多的,下面来重点的说下这个provider $parse的代码比较长,在源码文件夹中的ng目录里,parse.js里就是$parse的全部代码,当你了解完parse的核心之后,这部份代码其实可以独立出来,做成自己的计算器程序也是可以的,因为它的核心就是解析字符串,而且默认支持四则运算,运算符号的优先级处理,只是额外的增加了对变量的支持以及过滤器的支持,想想,把这块代码放在模板引擎里也是可以的,说多了,让我们来一步一步的分析parse代码吧. 记住,不管是哪个provider,先看它的$get属性,所以我们先来看看$parse的$get吧
可以看出,假如解析的是函数,则直接返回,是字符串的话,则需要进行parser.parse方法,这里重点说下这个 通过阅读parse.js文件,你会发现,这里有两个关键类 lexer, 负责解析字符串,然后生成token,有点类似编译原理中的词法分析器 parser, 负责对lexer生成的token,生成执行表达式,其实就是返回一个执行函数 看这里
第一句就是创建一个lexer实例,第二句是把lexer实例传给parser构造函数,然后生成parser实例,最后一句是调用parser.parse生成执行表达式,实质是一个函数 现在转到parser.parse里去
视线移到这句this.tokens = this.lexer.lex(text),然后来看看lex方法
这里我们假如传进的字符串是1+2,通常我们分析源码的时候,碰到代码复杂的地方,我们可以简单化处理,因为逻辑都一样,只是情况不一样罢了. 上面的代码主要就是分析传入到lex内的字符串,以一个whileloop开始,然后依次检查当前字符是否是数字,是否是变量标识等,假如是数字的话,则转到
readNumber方法,这里以1+2为例,当前ch是1,然后跳到readNumber方法
上面的代码就是检查从当前index开始的整个数字,包括带小数点的情况,检查完毕之后跳出loop,当前index向前进一个,以待以后检查后续字符串,最后保存到lex实例的token数组中,这里的fn属性就是以后执行时用到的,这里的return
number是利用了JS的闭包特性,number其实就是检查时外层的number变量值.以1+2为例,这时index应该停在+这里,在lex的while
loop中,+检查会跳到最后一个else里,这里有一个对象比较关键,OPERATORS,它保存着所有运算符所对应的动作,比如这里的+,对应的动作是
本文主要分析RootScopeProvider和ParseProvider
RootScopeProvider简介
从$get属性说起
this
.digestTtl =
function
(value) {
if
(arguments.length) {
TTL = value;
}
return
TTL;
};
function
Scope() {
this
.$id = nextUid();
this
.$$phase =
this
.$parent =
this
.$$watchers =
this
.$$nextSibling =
this
.$$prevSibling =
this
.$$childHead =
this
.$$childTail =
null
;
this
[
‘this‘
] =
this
.$root =
this
;
this
.$$destroyed =
false
;
this
.$$asyncQueue = [];
this
.$$postDigestQueue = [];
this
.$$listeners = {};
this
.$$listenerCount = {};
this
.$$isolateBindings = {};
}
var
$rootScope =
new
Scope();
return
$rootScope;
$
new
:
function
(isolate) {
var
ChildScope,
child;
if
(isolate) {
child =
new
Scope();
child.$root =
this
.$root;
// ensure that there is just one async queue per $rootScope and its children
child.$$asyncQueue =
this
.$$asyncQueue;
child.$$postDigestQueue =
this
.$$postDigestQueue;
}
else
{
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if
(!
this
.$$childScopeClass) {
this
.$$childScopeClass =
function
() {
this
.$$watchers =
this
.$$nextSibling =
this
.$$childHead =
this
.$$childTail =
null
;
this
.$$listeners = {};
this
.$$listenerCount = {};
this
.$id = nextUid();
this
.$$childScopeClass =
null
;
};
this
.$$childScopeClass.prototype =
this
;
}
child =
new
this
.$$childScopeClass();
}
child[
‘this‘
] = child;
child.$parent =
this
;
child.$$prevSibling =
this
.$$childTail;
if
(
this
.$$childHead) {
this
.$$childTail.$$nextSibling = child;
this
.$$childTail = child;
}
else
{
this
.$$childHead =
this
.$$childTail = child;
}
return
child;
}
$watch:
function
(watchExp, listener, objectEquality) {
var
scope =
this
,
get = compileToFn(watchExp,
‘watch‘
),
array = scope.$$watchers,
watcher = {
fn: listener,
last: initWatchVal,
get: get,
exp: watchExp,
eq: !!objectEquality
};
lastDirtyWatch =
null
;
// in the case user pass string, we need to compile it, do we really need this ?
if
(!isFunction(listener)) {
var
listenFn = compileToFn(listener || noop,
‘listener‘
);
watcher.fn =
function
(newVal, oldVal, scope) {listenFn(scope);};
}
if
(
typeof
watchExp ==
‘string‘
&& get.constant) {
var
originalFn = watcher.fn;
watcher.fn =
function
(newVal, oldVal, scope) {
originalFn.call(
this
, newVal, oldVal, scope);
arrayRemove(array, watcher);
};
}
if
(!array) {
array = scope.$$watchers = [];
}
// we use unshift since we use a while loop in $digest for speed.
// the while loop reads in reverse order.
array.unshift(watcher);
return
function
deregisterWatch() {
arrayRemove(array, watcher);
lastDirtyWatch =
null
;
};
}
get = compileToFn(watchExp,
‘watch‘
)
this
.$get = [
‘$filter‘
,
‘$sniffer‘
,
‘$log‘
,
function
($filter, $sniffer, $log) {
$parseOptions.csp = $sniffer.csp;
promiseWarning =
function
promiseWarningFn(fullExp) {
if
(!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp))
return
;
promiseWarningCache[fullExp] =
true
;
$log.warn(
‘[$parse] Promise found in the expression ‘
+ fullExp +
‘. ‘
+
‘Automatic unwrapping of promises in Angular expressions is deprecated.‘
);
};
return
function
(exp) {
var
parsedExpression;
switch
(
typeof
exp) {
case
‘string‘
:
if
(cache.hasOwnProperty(exp)) {
return
cache[exp];
}
var
lexer =
new
Lexer($parseOptions);
var
parser =
new
Parser(lexer, $filter, $parseOptions);
parsedExpression = parser.parse(exp,
false
);
if
(exp !==
‘hasOwnProperty‘
) {
// Only cache the value if it‘s not going to mess up the cache object
// This is more performant that using Object.prototype.hasOwnProperty.call
cache[exp] = parsedExpression;
}
return
parsedExpression;
case
‘
function
‘:
return
exp;
default
:
return
noop;
}
};
}];
var
lexer =
new
Lexer($parseOptions);
var
parser =
new
Parser(lexer, $filter, $parseOptions);
parsedExpression = parser.parse(exp,
false
);
parse:
function
(text, json) {
this
.text = text;
//TODO(i): strip all the obsolte json stuff from this file
this
.json = json;
this
.tokens =
this
.lexer.lex(text);
console.log(
this
.tokens);
if
(json) {
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
this
.assignment =
this
.logicalOR;
this
.functionCall =
this
.fieldAccess =
this
.objectIndex =
this
.filterChain =
function
() {
this
.throwError(
‘is not valid json‘
, {text: text, index: 0});
};
}
var
value = json ?
this
.primary() :
this
.statements();
if
(
this
.tokens.length !== 0) {
this
.throwError(
‘is an unexpected token‘
,
this
.tokens[0]);
}
value.literal = !!value.literal;
value.constant = !!value.constant;
return
value;
}
lex:
function
(text) {
this
.text = text;
this
.index = 0;
this
.ch = undefined;
this
.lastCh =
‘:‘
;
// can start regexp
this
.tokens = [];
var
token;
var
json = [];
while
(
this
.index
this
.text.length) {
this
.ch =
this
.text.charAt(
this
.index);
if
(
this
.is(
‘"\‘‘
)) {
this
.readString(
this
.ch);
}
else
if
(
this
.isNumber(
this
.ch) ||
this
.is(
‘.‘
) &&
this
.isNumber(
this
.peek())) {
this
.readNumber();
}
else
if
(
this
.isIdent(
this
.ch)) {
this
.readIdent();
// identifiers can only be if the preceding char was a { or ,
if
(
this
.was(
‘{,‘
) && json[0] ===
‘{‘
&&
(token =
this
.tokens[
this
.tokens.length - 1])) {
token.json = token.text.indexOf(
‘.‘
) === -1;
}
}
else
if
(
this
.is(
‘(){}[].,;:?‘
)) {
this
.tokens.push({
index:
this
.index,
text:
this
.ch,
json: (
this
.was(
‘:[,‘
) &&
this
.is(
‘{[‘
)) ||
this
.is(
‘}]:,‘
)
});
if
(
this
.is(
‘{[‘
)) json.unshift(
this
.ch);
if
(
this
.is(
‘}]‘
)) json.shift();
this
.index++;
}
else
if
(
this
.isWhitespace(
this
.ch)) {
this
.index++;
continue
;
}
else
{
var
ch2 =
this
.ch +
this
.peek();
var
ch3 = ch2 +
this
.peek(2);
var
fn = OPERATORS[
this
.ch];
var
fn2 = OPERATORS[ch2];
var
fn3 = OPERATORS[ch3];
if
(fn3) {
this
.tokens.push({index:
this
.index, text: ch3, fn: fn3});
this
.index += 3;
}
else
if
(fn2) {
this
.tokens.push({index:
this
.index, text: ch2, fn: fn2});
this
.index += 2;
}
else
if
(fn) {
this
.tokens.push({
index:
this
.index,
text:
this
.ch,
fn: fn,
json: (
this
.was(
‘[,:‘
) &&
this
.is(
‘+-‘
))
});
this
.index += 1;
}
else
{
this
.throwError(
‘Unexpected next character ‘
,
this
.index,
this
.index + 1);
}
}
this
.lastCh =
this
.ch;
}
return
this
.tokens;
}
readNumber:
function
() {
var
number =
‘‘
;
var
start =
this
.index;
while
(
this
.index
this
.text.length) {
var
ch = lowercase(
this
.text.charAt(
this
.index));
if
(ch ==
‘.‘
||
this
.isNumber(ch)) {
number += ch;
}
else
{
var
peekCh =
this
.peek();
if
(ch ==
‘e‘
&&
this
.isExpOperator(peekCh)) {
number += ch;
}
else
if
(
this
.isExpOperator(ch) &&
peekCh &&
this
.isNumber(peekCh) &&
number.charAt(number.length - 1) ==
‘e‘
) {
number += ch;
}
else
if
(
this
.isExpOperator(ch) &&
(!peekCh || !
this
.isNumber(peekCh)) &&
number.charAt(number.length - 1) ==
‘e‘
) {
this
.throwError(
‘Invalid exponent‘
);
}
else
{
break
;
}
}
this
.index++;
}
number = 1 * number;
this
.tokens.push({
index: start,
text: number,
json:
true
,
fn:
function
() {
return
number; }
});
}
<
‘+‘
:
function
(self, locals, a,b){
a=a(self, locals); b=b(self, locals);
if
(isDefined(a)) {
if
(isDefined(b)) {
return
a + b;
}
return
a;
}
return
isDefined(b)?b:undefined;}
下一篇:python3学习之匿名函数