chaojidan

导航

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表达式的。

 

 

 

加油!

posted on 2015-02-13 09:25  chaojidan  阅读(1942)  评论(0编辑  收藏  举报