5

I saw here the Array.prototype.forEach()'s polyfill and I have a question about its implementation :

/*1*/   if (!Array.prototype.forEach)
/*2*/   {
/*3*/     Array.prototype.forEach = function(fun /*, thisArg */)
/*4*/     {
/*5*/       "use strict";
/*6*/   
/*7*/       if (this === void 0 || this === null)
/*8*/         throw new TypeError();
/*9*/   
/*10*/       var t = Object(this);
/*11*/       var len = t.length >>> 0;
/*12*/       if (typeof fun !== "function")
/*13*/         throw new TypeError();
/*14*/   
/*15*/       var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
/*16*/       for (var i = 0; i < len; i++)
/*17*/       {
/*18*/         if (i in t)
/*19*/           fun.call(thisArg, t[i], i, t);
/*20*/       }
/*21*/     };
/*22*/   }

Looking at line #10 : why did they use Object(this) ?

As I searched its usages I saw this :

The Object constructor creates an object wrapper for the given value. If the value is null or undefined, it will create and return an empty object, otherwise, it will return an object of a type that corresponds to the given value. If the value is an object already, it will return the value.

So they wanted to check if it's null || undefined .

Ok , But they already checked it in lines #7-#8 !

Question :

What is the reason (which I'm missing) that they used Object(this) ?

Royi Namir
  • 144,742
  • 138
  • 468
  • 792

3 Answers3

3

So they wanted to check if it's null || undefined.

No. The purpose is

The Object constructor creates an object wrapper for the given value. If the value is null or undefined, it will create and return an empty object, otherwise, it will return an object of a type that corresponds to the given value. If the value is an object already, it will return the value.

It will convert primitives to objects, like "foo" to new String("foo").

This is strictly not really necessary, because the property access of .length and the indices would convert a primitive to an object anyway. It would make a difference however when the third callback parameter is used, you can expect an object there (and the same one for each invocation).

What is the reason that they used Object(this)?

Mostly to reproduce step #1 of the forEach spec, which is to call ToObject on the argument. In the spec this is required to be able to use [[Get]] and [[HasProperty]] internal methods on it.

Also, by converting a primitive to an object only once you gain speed.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • So why this doesnt work ?`Array.prototype.forEach=function (){alert('')}; Object("4").forEach()` – Royi Namir Mar 24 '14 at 11:12
  • Because `Object("4")` doesn't produce an array object but a string object, so it's not calling your custom method but throwing an error. `Array.prototype.forEach.call(Object("4"), alert.bind(window))` would work. – Bergi Mar 24 '14 at 11:17
  • Exactly , so why to convert `"foo"` to `new String("foo")` ? it also doesnt reproduce array... – Royi Namir Mar 24 '14 at 11:21
  • Creating an array is not the aim of this conversion, because it takes that `Array.prototype.forEach` is already called on the value. – Bergi Mar 24 '14 at 11:25
1

I was searching SO for this question because I wandered myself why this was. The currently correct answer left me not at a 100% satisfaction. So I was trying to execute code that would break if I removed the Object(this) and just did var O = this;. The definition of the forEach function can be found here: ECMAScript forEach. So you can see why vars are created and there names etc. The polyfil tries to be exact as it can be.

Since the OP wants to only know about the Object(this) I'll focus on that. I've found a reason why this is needed. Try this code:

Array.prototype.forEach.call("abc", func);

Basiscally we loop over the abc string. The Object(this) makes the abc to:

String {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

If we did not do this abc would be just plain "abc". Then in the loop of the polyfill we do this:

kValue = O[k];

In the case of the "abc" string this would get each letter of the string. But this would not work and would give undefined on IE <8.

Why this doesn't work is explained in this quote for other question: "This notation does not work in IE7. The first code snippet will return undefined in IE7. If you happen to use the bracket notation for strings all over your code and you want to migrate to .charAt(pos), this is a real pain: Brackets are used all over your code and there's no easy way to detect if that's for a string or an array/object." (source)

But when using Object(this) this will be no problem.

Another quote which I've found handy:

"NOTE The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method. Whether the forEach function can be applied successfully to a host object is implementation-dependent." (source)

Community
  • 1
  • 1
Haagenti
  • 7,974
  • 5
  • 38
  • 52
0

The purpose is not to check for undefined-ness. Rather, it is to handle primitives.

For instance, if this were a number such as 42, then it would be Object(42), which is a Number object with a value of 42.

This means you can then treat the input like an object from then on, even if it was a primitive originally.

To test, try using the polyfill on a string - strings have a .length property. As an example, try...

[].forEach.call("Hello!",function(letter) {console.log(letter);});
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
  • @thefourtheye "fail"? How so? It will just return `undefined` and the loop will not iterate over anything (because `0 < undefined` is false) – Niet the Dark Absol Mar 24 '14 at 09:17
  • @NiettheDarkAbsol I dont understand : If I write this : `function (fun ) {alert('')};Object("4").forEach();` - it won't alert , and "4" do has `"Length"` prop.....so ? – Royi Namir Mar 24 '14 at 09:19
  • Because `forEach()` is a method of arrays, not `String` objects. You must use the sort of syntax I showed in my demonstration. – Niet the Dark Absol Mar 24 '14 at 09:23
  • @thefourtheye Try `5..length`. Bonus points if you can tell me why it works :p – Niet the Dark Absol Mar 24 '14 at 09:23
  • @NiettheDarkAbsol Because it treats the `5.` as float? – thefourtheye Mar 24 '14 at 09:31
  • @thefourtheye That's about right, yes. When it sees `5`, it starts looking for a number. `.` is valid in a number, so it takes that. Then it sees `length`, which is not valid for a number and unexpected, so it errors. However, if it sees a second `.` then that's not valid for a number, and it knows unambiguously that you're looking for a property lookup, in this case `length` (which of course is undefined) – Niet the Dark Absol Mar 24 '14 at 10:18
  • @NiettheDarkAbsol Right, but why people have downvoted this answer? This seems legit – thefourtheye Mar 24 '14 at 10:26
  • @thefourtheye Because people are idiots? XD I dunno. I'm mostly confused as to why no downvoter has commented... – Niet the Dark Absol Mar 24 '14 at 10:29