Starting to get into angular.js, I've seen a common pattern relating to arguments of callbacks. An example is the ui-router documentation:
var myApp = angular.module('myApp', ['ui.router']);
myApp.config(function($stateProvider, $urlRouterProvider) {
// Do stuff with ui-router providers
}):
But, from want I've seen, the myApp.config callback's arguments could be different and it would still behave as expected:
myApp.config(function(OtherProvider) {
// Do stuff with provider from another service
});
How does myApp.config know the difference? Is there done weird magic introspection going on, or is there some basic JS concept that allows for this? E.g. something like:
myApp.config = function(callback) {
var providerNames = callback.argumentNames;
var providers = this.findProviders(providerNames);
};
Perhaps I'm too used to python, where this sort of feature would only be possible through some very scary hacks.
This is basically part of javascript. Javascript is very very flexible regarding functional arguments when it is invoking.
For example you declare a function:
function abc (first, second) {
}
But when you call it, you can pass any number of arguments even without zero argument. If you don't pass an argument but it has been defined when declaring the function, you will get value 'undefined' for that particular argument but it does not interrupt code execution.
Javascript provide a special local array (Basically everything is object in JS) named 'arguments' inside the function. I am calling special because it an Array with only one Array properties 'length' other Array methods or properties are unavailable
abc(firs,second,third)
Inside abc, we can check provided arguments
function abc (first, second) {
var args_total = arguments.length;
var no_of_defined_args = Function.length;
}
Yes Function.length is another properties by which you can check how many arguments are really defined by this function
This is a "scary hack" used by Angular for one method of dependency injection - where the service names are automatically resolved from the argument names. There is no intrinsic support for such an operation at the language level.
The argument values (but not the argument names) can be accessed via the arguments object within the function. However, the argument names themselves can only be accessed by applying [[ToString]] to the function object and parsing the result1, which is a "scary hack".
In the explicit forms the injected parameters must appear in the same order as the list of supplied dependencies - in this case the names of the parameters don't matter because the service names have been supplied separately.
An excerpt of the forms, from the linked documentation, slightly modified:
// service names supplied separately
// - only Order of parameters matters
someModule.controller('MyController',
['$scope', 'greeter', function(I_am_a_scope, greeter) { ..
// service names supplied separately
// - only Order of parameters matters
var MyController = function($scope, GREETER) { ..
MyController.$inject = ['$scope', 'greeter'];
// "scary hack" magic of Angular JS, as service names not specified separately
// - the Name of the parameters is used as service name
// arguably Too Much Magic (TM), and a source of pain for minification
someModule.controller('MyController', function(greeter, $scope) { ..
The first two forms use Function.prototype.apply, which is similar to apply() in Python. The third asks for, and parses, the textual representation of the function - a "scary hack".
1To see how the magical form can be implemented, consider the following:
f = function (a,b) { return a+b }
f.toString() // e.g. -> "function (a, b) { return a + b; }"
ECMAScript 5 says Function.prototype.toString returns:
An implementation-dependent representation of the function is returned. This representation has the syntax of a FunctionDeclaration. Note in particular that the use and placement of white space, line terminators, and semicolons within the representation String is implementation-dependent.
(I'm not aware of any modern browsers that don't return a 'usable' result; the behavior of returning a representation is not optional is ES5.)
Related
I'm looking to see how the typeof operator in JavaScript knows that an object is a function.
To be precise, I'm wondering how I can take the function 'body' and reverse engineer it to determine what arguments it expects. The arguments property seemed close, but is only available within the evaluated function.
Neither uneval() or toSource() appear to do quite what I want, in addition to being obsolete.
The specification shows that:
Objects that do not implement [[Call]] are objects
Objects that do implement [[Call]] are functions
([[Call]] is an "internal property" of objects - it's not exposed directly to interactable running JavaScript)
So anything that can be ()d is a function. This will even include Proxies, since proxies can be called as if they were functions.
For the other question:
I'm wondering how I can take the function 'body' and reverse engineer it to determine what arguments it expects
The simplest method would be to turn the function into a string and use a regex to parse the parameter list:
function sum(a, b) {
return a + b;
}
const argList = String(sum).match(/\((.*)\)/)[1];
console.log(argList.split(/,\s*/)); // depending on how complicated the list is
// you may need to make the above pattern more elaborate
I'm wondering, why do I sometime see functions that returns a function?
For example this answer from this question:
AngularJS custom filter function
$scope.criteriaMatch = function( criteria ) {
return function( item ) {
return item.name === criteria.name;
};
};
What does it mean to have a function that returns another function that returns a value?
There are many cases where you'd want to return a function. In this situation specifically it deals with how angular defines filters. The outer function is meant to handle any dependencies that might need to be injected or any variables that might need to be specified. The inner function is the actual filter step that is applied on a collection to return a smaller collection.
All functional languages and hence JavaScript allows for **Higher order functions*, wherein functions are treated as first class members of the language and can be returned from another function or passed as parameter to another functions. This allows for a lot of power in the language enabling things like:
Closures: Closures are again different kind of beast and adds a lot of power to asynchronous programming via callbacks. You can read more about closures here: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures
Abstraction: When you return a function exposing only certain part of functionality, you can always hide some part of it using local variables. This allows for abstraction in a language like javascript wherein you don't have public, private access specifiers as in other languages like Java or C#
Currying: You can implement currying in javascript by returning functions applied on selected attributes. E.g. Define a function sum such that the parameters can be partially applied to it. sum(3)(4)
function sum (a) {
return function (b) {
return a + b;
}
}
Factory pattern: You can use Higher order functions to generate functions and use the function as a factory
There are many other features in JavaScript language possible only because of the capability to return functions.
I'm a seasoned Java developer by now, and I love how Java uses types, signatures, methods, etc. It makes everything understandable, easy to follow and logical.
Now I'm trying to develop something in Javascript. I'm trying to learn Angular JS. However I am finding it so very confusing to understand how this code is developed.
Take a look at the code at this tutorial about ng-repeat. I'll post a sampling of the code;
app.directive('lkRepeat', function(){
return {
transclude : 'element',
compile : function(element, attr, linker){
return function($scope, $element, $attr){
var myLoop = $attr.lkRepeat,
match = myLoop.match(/^\s*(.+)\s+in\s+(.*?)\s*(\s+track\s+by\s+(.+)\s*)?$/),
indexString = match[1],
collectionString = match[2],
parent = $element.parent(),
elements = [];
// $watchCollection is called everytime the collection is modified
$scope.$watchCollection(collectionString, function(collection){
var i, block, childScope;
// check if elements have already been rendered
if(elements.length > 0){
// if so remove them from DOM, and destroy their scope
for (i = 0; i < elements.length; i++) {
elements[i].el.remove();
elements[i].scope.$destroy();
};
elements = [];
}
My question is: How can you read such code? When functions are called, where are the method signatures? Also, when returning a value of function with parameters like $scope, $element, $attr, are these just place holders for the variables that will be passed? If so, then why do they have such funny names, why not drop the dollar sign?
When functions are called, where are the method signatures?
The closet JavaScript comes to having method signatures is that, when declaring a function, you can name the arguments.
function foo (named_argument, another_named_argument) {
Arguments are always optional. If you don't specify a value, it will get undefined.
You can specify as many arguments as you like and the function can access them through the arguments object.
Also, when returning a value of function with parameters like $scope, $element, $attr, are these just place holders for the variables that will be passed?
Yes
If so, then why do they have such funny names, why not drop the dollar sign?
The dollar sign is just a character that is allowed in a JavaScript identifier. Some people use it to indicate that the variable is expected to hold a jQuery object (because jQuery keys all its functionality off a variable called $).
I am trying to accomplish something like in : eval() with variables from an object in the scope
The correct answers suggests using the "with" keyword, but I can't find any examples of someone actually using "with". Can someone explain how to pass multiple variables using "with" into an "eval" expression like in the link above ?
i wouldn't recommend using with or eval except as a learning exercise because either one slows code down, and using them both at once is especially bad and frowned upon by the larger js community.
but it does work:
function evalWithVariables(code) {
var scopes=[].slice.call(arguments,1), // an array of all object passed as variables
A=[], // and array of formal parameter names for our temp function
block=scopes.map(function(a,i){ // loop through the passed scope objects
var k="_"+i; // make a formal parameter name with the current position
A[i]=k; // add the formal parameter to the array of formal params
return "with("+k+"){" // return a string that call with on the new formal parameter
}).join(""), // turn all the with statements into one block to sit in front of _code_
bonus=/\breturn/.test(code) ? "": "return "; // if no return, prepend one in
// make a new function with the formal parameter list, the bonus, the orig code, and some with closers
// then apply the dynamic function to the passed data and return the result
return Function(A, block+bonus+code+Array(scopes.length+1).join("}")).apply(this, scopes);
}
evalWithVariables("a+b", {a:7}, {b:5}); // 12
evalWithVariables("(a+b*c) +' :: '+ title ", {a:7}, {b:5}, {c:10}, document);
// == "57 :: javascript - How to pass multiple variables into an "eval" expression using "with"? - Stack Overflow"
edited to use any number of scope sources, watch out for property name conflicts.
again, i don't normally use with, but this was kinda fun...
Consider the following code
<body ng-app="myApp">
<h1>Hello {{name}}</h1>
<h2>reverse of your name is {{ name | reverse }}</h2>
<input type="text" ng-model="name">
<script src='https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.js'></script>
<script>
angular.module('myApp', [])
.filter('reverse', function(){
return function(str){
return str.split('').reverse().join('');
}
});
</script>
</body>
The interested bit here is the reverse filter. Here is what I think its doing:
calls angular.filter() with two args. arg1: string & arg2: function(anonymous or no-name function to be precise)
arg2 function doesn't take a argument and instead nested another anonymous function inside it.
This nested anonymous function does takes single argument - the text or string on which this filter would apply.
this nested anonymous function takes the string, reverses it.
Q1. Is my understanding correct?
Q2. What the difference between:
normal version:
angular.filter('reverse', function(str){
return str.split('').reverse().join('');
});
nested version:
angular.filter('reverse', function(str){
return function(str){
return str.split('').reverse().join('');
}
});
Q3. Why is extra level of function nesting useful & under what circumstances will I return the value directly. Or return a function which then does return the actuall result?
Q4. How does this affects scope? does closures have any role to play here?
JSFiddle: http://jsfiddle.net/C7EDv/
(Q1.1) Right- two args, a string with the name of the filter and...
(Q2/3) The second parameter to filter (arg2) is a constructor (or "factory") function. It is only executed once upon creation of the filter.
The constructor function must return a function. That returned function will be executed when the filter it is associated with is used. Put another way, the returned function is what is injected (using $injector) in to the filter (http://docs.angularjs.org/api/ng.$filterProvider)
I've added comments below detailing this:
angular.filter('reverse', function(service){
// This is run only once- upon creation
return function(str){
// This is run every time the filter is called
return str.split('').reverse().join('');
}
});
(Q3) You'll always use this form ("the nested form") otherwise you'll get an error (Object ... has no method 'apply') as Angular needs a function to be returned which it can call (using apply()) whenever the filter is used. This works exactly like all providers in Angular including services.
(Q2) Were it possible to do what you called the "normal version" then the filter would run only once, upon creation. And whatever return value it got would be used for every invocation of the filter. Since having a filter that always returns the same value wouldn't be useful very often Angular instead uses this javascript constructor pattern (which you'll see used elsewhere in JS) so that each use of the filter results in a new invocation of the associated function.
(Q1.4)Yes, the returned function does reverse the string. Here's a nice 2 minute video on exactly this filter: http://www.thinkster.io/pick/EnA7rkqH82/angularjs-filters
(Q1.2/3)Should your filter use any services you'd pass them in where I used service above (the above link has an example of that). The parameter str is the input to the filter like you noted.
(Q4) The "run once" area does create a closure- so you can use it like you would any other closure.