Spread element magically turns functions into '

2020-03-09 07:26发布

问题:

Suppose I have this simple JavaScript function:

function returnArray(){
    return [1, 2, 3];
}

Further suppose that I then say

var test = [0, ...returnArray()];

You'd expect test to be equal to [0,1,2,3], and you'd be right. I tried that and of course it works.

Now I have this exercise where I'm suppose to build a function called double that takes an array as a parameter and returns another array that contains all of the original array's values doubled. So if I call double([1,2,3]) I should get [2,4,6]. The constraints of the exercise are that I must use only array destructuring, recursion, and the rest/spread operators to build my function. No array helpers allowed. So I came up with this:

function double(array){
  if (array.length===1) return 2*array[0];
  var [num, ...rest] = array;  
  if (rest.length!=0) return [2*num, ...double(rest)];  
}

If I run this function with any array whose size is at least two, I receive an error message saying that double is not a function. If I remove the ... operator before double, magically double is a function again, except that of course the result of double([1,2,3]) is [2,[4,6]], which is not quite the same as [2,4,6].

My first thought was that maybe, for some weird reason, you can't use ... in front of a function, even if the function returns an array, so I tested this hypothesis with my the returnArray() function above, and found out that it works just fine. I have no idea why it breaks down in the exercise. I can only guess recursion might have something to do with it, but I have no clue why it would be so. Can anyone point out what's wrong my my code?

EDIT: Thanks everyone, it was quite a silly mistake! I should have seen it. I added comments to some of your answers.

回答1:

It's a very strange error message, no question, but the main problem is a logical error in double: In two branches of the code, calling double results in a non-iterable value (in one case a number, in another undefined). But you're always applying spread notation to it. So that fails in those two cases. The cases are:

  1. You're only returning the number, not an array, in the array.length === 1 case.
  2. You're not returning anything in the case where array.length is not 1 and rest.length is 0, so the result of calling double in that case is undefined.

You're hitting the case where you try to spread a number first, like this:

function a() {
  return 42;
}
const b = [...a()];

For #1, you should be returning an array with one entry. For #2, You should be returning []. So the minimal changes version is:

function double(array) {
  if (array.length === 1) {
    return [2*array[0]];
    //     ^−−−−−−−−−−^−−−−−−−−−−− note
  }
  var [num, ...rest] = array;  
  if (rest.length > 0) {
    return [2*num, ...double(rest)];
  }
  return []; // <−−−−−−−−−−−−−−−−− note
}
console.log(double([1,2,3]));



回答2:

Your logical error has been pointed out in comments and answers, but let me point out a cleaner, simpler, less bug-prone way to write this which is more in line with basic principles of recursion.

function double([head, ...tail]) {
  if (head === undefined) return [];
  return [2*head, ...double(tail)];
}

In other words, there is only one "base case", namely the empty array, which returns an empty array. Everything else is simple recursion.

You could "functionalize" this further with

function map(fn) {
  return function iter([head, ...tail]) {
    return head === undefined ? [] : [fn(head), ...iter(tail)];
  };
}

const double = map(x => 2*x);
console.log(double([1, 2, 3]));


回答3:

Just a minor change:

function double(array) {
  // note the return here is an array, not a number
  if (array.length === 1) return [2 * array[0]];

  var [num, ...rest] = array;
  if (rest.length) return [2 * num, ...double(rest)];
}

console.log(double([1, 2, 3, 4]));

You were returning a number, and destructuring a number will leave you with an error.

...5 // throws SyntaxError


回答4:

Here is the error I get using node@6.10.3

if (rest.length!=0) return [2*num, ...double(rest)];  
                                        ^

TypeError: double(...)[Symbol.iterator] is not a function
    at double (/home/henrique/labs/test.js:4:41)
    at double (/home/henrique/labs/test.js:4:41)
    at Object.<anonymous> (/home/henrique/labs/test.js:7:1)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:390:7)

Which probably means that the result of the evaluation of some of your function call is not an iterable.

The error is here:

if (array.length===1) return 2*array[0];

Change to:

if (array.length===1) return [2*array[0]];

and it will work.