dot dot - 2 months ago 15
Javascript Question

JavaScript filter callback that uses arguments

The goal is to filter an array and remove all occurrences of elements specified in its argument list.

For example, given removeElements([1, 2, 3, 1, 2, 3,4], 2, 3), my output should be [1,1,4].

function removeElements(arr) {
//I got an error that says **functions** not allowed **inside loop**
for(var i=1;i<arguments.length;i++){
arr= arr.filter(function(e){
return e!==arguments[i];
});
}
return arr;
}


Second thing I tried is moving the filter out of the for loop.

function removeElements(arr) {
function isNotEqual(e){
return e!==this;
}
for(var i=1;i<arguments.length;i++){
arr= arr.filter(isNotEqual,arguments[i]);
}
return arr;
}


None of them work. It always return arr as [1,2,3,1,2,3,4].
Can you please tell as to what is wrong in my usage? Or what is the approach for using filter in this scenario?

Answer

To try to explain the reasons the snippets didn't succeed:

  1. Every function defines its own arguments, even when the function is embedded.

    function removeElements(arr) {
        console.log(arguments);
        // Arguments {
        //   0: Array [1, 2, 3, 1, 2, 3, 4],
        //   1: 2,
        //   2: 3
        // }
    
        arr = arr.filter(function (e) {
            console.log(arguments);
            // Arguments {
            //   0: 1, 2, 3, 1, ...             (each value in `arr`)
            //   1: 0, 1, 2, 3, ...             (each index)
            //   2: Array [1, 2, 3, 1, 2, 3, 4] (`arr` itself)
            // }
    
            // ...
        });
    
        return arr;
    }
    
    removeElements([1, 2, 3, 1, 2, 3, 4], 2, 3);
    

    By retrieving values from arguments inside of the iterator (function(e) {...}), the statement will compare e against values in the 2nd Arguments.

    for(var i=1;i<arguments.length;i++){
        arr = arr.filter(function(e){
            // 1st = 0              (the first index from `arr`)
            // 2nd = [1, 2, 3, ...] (the `arr` itself)
            console.log(arguments[i]);
    
            return e!==arguments[i];
        });
    }
    

    One option to resolve this is to access arguments outside of the iterator function, stashing the value in a variable that won't have the same conflict:

    for(var i=1;i<arguments.length;i++){
        var skip = arguments[i];
        arr = arr.filter(function (e) {
            return e !== skip;
        });
    }
    

    http://jsfiddle.net/y7evq6nq/

  2. If you're not using strict mode, the value of this will always be an Object.

    When you provide a primitive value for a thisArg, it will be boxed into its equivalent Object type. In this case, a new Number.

    function foo() {
        console.log(typeof this, this); // 'object' Number(3)
        return true;
    }
    
    [0].filter(foo, 3);
    

    And, since === first checks for type equality, a primitive and boxed number cannot be equal:

    var number = 3;
    var boxedNumber = new Number(3);
    
    console.log(typeof number);      // 'number'
    console.log(typeof boxedNumber); // 'object'
    
    console.log(typeof number === typeof boxedNumber); // false
    console.log(number === boxedNumber);               // false
    

    You can use the .valueOf() method to retrieve the primitive value from the object.

    function isNotEqual(e){
      return e!==this.valueOf();
    }
    

    http://jsfiddle.net/ow9b78bf/

    Or, you can try using strict mode, which allows this to hold a primitive value without boxing it.