搜索
您的当前位置:首页正文

Angularjs 1.3 中的$parse实例代码

2023-12-08 来源:好心意情感网

这次我们来看一下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, '(){}[].,;:?')) { /* 判断是否是(){}[].,;:? */ this.tokens.push({index: this.index, text: ch}); this.index++; } else if (this.isWhitespace(ch)) { /* 判断是否是空白符 */ this.index++; } else { /* 尝试匹配操作运算 */ var ch2 = ch + this.peek(); var ch3 = ch2 + this.peek(2); var op1 = OPERATORS[ch]; var op2 = OPERATORS[ch2]; var op3 = OPERATORS[ch3]; if (op1 || op2 || op3) { var token = op3 ? ch3 : (op2 ? ch2 : ch); this.tokens.push({index: this.index, text: token, operator: true}); this.index += token.length; } else { this.throwError('Unexpected next character ', this.index, this.index + 1); } } } return this.tokens; }

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

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) ? b : undefined;}, '-':function(self, locals, a, b) { a=a(self, locals); b=b(self, locals); return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); }, '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, '!':function(self, locals, a) {return !a(self, locals);}, //Tokenized as operators but parsed as assignment/filters '=':true, '|':true});

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

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: ch},"操作符": { 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? return (statements.length === 1) ? statements[0] : function $parseStatements(self, locals) { var value; for (var i = 0, ii = statements.length; i < ii; i++) { value = statements[i](self, locals); } return value; }; } } }

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

看一下filterChain:

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('?'))) { middle = this.assignment(); if (this.consume(':')) { var right = this.assignment(); return extend(function $parseTernary(self, locals) { return left(self, locals) ? middle(self, locals) : right(self, locals); }, { constant: left.constant && middle.constant && right.constant }); } } return left; }

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

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) ? a : 0) - (isDefined(b) ? b : 0); }

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

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

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 ? 's' // but if we are first then we check locals first, and if so read it first : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; if (expensiveChecks || isPossiblyDangerousMemberName(key)) { lookupJs = 'eso(' + lookupJs + ', fe)'; needsEnsureSafeObject = true; } code += 'if(s == null) return undefined;' + 's=' + lookupJs + ';'; }); code += 'return s;'; /* jshint -W054 */ var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject /* jshint +W054 */ evaledFnGetter.toString = valueFn(code);...

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

function('s', 'l', 'eso', 'fe'){if(s == null) return undefined;s=((l&&l.hasOwnProperty("test"))?l:s).test;return s;}

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

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 ? [] : null; return function $parseFunctionCall(scope, locals) { var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; //或者之前创建生成的匿名函数 var fn = fnGetter(scope, locals, context) || noop; if (args) { var i = argsFn.length; while (i--) { args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText); } } ensureSafeObject(context, expressionText); ensureSafeFunction(fn, expressionText); // IE doesn't have apply for some native functions //执行匿名函数的时候需要传入上下文 var v = fn.apply ? fn.apply(context, args) : fn(args[0], args[1], args[2], args[3], args[4]); if (args) { // Free-up the memory (arguments of the last function call). args.length = 0; } return ensureSafeObject(v, expressionText); }; }

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

...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实例代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

小编还为您整理了以下内容,可能对您也有帮助:

angularjs中undefined怎么判断

AngularJS表达式类似Javascript的代码片段,通常在数据绑定中用到,写在双大括号中,如:{{表达式}}。表达式是用$parse方法来处理的。

下面是一些合法的AngularJS表达式

1+2
3*10 | currency
user.name
你可能会认为AngularJS视图中的表达式就是Javascript表达式,这种认识不完全对,因为AngularJS不会用Javascript的eval()函数去执行表达式。 不过除了以下几个需要区别的地方以外,你可以把AngularJS表达式看成是Javascript表达式:

属性表达式:属性表达式对应于当前的作用域,不像Javascript对应的是全局的window对象。
允许未定义值:执行表达式时,AngularJS能够允许undefined或者null,不像Javascript会抛出一个异常。
没有控制结构: 你不能在AngularJS表达式中使用“条件判断”、“循环”、“抛出异常”等控制结构。
过滤器(类似unix中的管道操作符): 你可以通过过滤器链来传递表达式的结果。例如将日期对象转变成指定的阅读友好的格式。
如果你想要在表达式中使用标准的Javascript,那么你应该把它写成一个控制器的方法,然后在表达式中调用这个方法。如果你想在Javascript中执行AngularJS表达式,你可以使用$eval()方法。

举个例子:

<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
<script>
  function Cntl2($scope) {
      var exprs = $scope.exprs = [];
      $scope.expr= '3*10|currency';
      $scope.addExp= function(expr) { //在控制器里面定义了一个addExp方法,在这个方法中,使用的是标准的js代码
        exprs.push(expr);
      };
      $scope.removeExp= function(index) {
        exprs.splice(index,1);
      };
    }
</script>
</head>
<body>
<div ng-controller="Cntl2" class="expressions">
Expression:
<input type='text' ng-model="expr" size="80"/>
<button ng-click="addExp(expr)">Evaluate</button>
<ul>
<li ng-repeat="expr in exprs">
[<a href="" ng-click="removeExp($index)">X</a> ]
<tt>{{expr}}</tt> => <span ng-bind="$parent.$eval(expr)"></span> //这里的expr是在js中,如果想运行此angular表达式,就调用$eval。
</li>
</ul>
</div>
</body>
</html>

属性表达式

属性表达式计算是发生在作用域中的。Javascript默认是以window为作用域的。AngularJS要使用window作用域的话得用$window来指向全局window对象。 比如说,你使用window中定义的alert()方法,在AngularJS表达式中必须写成$window.alert()才行。

举个例子:

<!doctype html>
<html ng-app>
<head>
<script src="www.hbbz08.com //code.angularjs.org/angular-1.0.2.min.js"></script>
<script>
function Cntl1($window, $scope){
      $scope.name = 'World';
      $scope.greet = function() {
        ($window.mockWindow || $window).alert('Hello ' + $scope.name);
      }
    }
</script>
</head>
<body>
<div class="example2" ng-controller="Cntl1">
Name: <input ng-model="name" type="text"/>
<button ng-click="greet()">Greet</button>
</div>
</body>
</html>
允许未定义值
表达式在执行时是可以允许undifined和null的。 在Javascript中,计算a.b.c会抛出一个异常,如果这不是一个对象的话。但是如果大多数时候表达式是用来作数据绑定的,像下面这种形式:

{{a.b.c}}
那么表达式返回一个空值会比触发异常更有意义。因为通常我们是在等待服务器的响应,并且变量马上就会被定义和赋值。如果表达式不能容忍未定义的值,那么我们绑定的代码就不得不写成形如:

{{((a||{}).b||{}).c}}
angular在执行未定义的函数a.b.c()是,也会返回undefined,不会触发异常。

没有流程控制结构

你不能在表达式中使用控制结构。这样设计的原因在于AngularJS的设计理念之一就是逻辑代码都应该在控制器里。如果你需要使用条件、循环、或者处理异常,你就应该写在控制器的方法里。

过滤器

当你将数据呈献给用户时,你很可能需要将数据转换为阅读友好的格式。比方说,你可能需要在显示之前将一个日期对象转换为用户本地的时间格式。你可以用链式的过滤器来传递表达式,像下面这样:

name | uppercase
这个表达式会将name的值传递给uppercase这个过滤器。

链式过滤器使用的是下面这样的语法:

value | filter1 | filter2
你也可以通过冒号来给过滤器传递参数,比如,将123显示成带有两位小数的形式:123 | number:2

$符号

你可能会好奇,这个$的前缀有什么用?其实这只是一个标记AngularJS专有属性的符号,用来表示区别于开发者自定义属性的符号。

AngularJS的设计是在已有的对象上添加行为。使用$做前缀的话,就能使得开发者的代码和AngularJS的代码和谐共处了。

angularjs中undefined怎么判断

AngularJS表达式类似Javascript的代码片段,通常在数据绑定中用到,写在双大括号中,如:{{表达式}}。表达式是用$parse方法来处理的。

下面是一些合法的AngularJS表达式

1+2
3*10 | currency
user.name
你可能会认为AngularJS视图中的表达式就是Javascript表达式,这种认识不完全对,因为AngularJS不会用Javascript的eval()函数去执行表达式。 不过除了以下几个需要区别的地方以外,你可以把AngularJS表达式看成是Javascript表达式:

属性表达式:属性表达式对应于当前的作用域,不像Javascript对应的是全局的window对象。
允许未定义值:执行表达式时,AngularJS能够允许undefined或者null,不像Javascript会抛出一个异常。
没有控制结构: 你不能在AngularJS表达式中使用“条件判断”、“循环”、“抛出异常”等控制结构。
过滤器(类似unix中的管道操作符): 你可以通过过滤器链来传递表达式的结果。例如将日期对象转变成指定的阅读友好的格式。
如果你想要在表达式中使用标准的Javascript,那么你应该把它写成一个控制器的方法,然后在表达式中调用这个方法。如果你想在Javascript中执行AngularJS表达式,你可以使用$eval()方法。

举个例子:

<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.2.min.js"></script>
<script>
  function Cntl2($scope) {
      var exprs = $scope.exprs = [];
      $scope.expr= '3*10|currency';
      $scope.addExp= function(expr) { //在控制器里面定义了一个addExp方法,在这个方法中,使用的是标准的js代码
        exprs.push(expr);
      };
      $scope.removeExp= function(index) {
        exprs.splice(index,1);
      };
    }
</script>
</head>
<body>
<div ng-controller="Cntl2" class="expressions">
Expression:
<input type='text' ng-model="expr" size="80"/>
<button ng-click="addExp(expr)">Evaluate</button>
<ul>
<li ng-repeat="expr in exprs">
[<a href="" ng-click="removeExp($index)">X</a> ]
<tt>{{expr}}</tt> => <span ng-bind="$parent.$eval(expr)"></span> //这里的expr是在js中,如果想运行此angular表达式,就调用$eval。
</li>
</ul>
</div>
</body>
</html>

属性表达式

属性表达式计算是发生在作用域中的。Javascript默认是以window为作用域的。AngularJS要使用window作用域的话得用$window来指向全局window对象。 比如说,你使用window中定义的alert()方法,在AngularJS表达式中必须写成$window.alert()才行。

举个例子:

<!doctype html>
<html ng-app>
<head>
<script src="www.hbbz08.com //code.angularjs.org/angular-1.0.2.min.js"></script>
<script>
function Cntl1($window, $scope){
      $scope.name = 'World';
      $scope.greet = function() {
        ($window.mockWindow || $window).alert('Hello ' + $scope.name);
      }
    }
</script>
</head>
<body>
<div class="example2" ng-controller="Cntl1">
Name: <input ng-model="name" type="text"/>
<button ng-click="greet()">Greet</button>
</div>
</body>
</html>
允许未定义值
表达式在执行时是可以允许undifined和null的。 在Javascript中,计算a.b.c会抛出一个异常,如果这不是一个对象的话。但是如果大多数时候表达式是用来作数据绑定的,像下面这种形式:

{{a.b.c}}
那么表达式返回一个空值会比触发异常更有意义。因为通常我们是在等待服务器的响应,并且变量马上就会被定义和赋值。如果表达式不能容忍未定义的值,那么我们绑定的代码就不得不写成形如:

{{((a||{}).b||{}).c}}
angular在执行未定义的函数a.b.c()是,也会返回undefined,不会触发异常。

没有流程控制结构

你不能在表达式中使用控制结构。这样设计的原因在于AngularJS的设计理念之一就是逻辑代码都应该在控制器里。如果你需要使用条件、循环、或者处理异常,你就应该写在控制器的方法里。

过滤器

当你将数据呈献给用户时,你很可能需要将数据转换为阅读友好的格式。比方说,你可能需要在显示之前将一个日期对象转换为用户本地的时间格式。你可以用链式的过滤器来传递表达式,像下面这样:

name | uppercase
这个表达式会将name的值传递给uppercase这个过滤器。

链式过滤器使用的是下面这样的语法:

value | filter1 | filter2
你也可以通过冒号来给过滤器传递参数,比如,将123显示成带有两位小数的形式:123 | number:2

$符号

你可能会好奇,这个$的前缀有什么用?其实这只是一个标记AngularJS专有属性的符号,用来表示区别于开发者自定义属性的符号。

AngularJS的设计是在已有的对象上添加行为。使用$做前缀的话,就能使得开发者的代码和AngularJS的代码和谐共处了。

AngularJS 在实际应用中有哪些优缺点

AngularJS 在实际应用中优缺点很多,具体如下:

优点:

1. 模板功能强大丰富,并且是声明式的,自带了丰富的Angular指令;

2. 是一个比较完善的前端MV*框架,包含模板,数据双向绑定,路由,模块化,服务,过滤器,依赖注入等所有功能;

3. 自定义Directive,比jQuery插件还灵活,但是需要深入了解Directive的一些特性,简单的封装容易,复杂一点官方没有提供详细的介绍文档,我们可以通过阅读源代码来找到某些我们需要的东西,如:在directive使用 $parse;

4. ng模块化比较大胆的引入了Java的一些东西(依赖注入),能够很容易的写出可复用的代码,对于敏捷开发的团队来说非常有帮助,我们的项目从上线到目前,UI变化很大,在摸索中迭代产品,但是js的代码基本上很少改动。

5. 补充:Angular支持单元测试和e2e-testing。

缺点:

1. 验证功能错误信息显示比较薄弱,需要写很多模板标签,没有jQuery Validate方便,所以我们自己封装了验证的错误信息提示;

2. ngView只能有一个,不能嵌套多个视图,虽然有解决,但是貌似ui-router 对于URL的控制不是很灵活,必须是嵌套式的(也许我没有深入了解或者新版本有改进);

3. 对于特别复杂的应用场景,貌似性能有点问题,特别是在Windows下使用chrome浏览器,不知道是内存泄漏了还是什么其他问题,没有找到好的解决方案,奇怪的是在IE10下反而很快,对此还在观察中;

4. 这次从1.0.X升级到1.2.X,貌似有比较大的调整,没有完美兼容低版本,升级之后可能会导致一个兼容性的BUG,具体详细信息参考官方文档 AngularJS ,对应的中文版本:Angular 1.0到1.2 迁移指南

5. ng提倡在控制器里面不要有操作DOM的代码,对于一些jQuery 插件的使用,如果想不破坏代码的整洁性,需要写一些directive去封装插件,但是现在有很多插件的版本已经支持Angular了,如:jQuery File Upload Demo

6. Angular 太笨重了,没有让用户选择一个轻量级的版本,当然1.2.X后,Angular也在做一些更改,比如把route,animate等模块出去,让用户自己去选择。

如何使用angularjs处理动态菜单

1. 既然你使用了 ui-router,说明你的应用是 单页程序,既然是单页程序,首先要考虑动态路由是否有必要?只要根据用户角色 显示该角色可以访问的菜单即可,在每次路由切换的时候判断下是否有访问此路由的权限,没有就跳转到指定页面即可;

2. 至于你说的动态加载 是想根据用户角色动态返回指定角色的路由、模板、controller js吗?一般项目把所有的js和模板都打包压缩成一个js,反而效果更好;

3. 如果你非要想根据角色动态生成路由,可以在angular还没有启动的时候获取该用户角色的所有路由(也可以说菜单),然后循环菜单 通过 ui-router 动态加入即可;

4. 这样只有路由是动态载入了,关于每个路由对应的js,如果你也想实现 动态载入的话,估计就需要使用类似 requirejs 的东西,比如 marcoslin/angularAMD · GitHub 或者 atian25/angular-lazyload · GitHub 或者自己写一个也可以,原理就是在 路由的 resolve 中加载对应的js

5. 模板感觉就不需要动态加载了吧,因为使用ng,模板都是前端模板,如果要根据角色动态生成模板岂不是变成服务端渲染了。

本文如未解决您的问题请添加抖音号:51dongshi(抖音搜索懂视),直接咨询即可。

热门图文

  • 戏赠张叔甫黄庭坚的其他诗词有哪些

    《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。作者:黄庭坚。年代:宋代。我们为您从以下几个方面提供戏赠张叔甫的详细介绍。一、《戏赠张叔甫》的全文 点此查看《戏赠张叔甫》的详细内容。团扇复团扇,因风托方便。衔泥巢君屋,双燕令人羡。张公子,时相见。张公一生江海客,文章献纳麒麟殿。文采风流今尚存,看君不合长贫贱。醉中往往爱逃禅,解道澄江静如练。淮南百宗经行处,携手落日回高宴。城上乌,尾毕逋。尘沙立暝途,惟有摩尼珠。云梦泽南州,更有赤须胡。与君歌一曲,长铗归来乎。出无车,食无鱼。不须闻此意惨怆,幸是元无免破除。脱吾帽,向君笑。

  • 戏赠张叔甫黄庭坚的其他诗词

    《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。作者:黄庭坚。年代:宋代。我们为您从以下几个方面提供戏赠张叔甫的详细介绍。一、《戏赠张叔甫》的全文 点此查看《戏赠张叔甫》的详细内容。团扇复团扇,因风托方便。衔泥巢君屋,双燕令人羡。张公子,时相见。张公一生江海客,文章献纳麒麟殿。文采风流今尚存,看君不合长贫贱。醉中往往爱逃禅,解道澄江静如练。淮南百宗经行处,携手落日回高宴。城上乌,尾毕逋。尘沙立暝途,惟有摩尼珠。云梦泽南州,更有赤须胡。与君歌一曲,长铗归来乎。出无车,食无鱼。不须闻此意惨怆,幸是元无免破除。脱吾帽,向君笑。

  • 戏赠张叔甫黄庭坚其他诗词

    《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。《戏赠张叔甫》黄庭坚其他诗词:《秋怀二首》、《登快阁》、《诉衷情》、《虞美人宜州见梅作》、《寄黄几复》。作者:黄庭坚。年代:宋代。我们为您从以下几个方面提供戏赠张叔甫的详细介绍。一、《戏赠张叔甫》的全文 点此查看《戏赠张叔甫》的详细内容。团扇复团扇,因风托方便。衔泥巢君屋,双燕令人羡。张公子,时相见。张公一生江海客,文章献纳麒麟殿。文采风流今尚存,看君不合长贫贱。醉中往往爱逃禅,解道澄江静如练。淮南百宗经行处,携手落日回高宴。城上乌,尾毕逋。尘沙立暝途,惟有摩尼珠。云梦泽南州,更有赤须胡。与君歌一曲,长铗归来乎。出无车,食无鱼。不须闻此意惨怆,幸是元无免破除。脱吾帽,向君笑。

  • 紴字的组词 紴字的组词有哪些

    紴字的组词 紴字的组词有哪些。麻紴、;紴的笔顺是撇折、撇折、点、点、点、点、横撇。紴字的笔顺图解;共十一画;紴;1;撇折;2;撇折;3;点;4;点;5;点;6;点;7;横撇;8;撇;9;竖;10;横撇;11;捺;紴的拼音为bō,部首为糹,结构为左右,注音为ㄅㄛ,ㄅㄧˋ。紴字的具体字的具体解释是什么呢,我们通过以下几个方面为您介绍。一、基本解释 点此查看紴的详细内容。[ bō ]1.绦属。2.锦类。3.水波锦文。[ bì ]1.装束貌。二、康熙字典;紴【未集中】【糸部】 康熙筆画:11画,部外筆画:5画《廣韻》博禾切《集韻》逋禾切。音波。《說文》絛屬。又《集韻》匹靡切,音帔。《玉篇》水紴錦文也。又《集韻》平義切,音被。《類篇》裝束貌。三、说文解字;

  • 戏赠张叔甫相同朝代诗歌 戏赠张叔甫相同朝代诗歌有哪些

    《戏赠张叔甫》黄庭坚相同朝代诗词。《曹将军》、《下瞿塘》、《滕王阁》、《儿馁嗔郎罢妻寒怨藁砧唐眉山诗也戏为笺之》、《题艾溪》、《废贡院为米廪过之值盘厫》、《送客至灵谷》、《叹屩词》、《题陈朝玉爱竹轩》、《酬春湖史履庸惠四皓图》。《戏赠张叔甫》相同朝代的诗词:《曹将军》、《下瞿塘》、《滕王阁》、《儿馁嗔郎罢妻寒怨藁砧唐眉山诗也戏为笺之》、《题艾溪》、《废贡院为米廪过之值盘厫》、《送客至灵谷》、《叹屩词》、《题陈朝玉爱竹轩》、《酬春湖史履庸惠四皓图》作者:黄庭坚。年代:宋代。我们为您从以下几个方面提供戏赠张叔甫的详细介绍。一、《戏赠张叔甫》的全文 点此查看《戏赠张叔甫》的详细内容。团扇复团扇,因风托方便。衔泥巢君屋,双燕令人羡。张公子,时相见。张公一生江海客,文章献纳麒麟殿。

Top