Changing the function arguments trick

Modify the function's variables after the function executed.

I played with JavaScript closures before, for example changing the variables in the parent scope by recompiling the inner function. Now I found an even weirder trick to change the function's argument values after the function has been called.

Imagine we have a parent function with a couple of named parameters. The parent function returns another function named add. The function add always returns the sum of the arguments in the parent function.

1
2
3
4
5
6
7
8
9
10
11
12
13
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
return add;
}
var adder = parent(0, 1);
console.log(adder());
// in parent, a 0 b 1
// in child, a 0 b 1
// 1

Question:

Will adder() always return 1? Or is there a way to change its result?

Note that the outside code has time to try to modify the environment between the call to the parent and adder

1
2
3
4
5
var adder = parent(0, 1);
// do something here
// like adder.parent.a = 'foo'
// to get result "foo1"
console.log(adder());

Well, all parameters in a function are stored at runtime inside a special array-like object arguments. Can we return it from the parent function? Yes we can.

1
2
3
4
5
6
7
8
9
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}

Then we can modify the returned arguments by index to override the value of a and b!

1
2
3
4
5
6
7
8
9
10
11
12
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}
var adder = parent(0, 1);
adder.args[0] = 'foo';
console.log(adder());

the output shows the changed values between the parent run and the add execution

1
2
3
in parent, a 0 b 1
in child, a foo b 1
foo1

Can we modify both arguments? Yes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}
var adder = parent(0, 1);
adder.args[0] = 'foo';
adder.args[1] = 'bar';
console.log(adder());
// "foobar"

Do we need to pass "dummy" arguments to the parent? We intend to override them any way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}
var adder = parent();
adder.args[0] = 'foo';
adder.args[1] = 'bar';
console.log(adder());
// in parent, a undefined b undefined
// in child, a undefined b undefined
// NaN

Turns out - we do need to pass some dummy arguments to the parent function, otherwise the arguments object is not initialized. The number of dummy or placeholder arguments should be at least the number of arguments we intend to replace.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}
var adder = parent(0); // only 1 dummy argument
adder.args[0] = 'foo';
adder.args[1] = 'bar';
console.log(adder());
// in parent, a 0 b undefined
// in child, a foo b undefined
// "fooundefined"

We need both arguments, even if we pass undefined values. Thus we can simply pass the needed number of undefined values when calling the parent function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function parent(a, b) {
console.log('in parent, a', a, 'b', b);
function add() {
console.log('in child, a', a, 'b', b);
return a + b;
}
add.args = arguments; // parent arguments
return add;
}
var adder = parent.apply(undefined, new Array(parent.length));
adder.args[0] = 'foo';
adder.args[1] = 'bar';
console.log(adder());
// "foobar"

Note

The above example does NOT work in strict mode :( Modifying the arguments after the execution is forbidden.

Use case

Before you think this is a very contrived example without a practical use, let me show how we applied it. We have an open source library ng-describe for unit testing AngularJS code. Typical Angular unit tests are very verbose, with lots of boilerplate code just to inject the necessary dependencies from different modules.

1
2
3
4
5
6
7
8
9
10
11
12
13
// typical AngularJS unit test
describe('typical test', function () {
var foo;
beforeEach(function () {
angular.mock.module('A');
});
beforeEach(inject(function (_foo_) {
foo = _foo_;
}));
it('finally a test', function () {
expect(foo).toEqual('bar');
});
});

The same unit test can be written using ng-describe much shorter

1
2
3
4
5
6
7
8
9
ngDescribe({
modules: 'A',
inject: 'foo',
tests: function (deps) {
it('finally a test', function () {
expect(deps.foo).toEqual('bar');
});
});
});

One can inject multiple dependencies by providing a list of names. Each dependency will be placed into the deps object passed into the test callback function.

1
2
3
4
5
6
7
8
9
ngDescribe({
modules: 'A',
inject: ['foo', 'bar', 'baz'],
tests: function (deps) {
it('finally a test', function () {
// deps.foo, deps.bar, deps.baz
});
});
});

We need the dependencies object because the actual injection happens before each unit test; we don't want to initialize all the dependencies just once! Thus when the ngDescribe code executes the test callback, we only have the names of the dependencies. In a sense, we are scheduling each unit test by placing it in the queue with a corresponding beforeEach function.

1
2
3
4
5
6
7
8
9
10
11
12
13
// view of the test queue
var deps = {};
beforeEach(function () {
// for each inject name
// deps[name] = get the injected value "name"
});
tests: function testCallback(deps) {
// passed reference to empty deps object
it('finally a test', function () {
// deps.foo exists!
});
}
testCallback.apply(null, new Array(testCallback.length));

This is very similar to our initial parent - add example. The test callback is the parent, and each unit test function it is the inner one. The unit tests are placed onto the test runner queue; they will be executed later.

This gives us a way to implement a better API - instead of using an intermediate object deps that we can fill from the outside in beforeEach step, we can just list of the dependencies to be injected in the test callback.

1
2
3
4
5
6
7
8
ngDescribe({
modules: 'A',
tests: function (foo, bar, baz) {
it('finally a test', function () {
// use directly foo, bar, baz
});
});
});

Before we run the test callback, we can grab the names of the parameters it expects, there is Angular $injector.annotate utility that does it by inspecting function's source code. Thus our code can do something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// view of the test queue
var deps = {};
beforeEach(function () {
var parameters = $injector.annotate(testCallback);
// ['foo', 'bar', 'baz']
// for each inject name
// deps[name] = get the injected value "name"
});
tests: function testCallback(foo, bar, baz) {
// foo, bar, baz are all undefined
it('finally a test', function () {
// deps.foo exists!
});
}
testCallback.apply(null, new Array(testCallback.length));

But how do grab the reference to the arguments object from inside testCallback? We could ask the people writing unit tests to return it, but this would be bad :(

1
2
3
4
5
6
7
8
9
ngDescribe({
modules: 'A',
tests: function (foo, bar, baz) {
it('finally a test', function () {
// use directly foo, bar, baz
});
return arguments;
});
});

Here we apply another trick: we can change the source of the testCallback on the fly and add return arguments statement! Our logic then becomes something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// view of the test queue
var deps; // will be asigned below
beforeEach(function () {
var parameters = $injector.annotate(testCallback);
// ['foo', 'bar', 'baz']
// for each inject (name, index)
// deps[String(index)] = get the injected value "name"
});
tests: function testCallback(foo, bar, baz) {
// foo, bar, baz are all undefined
it('finally a test', function () {
// foo exists!
// bar exists!
// baz exists!
});
}
var testCallbackSource = testCallback.toString();
var addedReturnArguments = testCallbackSource.replace(/\}$/, 'return arguments;\n}');
var updatedCallback = eval('(' + addedReturnArguments + ')');
deps = updatedCallback.apply(null, new Array(testCallback.length));

Notice that we need to place injected values into deps structure (which now points) at the arguments returned by the rewritten testCallback function. We place the values by index, making sure the injected value foo goes into arguments[0], bar goes into arguments[1], etc.

The result is that when each unit test it runs, the named parameters in the parent scope testCallback are initialized. Nice! You can see the code for yourself inside ng-describe.js in the function runTestCallbackExposeArguments.