In AngularJS these two controller declarations are equivalent:
function BlahCtrl($scope, $http) { ... }
function BlahCtrl($http, $scope) { ... }
Both $http
and $scope
will be the correct variables no matter what order they are in. i.e. the variable named $http
will always be passed an instance of the $http
service.
How does Angular know which objects to pass in and in what order? I thought this kind of reflection was not possible with javascript.
You can refer to a function's arguments inside that function by using its arguments object. It has entries for each argument the function was called with, with the first entry's index at 0 . You can use arguments.length to count how many arguments the function was called with.
Use the typeof operator to check if a function is defined, e.g. typeof myFunction === 'function' . The typeof operator returns a string that indicates the type of a value. If the function is not defined, the typeof operator returns "undefined" and doesn't throw an error. Copied!
There are two ways to pass arguments to a function: by reference or by value. Modifying an argument that's passed by reference is reflected globally, but modifying an argument that's passed by value is reflected only inside the function.
typeof is a JavaScript keyword that will return the type of a variable when you call it. You can use this to validate function parameters or check if variables are defined. There are other uses as well. The typeof operator is useful because it is an easy way to check the type of a variable in your code.
If you call toString
on a function, you get the js declaration of that function:
function a(b,c) {}
a.toString(); // "function a(b,c){}"
then you can parse the string for the order of arguments.
Some investigation into the angular source code confirms this:
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
fn.$inject = $inject;
}
}
They stringify the function, then extract the arguments with a regular expression and store them in an array.
jsFiddle showing how this all works.
Although I don't know how they do it in, there is a simple way to do it.
Everything in JS has toString()
method. For functions, it shows the source code of that particular function (for in-built functions, you may get something like function() { [native code] }
).
Let us find the first (
and the first )
, which enclose the function's arguments. Then, let's strip the whitespaces and split the arguments by ,
. Voila, we get an array of argument names.
function a($scope, $http) { };
function b($http, $scope) { };
function getParameterList(f) {
var s = f.toString();
var start = s.indexOf('(');
var end = s.indexOf(')');
s = s.substring(start + 1, end);
return s.replace(/ /g,'').split(',');
}
So let's test it:
var aParams = getParameterList(a);
var bParams = getParameterList(b);
alert(aParams[0]); // $scope
alert(aParams[1]); // $http
alert(bParams[0]); // $http
alert(bParams[1]); // $scope
A fiddle: http://jsfiddle.net/jYPB8/
However, note that this behaviour of toString()
is defined in Function.prototype
and may be redefined - in which case, this algorithm won't work.
So while this may not be the actual solution you were looking for, I wanted to show you that this kind of reflection is possible in JavaScript, and it's actually very simple to do :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With