这次我们来看一下angular的Sandboxing Angular Expressions。关于内置方法的,核心有两块:Lexer和Parser。其中大家对$parse可能更了解一点。好了不多废话,先看Lexer的内部结构:
1.Lexer
//构造函数 var Lexer = function(options) { this.options = options; }; //原型 Lexer.prototype = { constructor: Lexer, lex: function(){}, is: function(){}, peek: function(){ /* 返回表达式的下一个位置的数据,如果没有则返回false */ }, isNumber: function(){ /* 判断当前表达式是否是一个数字 */ }, isWhitespace: function(){/* 判断当前表达式是否是空格符 */}, isIdent: function(){/* 判断当前表达式是否是英文字符(包含_和$) */}, isExpOperator: function(){/* 判断当时表达式是否是-,+还是数字 */}, throwError: function(){ /* 抛出异常 */}, readNumber: function(){ /* 读取数字 */}, readIdent: function(){ /* 读取字符 */}, readString: function(){ /*读取携带''或""的字符串*/ } };
这里指出一点,因为是表达式。所以类似"123"这类的东西,在Lexer看来应该算是数字而非字符串。表达式中的字符串必须使用单引号或者双引号来标识。Lexer的核心逻辑在lex方法中:
lex: function(text) { this.text = text; this.index = 0; this.tokens = []; while (this.index < this.text.length) { var ch = this.text.charAt(this.index); if (ch === '"' || ch === "'") { /* 尝试判断是否是字符串 */ this.readString(ch); } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { /* 尝试判断是否是数字 */ this.readNumber(); } else if (this.isIdent(ch)) { /* 尝试判断是否是字母 */ this.readIdent(); } else if (this.is(ch, '(){}[].,;:"htmlcode">var OPERATORS = extend(createMap(), { '+':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) "htmlcode">var _l = new Lexer({}); var a = _l.lex("a = a + 1"); console.log(a);结合之前的lex方法,我们来回顾下代码执行过程:
1.index指向'a'是一个字母。匹配isIdent成功。将生成的token存入tokens中
2.index指向空格符,匹配isWhitespace成功,同上
3.index指向=,匹配操作运算符成功,同上
4.index指向空格符,匹配isWhitespace成功,同上
5.index指向'a'是一个字母。匹配isIdent成功。同上
7.index指向+,匹配操作运算符成功,同上
8.index指向空格符,匹配isWhitespace成功,同上
9.index指向1,匹配数字成功,同上
以上则是"a = a + 1"的代码执行过程。9步执行结束之后,跳出while循环。刚才我们看到了,每次匹配成功,源码会生成一个token。因为匹配类型的不同,生成出来的token的键值对略有不同:
number:{ index: start, text: number, constant: true, value: Number(number) }, string: { index: start, text: rawString, constant: true, value: string }, ident: { index: start, text: this.text.slice(start, this.index), identifier: true /* 字符表示 */ }, '(){}[].,;:"操作符": { index: this.index, text: token, operator: true } //text是表达式,而value才是实际的值number和string其实都有相对应的真实值,意味着如果我们表达式是2e2,那number生成的token的值value就应该是200。到此我们通过lexer类获得了一个具有token值得数组。从外部看,实际上Lexer是将我们输入的表达式解析成了token json。可以理解为生成了表达式的语法树(AST)。但是目前来看,我们依旧还没有能获得我们定义表达式的结果。那就需要用到parser了。
2.Parser
先看一下Parser的内部结构:
//构造函数 var Parser = function(lexer, $filter, options) { this.lexer = lexer; this.$filter = $filter; this.options = options; }; //原型 Parser.prototype = { constructor: Parser, parse: function(){}, primary: function(){}, throwError: function(){ /* 语法抛错 */}, peekToken: function(){}, peek: function(){/*返回tokens中的第一个成员对象 */}, peekAhead: function(){ /* 返回tokens中指定成员对象,否则返回false */}, expect: function(){ /* 取出tokens中第一个对象,否则返回false */ }, consume: function(){ /* 取出第一个,底层调用expect */ }, unaryFn: function(){ /* 一元操作 */}, binaryFn: function(){ /* 二元操作 */}, identifier: function(){}, constant: function(){}, statements: function(){}, filterChain: function(){}, filter: function(){}, expression: function(){}, assignment: function(){}, ternary: function(){}, logicalOR: function(){ /* 逻辑或 */}, logicalAND: function(){ /* 逻辑与 */ }, equality: function(){ /* 等于 */ }, relational: function(){ /* 比较关系 */ }, additive: function(){ /* 加法,减法 */ }, multiplicative: function(){ /* 乘法,除法,求余 */ }, unary: function(){ /* 一元 */ }, fieldAccess: function(){}, objectIndex: function(){}, functionCall: function(){}, arrayDeclaration: function(){}, object: function(){} }Parser的入口方法是parse,内部执行了statements方法。来看下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"htmlcode">filterChain: function() { /* 针对angular语法的filter */ var left = this.expression(); var token; while ((token = this.expect('|'))) { left = this.filter(left); } return left; }其中filterChain是针对angular表达式独有的"|"filter写法设计的。我们先绕过这块,进入expression
expression: function() { return this.assignment(); }再看assignment:
assignment: function() { var left = this.ternary(); var right; var token; if ((token = this.expect('='))) { if (!left.assign) { this.throwError('implies assignment but [' + this.text.substring(0, token.index) + '] can not be assigned to', token); } right = this.ternary(); return extend(function $parseAssignment(scope, locals) { return left.assign(scope, right(scope, locals), locals); }, { inputs: [left, right] }); } return left; }我们看到了ternary方法。这是一个解析三目操作的方法。与此同时,assignment将表达式以=划分成left和right两块。并且两块都尝试执行ternary。
ternary: function() { var left = this.logicalOR(); var middle; var token; if ((token = this.expect('"htmlcode">logicalOR -> logicalAND -> equality -> relational -> additive -> multiplicative -> unary好吧,嵌套级数确实有点多。那么我们看下unary。
unary: function() { var token; if (this.expect('+')) { return this.primary(); } else if ((token = this.expect('-'))) { return this.binaryFn(Parser.ZERO, token.text, this.unary()); } else if ((token = this.expect('!'))) { return this.unaryFn(token.text, this.unary()); } else { return this.primary(); } }这边需要看两个主要的方法,一个是binaryFn和primay。如果判断是-,则必须通过binaryFn去添加函数。看下binaryFn
binaryFn: function(left, op, right, isBranching) { var fn = OPERATORS[op]; return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { constant: left.constant && right.constant, inputs: !isBranching && [left, right] }); }其中OPERATORS是之前聊Lexer也用到过,它根据操作符存储相应的操作函数。看一下fn(self, locals, left, right)。而我们随便取OPERATORS中的一个例子:
'-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); return (isDefined(a) "htmlcode">var _l = new Lexer({}); var _p = new Parser(_l); var a = _p.parse("1 + 1 + 2"); console.log(a()); //4我们看下1+1+2生成的token是什么样的:
[ {"index":0,"text":"1","constant":true,"value":1},{"index":2,"text":"+","operator":true},{"index":4,"text":"1","constant":true,"value":1},{"index":6,"text":"+","operator":true},{"index":8,"text":"2","constant":true,"value":2} ]Parser根据lexer生成的tokens尝试解析。tokens每一个成员都会生成一个函数,其先后执行逻辑按照用户输入的1+1+2的顺序执行。注意像1和2这类constants为true的token,parser会通过constant生成需要的函数$parseConstant,也就是说1+1+2中的两个1和一个2都是返回$parseConstant函数,通过$parseBinaryFn管理加法逻辑。
constant: function() { var value = this.consume().value; return extend(function $parseConstant() { return value; //这个函数执行之后,就是将value值返回。 }, { constant: true, literal: true }); }, binaryFn: function(left, op, right, isBranching) { var fn = OPERATORS[op];//加法逻辑 return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right);//left和right分别表示生成的对应函数 }, { constant: left.constant && right.constant, inputs: !isBranching && [left, right] }); }那我们demo中的a应该返回什么函数呢?当然是$parseBinaryFn。其中的left和right分别是1+1的$parseBinaryFn,right就是2的$parseConstant。
再来一个例子:
var _l = new Lexer({}); var _p = new Parser(_l); var a = _p.parse('{"name": "hello"}'); console.log(a);这边我们传入一个json,理论上我们执行完a函数,应该返回一个{name: "hello"}的对象。它调用了Parser中的object
object: function() { var keys = [], valueFns = []; if (this.peekToken().text !== '}') { do { if (this.peek('}')) { // Support trailing commas per ES5.1. break; } var token = this.consume(); if (token.constant) { //把key取出来 keys.push(token.value); } else if (token.identifier) { keys.push(token.text); } else { this.throwError("invalid key", token); } this.consume(':'); //冒号之后,则是值,将值存在valueFns中 valueFns.push(this.expression()); //根据逗号去迭代下一个 } while (this.expect(',')); } this.consume('}'); return extend(function $parseObjectLiteral(self, locals) { var object = {}; for (var i = 0, ii = valueFns.length; i < ii; i++) { object[keys[i]] = valueFns[i](self, locals); } return object; }, { literal: true, constant: valueFns.every(isConstant), inputs: valueFns }); }比方我们的例子{"name": "hello"},object会将name存在keys中,hello则会生成$parseConstant函数存在valueFns中,最终返回$parseObjectLiternal函数。
下一个例子:
var a = _p.parse('{"name": "hello"}["name"]');这个跟上一个例子的差别在于后面尝试去读取name的值,这边则调用parser中的objectIndex方法。
objectIndex: function(obj) { var expression = this.text; var indexFn = this.expression(); this.consume(']'); return extend(function $parseObjectIndex(self, locals) { var o = obj(self, locals), //parseObjectLiteral,实际就是obj i = indexFn(self, locals), //$parseConstant,这里就是name v; ensureSafeMemberName(i, expression); if (!o) return undefined; v = ensureSafeObject(o[i], expression); return v; }, { assign: function(self, value, locals) { var key = ensureSafeMemberName(indexFn(self, locals), expression); // prevent overwriting of Function.constructor which would break ensureSafeObject check var o = ensureSafeObject(obj(self, locals), expression); if (!o) obj.assign(self, o = {}, locals); return o[key] = value; } }); }很简单吧,obj[xx]和obj.x类似。大家自行阅读,我们再看一个函数调用的demo
var _l = new Lexer({}); var _p = new Parser(_l, '', {}); var demo = { "test": function(){ alert("welcome"); } }; var a = _p.parse('test()'); console.log(a(demo));我们传入一个test的调用。这边调用了parser中的functionCall方法和identifier方法
identifier: function() { var id = this.consume().text; //Continue reading each `.identifier` unless it is a method invocation while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { id += this.consume().text + this.consume().text; } return getterFn(id, this.options, this.text); }看一下getterFn方法
... forEach(pathKeys, function(key, index) { ensureSafeMemberName(key, fullExp); var lookupJs = (index // we simply dereference 's' on any .dot notation "' + key + '"))"htmlcode">function('s', 'l', 'eso', 'fe'){ if(s == null) return undefined; s=((l&&l.hasOwnProperty("test"))"htmlcode">functionCall: function(fnGetter, contextGetter) { var argsFn = []; if (this.peekToken().text !== ')') { /* 确认调用时有入参 */ do { //形参存入argsFn argsFn.push(this.expression()); } while (this.expect(',')); } this.consume(')'); var expressionText = this.text; // we can safely reuse the array across invocations var args = argsFn.length "htmlcode">... return function $parse(exp, interceptorFn, expensiveChecks) { var parsedExpression, oneTime, cacheKey; switch (typeof exp) { case 'string': cacheKey = exp = exp.trim(); var cache = (expensiveChecks ? cacheExpensive : cacheDefault); parsedExpression = cache[cacheKey]; if (!parsedExpression) { if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { oneTime = true; exp = exp.substring(2); } var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; //调用lexer和parser var lexer = new Lexer(parseOptions); var parser = new Parser(lexer, $filter, parseOptions); parsedExpression = parser.parse(exp); //添加$$watchDelegate,为scope部分提供支持 if (parsedExpression.constant) { parsedExpression.$$watchDelegate = constantWatchDelegate; } else if (oneTime) { //oneTime is not part of the exp passed to the Parser so we may have to //wrap the parsedExpression before adding a $$watchDelegate parsedExpression = wrapSharedExpression(parsedExpression); parsedExpression.$$watchDelegate = parsedExpression.literal ? oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; } else if (parsedExpression.inputs) { parsedExpression.$$watchDelegate = inputsWatchDelegate; } //做相关缓存 cache[cacheKey] = parsedExpression; } return addInterceptor(parsedExpression, interceptorFn); case 'function': return addInterceptor(exp, interceptorFn); default: return addInterceptor(noop, interceptorFn); } };总结:Lexer和Parser的实现确实让我大开眼界。通过这两个函数,实现了angular自己的语法解析器。逻辑部分还是相对复杂
以上所述是小编给大家介绍的Angularjs 1.3 中的$parse实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!