That is actually an undocumented feature. Looking at the implementation of selection.on() back in v3.5.17 you will notice that selection.each() is used internally to bind the listener to each node of the current selection.
return this.each(d3_selection_on(type, listener, capture));
From there on the internal function d3_selection_on will evaluate to onAdd for the addition of listeners and the reference to the onAdd function will be passed as the callback to .each(). Therefore, .each() will actually execute onAdd for every node in the current selection. onAdd, on the other hand, will close over the arguments passed to it by the .each() call whereby storing them in the listener's context:
function d3_selection_on(type, listener, capture) {
/* ... */
function onAdd() {
var l = wrap(listener, d3_array(arguments)); // wrap points to d3_selection_onListener
/* ... */
}
/* ... */
function d3_selection_onListener(listener, argumentz) {
return function(e) {
/* ... */
argumentz[0] = this.__data__; // Set the latest datum as the first argument
try {
listener.apply(this, argumentz); // Pass arguments from closure to the listener
} finally {
d3.event = o;
}
};
}
Looking at the implementation of selection.each() one notices that not just two arguments—as mentioned in the docs—are passed to the callback but rather three arguments:
if (node = group[i]) callback(node, i, j);
The third argument j being the group's index. Since your code does not make use of grouping that argument always evaluates to 0.
Modifying your demo one can easily demonstrate how setting up a grouped selection affects the value of that third argument. The following snippet duplicates your three rects putting each set of rects into one <g> while establishing a grouping using a sub-selection with its own data binding.
var svg = d3.select("body").append("svg")
function f(d, idx, whoami) {
console.log('I am the data:\t' + d);
console.log('I am the index:\t' + idx);
console.log('But who am i?\t' + whoami);
console.log('Length:' + arguments.length);
console.log(arguments);
}
var data = [['A', 'B', 'C'], ['a', 'b', 'c']];
svg.selectAll('g')
.data(data)
.enter().append('g')
.selectAll('rect')
.data(d => d)
.enter().append('rect')
.attr("x", function(el, i, j) {return j * 110;})
.attr("y", function(el, i) {return i * 40;})
.attr("width", 100)
.attr("height", 40)
.on("click", f, true);
rect {
fill: #333;
opacity: 0.3;
stroke: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
Again, this is totally undocumented as it is mentioned neither in the documentation on .each() nor in the one on .on().