Understanding d3.js source: stuck at function.call() and “=+”
-
12-06-2021 - |
Question
In the source code of d3.layout.force, line 158, there is this code
force.charge = function(x) {
if (!arguments.length) return charge;
charge = typeof x === "function" ? x : +x;
return force;
};
Now, if you go to line 225, you will see
charges = [];
if (typeof charge === "function") {
for (i = 0; i < n; ++i) {
charges[i] = +charge.call(this, nodes[i], i);
}
} else {
for (i = 0; i < n; ++i) {
charges[i] = charge;
}
}
What I did not understand here is the line
charges[i] = +charge.call(this, nodes[i], i);
I am new to JavaScript and can not understand what's going on here.
As far as I understood charge takes only 1 argument (x
). Here "this
" is passed to give the context of current object but what about the other two? Which one of "nodes[i]
" and "i
" is taken as "x
" ?
Again what is "= +
" doing here?
Solution
You have to follow charge
more carefully. It is variable defined in line 11:
charge = -30,
The function force.charge
which you quoted is for setting the charge, it is not the function referred to in +charge.call(this, nodes[i], i);
. Have a look at the second line of force.charge
:
charge = typeof x === "function" ? x : +x;
x
can be a function (callback) you pass, to dynamically calculate the charge. The current node (nodes[i]
) and the index of the node (i
) will be passed to this callback, so that you can calculate the charge dynamically based on these values:
force.charge(function(node, index) {
return index * 2;
});
x
(and therefore charge
) can also be a number or numerical string. That's why it is tested beforehand whether charge
is a function or not:
if (typeof charge === "function") {
// function so we call it and pass the current node and index
} else {
// static value, the same for each node
}
Apert from that, you can always pass any number of arguments to a function, no matter how many parameters it has defined. For example:
function foo() {
alert([].join.call(null, arguments));
}
foo('a', 'b');
will alert a,b
.
To answer your questions: The arguments passed to .call()
[MDN] or .apply()
[MDN] are passed in the same order to the function. So if I have a function function foo(a, b, c)
then foo.call(null, x, y)
would pass x
as a
and y
as b
(c
would be undefined
).
The +
operator is the unary plus operator [MDN], which simply converts the operand into a number.
OTHER TIPS
Check out the MDN listings for call, apply and bind.
It's a tough concept to wrap your head around but what's happening in call and apply is that you're choosing to execute a function in a different "context."
I say "context" with quotes as "execution context" has an exact meaning in JS and this isn't it. I don't have a great word for it but what's happening here is that you're making swapping out the this
object when executing the function.
This might help:
var obj = { foo: "bar" };
method.call( obj, "arg" );
function method( arg ) {
console.log( this.foo ); #bar
console.log( arg ); #"arg"
}