AngularJS源码解析4:Parse解析器的详解
$ParseProvider简介
此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider。
function $ParseProvider() {
var cache = {};
var $parseOptions = {
csp: false,
unwrapPromises: false,
logPromiseWarnings: true
};
this.unwrapPromises = function(value) {
if (isDefined(value)) {
$parseOptions.unwrapPromises = !!value;
return this;
} else {
return $parseOptions.unwrapPromises;
}
};
this.logPromiseWarnings = function(value) {
if (isDefined(value)) {
$parseOptions.logPromiseWarnings = value;
return this;
} else {
return $parseOptions.logPromiseWarnings;
}
};
this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) {
.........
}];
}
记住,不管是哪个provider,我们都要先看它的$get属性。
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') {
cache[exp] = parsedExpression;
}
return parsedExpression;
case 'function':
return exp;
default:
return noop;
}
};
}];
在switch语句中可以看出,如果解析的是函数,则直接返回,如果是字符串,则需要对字符串进行解析。
代码中有两个关键类:
lexer,负责解析字符串,然后生成token,有点类似编译原理中的词法分析器
parser,负责对lexer生成的token,生成执行表达式,其实就是返回一个方法
switch中会先创建一个lexer实例对象,然后把lexer实例的对象传给parser的构造函数,生成parser实例对象,最后调用parser.parse方法生成执行表达式,实质是一个方法。
接下来,我们来看看parser.parse方法:
parse: function (text, json) {
this.text = text;
this.json = json;
this.tokens = this.lexer.lex(text);
if (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;
},
里面的重点是this.tokens = this.lexer.lex(text);这句,它通过lexer解析字符串,生成token。
然后,执行var value = json ? this.primary() : this.statements();这句代码,默认情况下,json是false,所以会执行this.statements()方法,这里将会生成执行表达式。我们来看下parse的statements方法的源码:
statements: function() {
var statements = [];
while (true) {
if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']'))
statements.push(this.filterChain());
if (!this.expect(';')) {
// optimize for the common case where there is only one statement.
// TODO(size): maybe we should not support multiple statements?
return (statements.length === 1)
? statements[0]
: function(self, locals) {
var value;
for (var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement) {
value = statement(self, locals);
}
}
return value;
};
}
}
},
上面的代码中,有一个无限循环的while循环,此循环,会在return语句后,停止。
在while循环中,生成执行表达式的是filterChain方法返回的值,它被push到statements数组中。
最后,我们看return语句,也就是statements方法的返回值。如果表达式数组的长度为1,则返回第一个执行表达式,否则返回一个包装函数,里面是一个循环,循环处理所有的执行表达式,但是只返回最后一个表达式的值。
接下来,我们来看看lex方法:
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;
},
上面的代码主要就是分析传入到lex内的字符串text,主要通过一个while循环,依次检查当前字符是否是数字,是否是变量标识等,假如是数字的话,则转到lexer的
readNumber方法。最后,反正生成了一个token给Parser构造方法生成一个Parser实例对象parser。然后通过这个实例对象parser的parse来解析字符串表达式,生成执行表达式(其实就是一个方法)。
好了,说完了生成执行表达式的代码,其实parse的任务已经完成了。
在上一课的$RootScopeProvider源码解析中,有讲到,$RootScopeProvider的$watch函数中的compileToFn方法调用了$parse方法,而本课主要来讲解$parse如何通过解析字符串来生成执行表达式。
通过,这两课的讲解,大家知道了,angular内置的服务提供者,都带有$get属性,并且真正的定义也在$get属性值中。$RootScopeProvider服务提供者,是用来实例化一个根作用域对象的。
$ParseProvider服务提供者,是用来实例化一个解析器对象的,它主要用来解析页面中的angular表达式的。
加油!
AngularJS源码解析4:Parse解析器的详解的更多相关文章
- Spring源码分析之Bean的创建过程详解
前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...
- Spring源码剖析7:AOP实现原理详解
前言 前面写了六篇文章详细地分析了Spring Bean加载流程,这部分完了之后就要进入一个比较困难的部分了,就是AOP的实现原理分析.为了探究AOP实现原理,首先定义几个类,一个Dao接口: pub ...
- JDK源码分析(12)之 ConcurrentHashMap 详解
本文将主要讲述 JDK1.8 版本 的 ConcurrentHashMap,其内部结构和很多的哈希优化算法,都是和 JDK1.8 版本的 HashMap是一样的,所以在阅读本文之前,一定要先了解 Ha ...
- jQuery 源码分析(十) 数据缓存模块 data详解
jQuery的数据缓存模块以一种安全的方式为DOM元素附加任意类型的数据,避免了在JavaScript对象和DOM元素之间出现循环引用,以及由此而导致的内存泄漏. 数据缓存模块为DOM元素和JavaS ...
- Vue.js 源码分析(十) 基础篇 ref属性详解
ref 被用来给元素或子组件注册引用信息.引用信息将会注册在父组件的 $refs 对象上.如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素:如果用在子组件上,引用就指向组件实例,例如: ...
- vuex源码分析(二) state及strict属性 详解
state也就是vuex里的值,也即是整个vuex的状态,而strict和state的设置有关,如果设置strict为true,那么不能直接修改state里的值,只能通过mutation来设置 例1: ...
- Apache Spark源码走读之16 -- spark repl实现详解
欢迎转载,转载请注明出处,徽沪一郎. 概要 之所以对spark shell的内部实现产生兴趣全部缘于好奇代码的编译加载过程,scala是需要编译才能执行的语言,但提供的scala repl可以实现代码 ...
- spark最新源码下载并导入到开发环境下助推高质量代码(Scala IDEA for Eclipse和IntelliJ IDEA皆适用)(以spark2.2.0源码包为例)(图文详解)
不多说,直接上干货! 前言 其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. ...
- jQuery 源码分析(十九) DOM遍历模块详解
jQuery的DOM遍历模块对DOM模型的原生属性parentNode.childNodes.firstChild.lastChild.previousSibling.nextSibling进行了封装 ...
- jQuery源码分析(九) 异步队列模块 Deferred 详解
deferred对象就是jQuery的回调函数解决方案,它解决了如何处理耗时操作的问题,比如一些Ajax操作,动画操作等.(P.s:紧跟上一节:https://www.cnblogs.com/grea ...
随机推荐
- S EAI 客户主数据导入_test(detail)
一. 客户主数据模板导出 客户主数据和新联系人导入 Account_Config.ini文件 [Public] ConnectString=host="siebel://10.10.1.15 ...
- shell脚本学习指南-学习(2)
1.I/O重定向符:< > >与管道 | #! /bin/bash echo -n "Enter your name!" //输出 printf &qu ...
- Spring Boot 响应jsp
添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http:/ ...
- JS Code Snippet --- Cookie
<a id="quitBtn" href="#" class="exit">Exit</a> <a id=&q ...
- 通过HttpWebRequest实现模拟登陆
1>通过HttpWebRequest模拟登陆 using System; using System.Collections.Generic; using System.Linq; using S ...
- ASP.NET文件上传大小限制
上传限制 我们以为的文件大小限制 我们大家都知道ASP.NET为我们提供了文件上传服务器控件FileUpload,默认情况下可上传的最大文件为4M,如果要改变可上传文件大小限制,那么我们可以在web. ...
- 使用MySQLMTOP监控MySQL性能
一.服务器角色 服务器角色 172.18.35.29 10.160.22.14 (MySQL Master) 10.160.22.47 (MySQL Slave) 监控点 YES NO NO 被监控点 ...
- winscp 使用root身份登录
参考: https://www.haiyun.me/archives/winscp-sftp-sudo-root.html 一般root账户在服务器上会被禁止ssh,此时普通用户通过sudo执行管理员 ...
- Java解析XML文档——dom解析xml
一.前言 用Java解析XML文档,最常用的有两种方法:使用基于事件的XML简单API(Simple API for XML)称为SAX和基于树和节点的文档对象模型(Document Object M ...
- android屏幕页面实现滚动,页面跳转
在 在LinearLayout外面包一层ScrollView即可,如下代码 Apidemo 中关于如何使用ScrollView说明,请参考:<ScrollView xmlns:android=& ...