简体   繁体   中英

JavaScript: passing built-in objects' methods as callback functions

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:

  • previousValue
  • currentValue
  • currentIndex
  • array

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM