这次我们来看一下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实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件!
如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
白云城资源网 Copyright www.dyhadc.com
暂无“Angularjs 1.3 中的$parse实例代码”评论...
RTX 5090要首发 性能要翻倍!三星展示GDDR7显存
三星在GTC上展示了专为下一代游戏GPU设计的GDDR7内存。
首次推出的GDDR7内存模块密度为16GB,每个模块容量为2GB。其速度预设为32 Gbps(PAM3),但也可以降至28 Gbps,以提高产量和初始阶段的整体性能和成本效益。
据三星表示,GDDR7内存的能效将提高20%,同时工作电压仅为1.1V,低于标准的1.2V。通过采用更新的封装材料和优化的电路设计,使得在高速运行时的发热量降低,GDDR7的热阻比GDDR6降低了70%。
更新日志
2025年10月25日
2025年10月25日
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]