angularjs-1.3代码学习-$parse

前端之家收集整理的这篇文章主要介绍了angularjs-1.3代码学习-$parse前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

这次我们来看一下angular的SandBoxing Angular Expressions。关于内置方法的,核心有两块:Lexer和Parser。其中大家对$parse可能更了解一点。好了不多废话,先看Lexer的内部结构:

1.Lexer

  1. //构造函数
  2. var Lexer = function(options) {
  3. this.options = options;
  4. };
  5. //原型
  6. Lexer.prototype = {
  7. 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(){ /*读取携带''或""的字符串*/ }
  8. };

这里指出一点,因为是表达式。所以类似"123"这类的东西,在Lexer看来应该算是数字而非字符串。表达式中的字符串必须使用单引号或者双引号来标识。Lexer的核心逻辑在lex方法中:

  1. lex: function(text) {
  2. this.text = text;
  3. this.index = 0;
  4. this.tokens = [];
  5. while (this.index < this.text.length) {
  6. var ch = this.text.charAt(this.index);
  7. if (ch === '"' || ch === "'") {
  8. /* 尝试判断是否是字符串 */
  9. this.readString(ch);
  10. } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) {
  11. /* 尝试判断是否是数字 */
  12. this.readNumber();
  13. } else if (this.isIdent(ch)) {
  14. /* 尝试判断是否是字母 */
  15. this.readIdent();
  16. } else if (this.is(ch,'(){}[].,;:?')) {
  17. /* 判断是否是(){}[].,;:? */
  18. this.tokens.push({index: this.index,text: ch});
  19. this.index++;
  20. } else if (this.isWhitespace(ch)) {
  21. /* 判断是否是空白符 */
  22. this.index++;
  23. } else {
  24. /* 尝试匹配操作运算 */
  25. var ch2 = ch + this.peek();
  26. var ch3 = ch2 + this.peek(2);
  27. var op1 = OPERATORS[ch];
  28. var op2 = OPERATORS[ch2];
  29. var op3 = OPERATORS[ch3];
  30. if (op1 || op2 || op3) {
  31. var token = op3 ? ch3 : (op2 ? ch2 : ch);
  32. this.tokens.push({index: this.index,text: token,operator: true});
  33. this.index += token.length;
  34. } else {
  35. this.throwError('Unexpected next character ',this.index,this.index + 1);
  36. }
  37. }
  38. }
  39. return this.tokens;
  40. }

主要看一下匹配操作运算。这里源码中会调用OPERATORS。看一下OPERATORS:

  1. var OPERATORS = extend(createMap(),{
  2. '+':function(self,locals,a,b) {
  3. a=a(self,locals); b=b(self,locals);
  4. if (isDefined(a)) {
  5. if (isDefined(b)) {
  6. return a + b;
  7. }
  8. return a;
  9. }
  10. return isDefined(b) ? b : undefined;},'-':function(self,b) {
  11. a=a(self,locals);
  12. return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
  13. },'*':function(self,b) {return a(self,locals) * b(self,locals);},'/':function(self,locals) / b(self,'%':function(self,locals) % b(self,'===':function(self,locals) === b(self,'!==':function(self,locals) !== b(self,'==':function(self,locals) == b(self,'!=':function(self,locals) != b(self,'<':function(self,locals) < b(self,'>':function(self,locals) > b(self,'<=':function(self,locals) <= b(self,'>=':function(self,locals) >= b(self,'&&':function(self,locals) && b(self,'||':function(self,locals) || b(self,'!':function(self,a) {return !a(self,//Tokenized as operators but parsed as assignment/filters
  14. '=':true,'|':true
  15. });

可以看到OPERATORS实际上存储的是操作符和操作符函数的键值对。根据操作符返回对应的操作符函数。我们看一下调用例子:

  1. var _l = new Lexer({});
  2. var a = _l.lex("a = a + 1");
  3. 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的键值对略有不同:

  1. number:{
  2. index: start,text: number,constant: true,value: Number(number)
  3. },string: {
  4. index: start,text: rawString,value: string
  5. },ident: {
  6. index: start,text: this.text.slice(start,this.index),identifier: true /* 字符表示 */
  7. },'(){}[].,;:?': {
  8. index: this.index,text: ch
  9. },"操作符": {
  10. index: this.index,operator: true
  11. }
    //text是表达式,而value才是实际的值

number和string其实都有相对应的真实值,意味着如果我们表达式是2e2,那number生成的token的值value就应该是200。到此我们通过lexer类获得了一个具有token值得数组。从外部看,实际上Lexer是将我们输入的表达式解析成了token json。可以理解为生成了表达式的语法树(AST)。但是目前来看,我们依旧还没有能获得我们定义表达式的结果。那就需要用到parser了。

2.Parser

先看一下Parser的内部结构:

  1. //构造函数
  2. var Parser = function(lexer,$filter,options) {
  3. this.lexer = lexer;
  4. this.$filter = $filter;
  5. this.options = options;
  6. };
  7. //原型
  8. Parser.prototype = {
  9. 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(){}
  10. }

Parser的入口方法是parse,内部执行了statements方法。来看下statements:

  1. statements: function() {
  2. var statements = [];
  3. while (true) {
  4. if (this.tokens.length > 0 && !this.peek('}',')',';',']'))
  5. statements.push(this.filterChain());
  6. if (!this.expect(';')) {
  7. // optimize for the common case where there is only one statement.
  8. // TODO(size): maybe we should not support multiple statements?
  9. return (statements.length === 1)
  10. ? statements[0]
  11. : function $parseStatements(self,locals) {
  12. var value;
  13. for (var i = 0,ii = statements.length; i < ii; i++) {
  14. value = statements[i](self,locals);
  15. }
  16. return value;
  17. };
  18. }
  19. }
  20. }

这里我们将tokens理解为表达式,实际上它就是经过表达式通过lexer转换过来的。statements中。如果表达式不以},),;,]开头,将会执行filterChain方法。当tokens检索完成之后,最后返回了一个$parseStatements方法。其实Parser中很多方法都返回了类似的对象,意味着返回的内容将需要执行后才能得到结果。

看一下filterChain:

  1. filterChain: function() {
  2. /* 针对angular语法的filter */
  3. var left = this.expression();
  4. var token;
  5. while ((token = this.expect('|'))) {
  6. left = this.filter(left);
  7. }
  8. return left;
  9. }

其中filterChain是针对angular表达式独有的"|"filter写法设计的。我们先绕过这块,进入expression

  1. expression: function() {
  2. return this.assignment();
  3. }

再看assignment:

  1. assignment: function() {
  2. var left = this.ternary();
  3. var right;
  4. var token;
  5. if ((token = this.expect('='))) {
  6. if (!left.assign) {
  7. this.throwError('implies assignment but [' +
  8. this.text.substring(0,token.index) + '] can not be assigned to',token);
  9. }
  10. right = this.ternary();
  11. return extend(function $parseAssignment(scope,locals) {
  12. return left.assign(scope,right(scope,locals),locals);
  13. },{
  14. inputs: [left,right]
  15. });
  16. }
  17. return left;
  18. }

我们看到了ternary方法。这是一个解析三目操作的方法。与此同时,assignment将表达式以=划分成left和right两块。并且两块都尝试执行ternary。

  1. ternary: function() {
  2. var left = this.logicalOR();
  3. var middle;
  4. var token;
  5. if ((token = this.expect('?'))) {
  6. middle = this.assignment();
  7. if (this.consume(':')) {
  8. var right = this.assignment();
  9. return extend(function $parseTernary(self,locals) {
  10. return left(self,locals) ? middle(self,locals) : right(self,locals);
  11. },{
  12. constant: left.constant && middle.constant && right.constant
  13. });
  14. }
  15. }
  16. return left;
  17. }

在解析三目运算之前,又根据?将表达式划分成left和right两块。左侧再去尝试执行logicalOR,实际上这是一个逻辑与的解析,按照这个执行流程,我们一下有了思路。这有点类似我们一般写三目时。代码的执行情况,比如: 2 > 2 ? 1 : 0。如果把这个当成表达式,那根据?划分left和right,left就应该是2 > 2,right应该就是 1: 0。然后尝试在left看是否有逻辑或的操作。也就是,Parser里面的方法调用的嵌套级数越深,其方法的优先级则越高。好,那我们一口气看看这个最高的优先级在哪?

  1. logicalOR -> logicalAND -> equality -> relational -> additive -> multiplicative -> unary

好吧,嵌套级数确实有点多。那么我们看下unary。

  1. unary: function() {
  2. var token;
  3. if (this.expect('+')) {
  4. return this.primary();
  5. } else if ((token = this.expect('-'))) {
  6. return this.binaryFn(Parser.ZERO,token.text,this.unary());
  7. } else if ((token = this.expect('!'))) {
  8. return this.unaryFn(token.text,this.unary());
  9. } else {
  10. return this.primary();
  11. }
  12. }

这边需要看两个主要的方法,一个是binaryFn和primay。如果判断是-,则必须通过binaryFn去添加函数。看下binaryFn

  1. binaryFn: function(left,op,right,isBranching) {
  2. var fn = OPERATORS[op];
  3. return extend(function $parseBinaryFn(self,locals) {
  4. return fn(self,left,right);
  5. },{
  6. constant: left.constant && right.constant,inputs: !isBranching && [left,right]
  7. });
  8. }

其中OPERATORS是之前聊Lexer也用到过,它根据操作符存储相应的操作函数。看一下fn(self,right)。而我们随便取OPERATORS中的一个例子:

  1. '-':function(self,locals);
  2. return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0);
  3. }

其中a和b就是left和right,他们其实都是返回的跟之前类似的$parseStatements方法。默认存储着token中的value。经过事先解析好的四则运算来生成最终答案。其实这就是Parser的基本功能。至于嵌套,我们可以把它理解为js的操作符的优先级。这样就一目了然了。至于primay方法。塔刷选{ ( 对象做进一步的解析过程。

Parser的代码并不复杂,只是函数方法调用密切,让我们再看一个例子:

  1. var _l = new Lexer({});
  2. var _p = new Parser(_l);
  3. var a = _p.parse("1 + 1 + 2");
  4. console.log(a()); //4

我们看下1+1+2生成的token是什么样的:

  1. [
  2. {"index":0,"text":"1","constant":true,"value":1},{"index":2,"text":"+","operator":true},{"index":4,{"index":6,{"index":8,"text":"2","value":2}
  3. ]

Parser根据lexer生成的tokens尝试解析。tokens每一个成员都会生成一个函数,其先后执行逻辑按照用户输入的1+1+2的顺序执行。注意像1和2这类constants为true的token,parser会通过constant生成需要的函数$parseConstant,也就是说1+1+2中的两个1和一个2都是返回$parseConstant函数,通过$parseBinaryFn管理加法逻辑。

  1. constant: function() {
  2. var value = this.consume().value;
  3. return extend(function $parseConstant() {
  4. return value; //这个函数执行之后,就是将value值返回。
  5. },{
  6. constant: true,literal: true
  7. });
  8. },
  1. binaryFn: function(left,isBranching) {
  2. var fn = OPERATORS[op];//加法逻辑
  3. return extend(function $parseBinaryFn(self,locals) {
  4. return fn(self,right);//left和right分别表示生成的对应函数
  5. },right]
  6. });
  7. }

那我们demo中的a应该返回什么函数呢?当然是$parseBinaryFn。其中的left和right分别是1+1的$parseBinaryFn,right就是2的$parseConstant。

再来一个例子:

  1. var _l = new Lexer({});
  2. var _p = new Parser(_l);
  3. var a = _p.parse('{"name": "hello"}');
  4. console.log(a);

这边我们传入一个json,理论上我们执行完a函数,应该返回一个{name: "hello"}的对象。它调用了Parser中的object

  1. object: function() {
  2. var keys = [],valueFns = [];
  3. if (this.peekToken().text !== '}') {
  4. do {
  5. if (this.peek('}')) {
  6. // Support trailing commas per ES5.1.
  7. break;
  8. }
  9. var token = this.consume();
  10. if (token.constant) {
  11. //把key取出来
  12. keys.push(token.value);
  13. } else if (token.identifier) {
  14. keys.push(token.text);
  15. } else {
  16. this.throwError("invalid key",token);
  17. }
  18. this.consume(':');
  19. //冒号之后,则是值,将值存在valueFns中
  20. valueFns.push(this.expression());
  21. //根据逗号去迭代下一个
  22. } while (this.expect(','));
  23. }
  24. this.consume('}');
  25. return extend(function $parSEObjectLiteral(self,locals) {
  26. var object = {};
  27. for (var i = 0,ii = valueFns.length; i < ii; i++) {
  28. object[keys[i]] = valueFns[i](self,locals);
  29. }
  30. return object;
  31. },{
  32. literal: true,constant: valueFns.every(isConstant),inputs: valueFns
  33. });
  34. }

比方我们的例子{"name": "hello"},object会将name存在keys中,hello则会生成$parseConstant函数存在valueFns中,最终返回$parSEObjectLiternal函数

下一个例子:

  1. var a = _p.parse('{"name": "hello"}["name"]');

这个跟上一个例子的差别在于后面尝试去读取name的值,这边则调用parser中的objectIndex方法

  1. objectIndex: function(obj) {
  2. var expression = this.text;
  3. var indexFn = this.expression();
  4. this.consume(']');
  5. return extend(function $parSEObjectIndex(self,locals) {
  6. var o = obj(self,//parSEObjectLiteral,实际就是obj
  7. i = indexFn(self,//$parseConstant,这里就是name
  8. v;
  9. ensureSafeMemberName(i,expression);
  10. if (!o) return undefined;
  11. v = ensureSafeObject(o[i],expression);
  12. return v;
  13. },{
  14. assign: function(self,value,locals) {
  15. var key = ensureSafeMemberName(indexFn(self,expression);
  16. // prevent overwriting of Function.constructor which would break ensureSafeObject check
  17. var o = ensureSafeObject(obj(self,expression);
  18. if (!o) obj.assign(self,o = {},locals);
  19. return o[key] = value;
  20. }
  21. });
  22. }

很简单吧,obj[xx]和obj.x类似。大家自行阅读,我们再看一个函数调用的demo

  1. var _l = new Lexer({});
  2. var _p = new Parser(_l,'',{});
  3. var demo = {
  4. "test": function(){
  5. alert("welcome");
  6. }
  7. };
  8. var a = _p.parse('test()');
  9. console.log(a(demo));

我们传入一个test的调用。这边调用了parser中的functionCall方法和identifier方法

  1. identifier: function() {
  2. var id = this.consume().text;
  3. //Continue reading each `.identifier` unless it is a method invocation
  4. while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2,'(')) {
  5. id += this.consume().text + this.consume().text;
  6. }
  7. return getterFn(id,this.options,this.text);
  8. }

看一下getterFn方法

  1. ...
  2. forEach(pathKeys,function(key,index) {
  3. ensureSafeMemberName(key,fullExp);
  4. var lookupJs = (index
  5. // we simply dereference 's' on any .dot notation
  6. ? 's'
  7. // but if we are first then we check locals first,and if so read it first
  8. : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key;
  9. if (expensiveChecks || isPossiblyDangerousMemberName(key)) {
  10. lookupJs = 'eso(' + lookupJs + ',fe)';
  11. needsEnsureSafeObject = true;
  12. }
  13. code += 'if(s == null) return undefined;\n' +
  14. 's=' + lookupJs + ';\n';
  15. });
  16. code += 'return s;';
  17. /* jshint -W054 */
  18. var evaledFnGetter = new Function('s','l','eso','fe',code); // s=scope,l=locals,eso=ensureSafeObject
  19. /* jshint +W054 */
  20. evaledFnGetter.toString = valueFn(code);
  21. ...

这是通过字符串创建一个匿名函数方法。我们看下demo的test生成了一个什么匿名函数

  1. function('s','fe'){
  2. if(s == null) return undefined;
  3. s=((l&&l.hasOwnProperty("test"))?l:s).test;
  4. return s;
  5. }

这个匿名函数的意思,需要传入一个上下文,匿名函数通过查找上下文中是否有test属性,如果没有传上下文则直接返回未定义。这也就是为什么我们在生成好的a函数在执行它时需要传入demo对象的原因。最后补一个functionCall

  1. functionCall: function(fnGetter,contextGetter) {
  2. var argsFn = [];
  3. if (this.peekToken().text !== ')') {
  4. /* 确认调用时有入参 */
  5. do {
  6. //形参存入argsFn
  7. argsFn.push(this.expression());
  8. } while (this.expect(','));
  9. }
  10. this.consume(')');
  11. var expressionText = this.text;
  12. // we can safely reuse the array across invocations
  13. var args = argsFn.length ? [] : null;
  14. return function $parseFunctionCall(scope,locals) {
  15. var context = contextGetter ? contextGetter(scope,locals) : isDefined(contextGetter) ? undefined : scope;
  16. //或者之前创建生成的匿名函数
  17. var fn = fnGetter(scope,context) || noop;
  18. if (args) {
  19. var i = argsFn.length;
  20. while (i--) {
  21. args[i] = ensureSafeObject(argsFn[i](scope,expressionText);
  22. }
  23. }
  24. ensureSafeObject(context,expressionText);
  25. ensureSafeFunction(fn,expressionText);
  26. // IE doesn't have apply for some native functions
  27. //执行匿名函数的时候需要传入上下文
  28. var v = fn.apply
  29. ? fn.apply(context,args)
  30. : fn(args[0],args[1],args[2],args[3],args[4]);
  31. if (args) {
  32. // Free-up the memory (arguments of the last function call).
  33. args.length = 0;
  34. }
  35. return ensureSafeObject(v,expressionText);
  36. };
  37. }

下面我们看一下$ParseProvider,这是一个基于Lex和Parser函数的angular内置provider。它对scope的api提供了基础支持

  1. ...
  2. return function $parse(exp,interceptorFn,expensiveChecks) {
  3. var parsedExpression,oneTime,cacheKey;
  4. switch (typeof exp) {
  5. case 'string':
  6. cacheKey = exp = exp.trim();
  7. var cache = (expensiveChecks ? cacheExpensive : cacheDefault);
  8. parsedExpression = cache[cacheKey];
  9. if (!parsedExpression) {
  10. if (exp.charAt(0) === ':' && exp.charAt(1) === ':') {
  11. oneTime = true;
  12. exp = exp.substring(2);
  13. }
  14. var parSEOptions = expensiveChecks ? $parSEOptionsExpensive : $parSEOptions;
  15. //调用lexer和parser
  16. var lexer = new Lexer(parSEOptions);
  17. var parser = new Parser(lexer,parSEOptions);
  18. parsedExpression = parser.parse(exp);
  19. //添加$$watchDelegate,为scope部分提供支持
  20. if (parsedExpression.constant) {
  21. parsedExpression.$$watchDelegate = constantWatchDelegate;
  22. } else if (oneTime) {
  23. //oneTime is not part of the exp passed to the Parser so we may have to
  24. //wrap the parsedExpression before adding a $$watchDelegate
  25. parsedExpression = wrapSharedExpression(parsedExpression);
  26. parsedExpression.$$watchDelegate = parsedExpression.literal ?
  27. oneTimeLiteralWatchDelegate : oneTimeWatchDelegate;
  28. } else if (parsedExpression.inputs) {
  29. parsedExpression.$$watchDelegate = inputsWatchDelegate;
  30. }
  31. //做相关缓存
  32. cache[cacheKey] = parsedExpression;
  33. }
  34. return addInterceptor(parsedExpression,interceptorFn);
  35. case 'function':
  36. return addInterceptor(exp,interceptorFn);
  37. default:
  38. return addInterceptor(noop,interceptorFn);
  39. }
  40. };

总结:Lexer和Parser的实现确实让我大开眼界。通过这两个函数,实现了angular自己的语法解析器。逻辑部分还是相对复杂

时间不多,内容刚好,以上是个人阅读源码的一些理解,有不对或者偏差的地方,还希望园友们斧正。共同进步。

猜你在找的Angularjs相关文章