What's wrong with my map version for an array in JS?

6
var kiko = [1,2,3,4,5,6];

var forEach = function(array, newArray, action){
     for (var i=0; i<array.length; i++){
        var newArray = [];
        newArray.action(array[i]);
    };   
};

forEach(kiko, newKiko, push)
newKiko

I'm studying abstract functions, and I came across an example of the Eloquent Javascript book in which an array.map version is created from scratch. I tried doing mine, which scans an array, and creates another array for each item it passes through. I'm getting the error "Push is not defined" but it's a native method of arrays in JS. What could be going wrong?

I tried a second variant, but it gives me an empty array:

var kiko = [1,2,3,4,5,6];

var forEach = function(array, action){
    for (var i=0; i<array.length; i++){
        action(array[i]);
    };   
};


forEach(kiko, function(element){  
    var newKiko = [];
    newKiko.push(element);
})

newKiko
    
asked by anonymous 10.06.2015 / 19:20

4 answers

3

In your first code, you are trying to choose a method dynamically, but the . operator is not the correct way to do this. When you do objeto.algo it always looks for a property / method with the name algo , it does not matter whether or not there is a so-called variable. To choose a method of an object from its name, it is necessary to use strings and brackets:

var action = "push";
...
newArray[action](...);

This gives you some flexibility as long as the (newArray, action) pair is consistent (i.e. action always has the name of a newArray method that receives an argument). On the other hand, this forces you to always use an object, you can not use an isolated function.

Your second code, on the other hand, is perfect (the definition, not the call, as pointed out by other answers), that is, perfect for forEach (for map , see the bfavaretto answer ). If you want to call with an isolated function, you call, if you want to call with a method of an object, you "bind" that method to the object through the function bind (or perhaps with some other type of currying ):

var newKiko = [];

var push = Array.prototype.push;
// ou:
var push = newKiko.push;

var boundPush = push.bind(newKiko); // Fixa o "this" da função

...

boundPush(10); // Faz o mesmo que: newKiko.push(10)

Examples:

var kiko = [1,2,3,4,5,6];

var forEach = function(array, action){
    for (var i=0; i<array.length; i++){
        action(array[i]);
    };   
};

// Chamando com uma função qualquer
var newKiko = [];
forEach(kiko, function(element){  
    newKiko.push(element);
})
document.body.innerHTML += "<p>" + JSON.stringify(newKiko) + "</p>";

// Chamando com um método de um objeto
var newKiko2 = [];
forEach(kiko, newKiko2.push.bind(newKiko2));
document.body.innerHTML += "<p>" + JSON.stringify(newKiko2) + "</p>";

And by the way, the error in the call I mentioned earlier was re-creating the newKiko list within the function used as callback - the first example above shows the correct way to call the code that You wrote.

    
11.06.2015 / 01:16
5

To answer your question of what is failing in your code:

1- Your function does not return anything, it is running half-senseless. After the for loop you have to give return newArray;

2- The push method is an Array method and not available in global scope. You can "copy" it but you must call Array.prototype.push . Then when you use you have to use .call) like this:

function teste(arr, action) {
    action.call(arr, 3);
}
var foo = [1, 2];
teste(foo, Array.prototype.push);
console.log(foo); // dá [1, 2, 3]

3- When you use var newArray = []; within the for cycle you are deleting the array and starting over. You can remove this line.

The corrected code could look like this: link

var kiko = [1, 2, 3, 4, 5, 6];
var forEach = function (array, newArray, action) {
    for (var i = 0; i < array.length; i++) {
        action.call(newArray, array[i]);
    };
    return newArray;
};

var newKiko = forEach(kiko, [], Array.prototype.push)
console.log(newKiko); // [1, 2, 3, 4, 5, 6]

or if you do not use return but rather passing the array by reference: link

var kiko = [1, 2, 3, 4, 5, 6];
var forEach = function (array, newArray, action) {
    for (var i = 0; i < array.length; i++) {
        action.call(newArray, array[i]);
    };
};
var newArray = [];
forEach(kiko, newArray, Array.prototype.push)
console.log(newArray); // [1, 2, 3, 4, 5, 6]
    
10.06.2015 / 20:53
5

The other answers are very good, but you have to say one thing: why the hell do you want to create a map function and call it forEach ? They are two different things! And it is very important that the function name represents well what it does. The idea of a foreach is just to iterate over a list, such as the Maicon's response a>. If you intend to map one array to another, call the function of map itself.

On the implementation, I really like the book version you cited (great book, by the way):

function map(array, transform) {
  var mapped = [];
  for (var i = 0; i < array.length; i++)
    mapped.push(transform(array[i]));
  return mapped;
}

Notice that it does not get the output array, it creates this array and returns. Maybe you have a previous C training, where it is common to pass a pointer to an output array, but in JS this would rarely be a good solution.

    
10.06.2015 / 21:49
3

In the first example, the push function is not set because it is unique to Array . It is not a global function.

In your second example the error is here:

forEach(kiko, function(element){  
    var newKiko = []; /* <-- aqui */
    newKiko.push(element);
})

You are cleaning the array every time it passes for . The correct would be:

var kiko = [1,2,3,4,5,6];
var newKiko = []; /* coloca aqui */

var forEach = function(array, action){
    for (var i=0; i<array.length; i++){
        action(array[i]);
    };   
};


forEach(kiko, function(element){  
    newKiko.push(element);
})

newKiko
    
10.06.2015 / 19:45