"foo()" ng-show="foo != bar" ----> "foo != bar" Hello, {{ name }}! ----> "name" New Angular developers will see cases like these and assume that these expressions are Javascript, but they are wrong! These may look a lot like Javascript and act a lot like Javascript, but Angular expressions are definitely not Javascript. In fact, Angular expressions are their own language, which is why our toy example above doesn't work as you might expect. In most cases, they are pretty similar though: you use '.' to access properties, '[]' to index arrays, '=' for assignment, etc. In fact, as long as you are behaving well, you'll likely never notice the distinction, but here are a few interesting differences: 1. no support for any loop constructs (while, for, etc.) 2. no support for any conditionals, except the ternary operator: ? 3. support only a subset of Javascript operators (*, /, %, etc.) 4. added support for appending a filter chain via pipe operator: | 5. added support for declaring a one-time binding via :: prefix For more details on Angular expression syntax and some of the design philosophy, you can check out the Angular Expressions page from the Developer Guide. Angular's $parse Service OK, back to our example. Since ++points isn't a valid Angular expression, what happens? The helpful error message in the console points us in the right direction: Error: [$parse:syntax] Syntax Error: Token '+' not a primary expression at column 2 of the expression [++points] starting at [+points] From this message, we see that our expression used some unsupported syntax (in this case, the pre-increment operator ++). The error message is thrown by the very magical core Angular service, $parse. Let's investigate some more. Since Angular expressions are their own language, we need a way to evaluate them using our specialized grammar. That's where $parse comes in. $parse turns an expression like this: "data" ...into an executable Javascript function like this: function (s, l) { if(s == null) return undefined; s=((l&&l.hasOwnProperty("data"))?l:s).data; return s; } How does it work? Therein lies the magic of $parse: it implements a language-within-a-language that essentially transpiles to regular Javascript. Whenever you call $parse, Angular does what you'd expect a compiler to do: it tokenizes the expression (via a Lexer that implements the Angular grammar), and then parses those tokens into executable statements (via a Parser). The statements are wrapped up into a new Function object and returned, and afterwards you can evaluate the expression by invoking the Function and passing the scope and locals as arguments. That's how it works! If you thumb through the source code a bit, you'll see the implementation of the Angular expression "language" tucked inside of parse.js: function $parse(exp, interceptorFn) { ... var lexer = new Lexer($parseOptions); var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp); ... } As you might expect, it's a bit tricky to implement a compiler in regular Javascript code: there's a lot of loops, deeply nested functions, conditionals, and string manipulation going on under the hood. You should definitely skim through the Angular source code some time to see for yourself what's going on, but be forewarned: it's as crazy as it sounds. For example, here's a snippet of how the Lexer identifies some basic tokens like strings, numbers, etc.: 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(); } else if (this.is('(){}[].,;:?')) { ... } } Yikes, that's a lot of conditionals... Somehow, I doubt that's how gcc tokenizes source files. Joking aside, I understand the motivation behind doing this: expressions are necessary to support declarative templates, and Angular's implementation is more secure than the alternative (Javascript eval()). However, that doesn't mean it's not a bit scary to know that this is happening every time you write an ng-show directive to hide some social buttons. The good news is that the performance of this code isn't really much of a concern: calls to $parse are relatively infrequent, and there are some pretty reasonable optimizations in place to make it speedy (e.g. an expression cache), so in general, I wouldn't worry about it. But still, now that you know what's in there, anytime you find yourself using $parse directly just be careful about it. For example: 1. try to avoid calling it from inside something that runs frequently (like a watcher) 2. make your expressions simple; when possible, try putting the data you need directly on the scope instead of through references (i.e. "data" instead of "someObject.getHandle(4).getChild().container.data") 3. etc. How To Use $parse Directly Most of the time, $parse is going to be called for you automatically, or it'll be part of a core directive you are using. I think that's for the best, but if you do have a scenario where you have to use it, it's easy to do. Just inject the $parse service like any other and use $parse(expr) to get the parsed function, which can be invoked to get the expression value by passing in a scope object for context: $scope.data = "foo"; var parsedExprFn = $parse("data"); console.log(parsedExprFn($scope)); // "foo" Some $parse Demos Now that you understand the basics of how Angular magically transforms string expressions into executable functions, here are a couple demos to play around with. 1. Using $parse to generate Javascript functions2. Basic $parse performance comparison This test generates 10000 string expressions and evaluates the performance of Javascript eval() against Angular's $eval (which calls $parse). Notice how much faster $eval is the second time around thanks to the expression cache which skips the lexing & parsing! Fixing Our Bug Finally, let's fix that original demo, so we can start giving ourselves points for all this Angular learning we're doing. Since the only problem was using an unsupported operator, we can fix it by changing: Add points to Add points The result: Keep Learning Now that we've gone through it, it doesn't seem as magical anymore, does it? That's why open source projects like Angular are so great: you can learn a ton by just reading through the source code, helping out in the IRC channel, and replying to Github issues and Stack Overflow questions. So get out there, learn something new, and help out while you're doing it! If you love Angular, open source, and learning, you'd probably love working with us: we're work with Angular and contribute to open source projects everyday while we're helping companies learn, and we'd love to hear from you. Check out our jobs page or e-mail us for more info!" />

Поделиться на Facebook

Поделиться в Twitter

Сообщить об опасности

Спасибо за отзыв!

Новое в блоге
Временная недоступность сайта Сегодня ранним утром у нас возникли проблемы с хостингом из-за чего сайт некоторое время был недоступен. Сейчас сервис работает в полном объеме.
Приносим свои извинения.
Команда Реформал.ру
Адаптивный дизайн виджета Мобильные устройства плотно вошли в нашу жизнь, поэтому Реформал становится удобнее и для своих мобильных пользователей.
Мы реализовали долгожданную функцию адаптивного дизайна нашего виджета обратной связи и теперь, если сайт пользователя адаптивен (оптимизируется под мобильные устройства), то и виджет Реформала на таком сайте также будет адаптивным. А именно — при переходе...
Функция настраиваемых категорий отзывов Реформал реализовал функцию настраиваемых категорий. Теперь вы самостоятельно можете создавать, редактировать и управлять категориями и подкатегориями отзывов в своем проекте. Помимо названия, каждой категории и ее производной вы также можете присвоить...
Новые возможности смены email’a и верификации проектов Одной хорошей новости, как нам показалось, мало, поэтому сегодня мы решили порадовать вас сразу двумя.
По многочисленным просьбам мы упростили процедуру смены email’ов к аккаунтам и проектам, закрепленными за этими аккаунтами. Теперь вы можете изменить свой электронный адрес самостоятельно в меню настроек вашего профиля в разделе оповещений....