I've been working through Eloquent JavaScript 's exercises and found out something I think is odd. I wrote a trivial array-flattening piece of code:
var arrays = [[1, 2, 3], [4, 5], [6]];
var out = arrays.reduce(function(acc, next){ return acc.concat(next); });
console.log(out);
// → [1, 2, 3, 4, 5, 6]
So far so good. But that didn't seem pretty to me, so I rewrote it as:
var arrays = [[1, 2, 3], [4, 5], [6]];
var my_concat = function(acc, next){ return acc.concat(next); }
var out = arrays.reduce(my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
It was better, but do we really need to introduce a function, be it anonymous or named, to do such a basic thing? Array.prototype.concat.call
's call signature is exactly what we need! Feeling smart, I rewrote the code again:
var arrays = [[1, 2, 3], [4, 5], [6]];
var out = arrays.reduce([].concat.call);
// → TypeError: arrays.reduce is not a function (line 2)
Well, that haven't turned out as I expected. The error message seemed cryptic to me.
I decided to investigate. This works:
var arrays = [[1, 2, 3], [4, 5], [6]];
var my_concat = function(acc, next){ return [].concat.call(acc,next); }
var out = arrays.reduce(my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
And this also works:
var arrays = [[1, 2, 3], [4, 5], [6]];
arrays.my_concat = function(acc, next) { return [].concat.call(acc, next); }
var out = arrays.reduce(arrays.my_concat);
console.log(out);
// → [1, 2, 3, 4, 5, 6]
More tinkering in the console:
[].concat.call
// → call() { [native code] }
typeof [].concat.call
// → "function"
[].concat.call([1, 2, 3], [4, 5])
// → [1, 2, 3, 4, 5]
var cc = [].concat.call
cc
// → call() { [native code] }
typeof cc
// → "function"
cc([1, 2, 3], [4, 5])
// → Uncaught TypeError: cc is not a function(…)
And even this works:
Array.prototype.my_concat = function(acc, next) { return [].concat.call(acc, next); }
// → function (acc, next) { return [].concat.call(acc, next); }
[[1, 2, 3], [4, 5], [6]].reduce([].my_concat)
// → [1, 2, 3, 4, 5, 6]
[[1, 2, 3], [4, 5], [6]].reduce([].concat.call)
// → Uncaught TypeError: [[1,2,3],[4,5],[6]].reduce is not a function(…)
Is there something special about built-in functions like .call
?
call
is just a method that most functions inherit from Function.prototype
. That is,
arrays.reduce.call === Function.prototype.call
The call
method knows which function you want to call because that function is passed as the this
value.
When you pass call
as the callback, it will be called passing undefined
as the this
value. Since undefined
is not a function, it throws. On Firefox I get this error:
TypeError: Function.prototype.call called on incompatible undefined
Instead, you could try one of these callbacks
Function.call.bind([].concat);
[].concat.bind([]);
However, the problem is that this won't work properly, because the callback is called with 4 arguments, not 2:
You want to get rid of the last two, so you need a custom function anyways.
However, these are not good approaches. Each time you call concat
, it creates a new array. Therefore, if you want to flatten an array, you should call concat
only once instead of per each item in the array:
[].concat.apply([], arrays); // this works
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.