Adelin
Adelin

Reputation: 8219

Redundant type checking for function?

In the Polyfill section of the Array.prototype.forEach js mdn, you can find the following check:

if (typeof callback !== 'function') {
    throw new TypeError(callback + ' is not a function');
}

In this specific case, why bother checking for function? During my tests, calling the function directly yields the exact same behavior

var callback;
callback();
// Uncaught TypeError: callback is not a function

var callback = "";
callback();
// Uncaught TypeError: callback is not a function

var callback = 2;
callback();
// Uncaught TypeError: callback is not a function

var callback = [];
callback();
// Uncaught TypeError: callback is not a function

var callback = {};
callback();
// Uncaught TypeError: callback is not a function

Actually it's a little bit worse with this type of string concatenation, because we might see "[object Object] is not a function":

function newCallback(val){
    if (typeof val !== 'function') {
        throw new TypeError(val+ ' is not a function');
    }
}
newCallback({});
//Uncaught TypeError: [object Object] is not a function

Upvotes: 4

Views: 229

Answers (3)

t.niese
t.niese

Reputation: 40872

The behaviour of forEach is is defined that way 15.4.4.18 Array.prototype.forEach:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".
  3. Let len be ToUint32(lenValue).
  4. If IsCallable(callbackfn) is false, throw a TypeError exception.
  5. If thisArg was supplied, let T be thisArg; else let T be undefined.
  6. Let k be 0.
  7. Repeat, while k < len [...]

And because the polyfill is implemented according to the specification, the test if callbackfn is a function (4.) has to be done before the iteration starts (7.). And because of that the error will also be thrown even if callbackfn is not called (in the case when len is 0).

Upvotes: 2

Adelin
Adelin

Reputation: 8219

I just noticed that they don't explicitly call the callback like I did (callback()) but rather they do callback.call(), which no longer yields the same behavior and in this case makes sense to ensure the object is of type function.

// Call the Call internal method of callback with T as
// the this value and argument list containing kValue, k, and O.
callback.call(T, kValue, k, O);

Upvotes: 1

Ry-
Ry-

Reputation: 225095

In this specific case, why bother checking for function?

If the array is empty, there won’t be an attempt to call the function, but it should still fail.

[].forEach('foo')

There’s also a difference for non-empty arrays when element access has side-effects:

let foo = {
    get [0]() {
        alert(1);
    }
};

Array.prototype.forEach.call(foo, …);
// alert shows up if the early type check is missing

Upvotes: 3

Related Questions