Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are the MDC prototype functions written this way?

Tags:

javascript

In MDC there are plenty of code snippets that meant to implement support for new ECMAScript standards in browsers that don't support them, such as the Array.prototype.map function:

if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp */)
  {
    "use strict";

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in t)
        res[i] = fun.call(thisp, t[i], i, t);
    }

    return res;
  };
}

What's the benefit (if there's any) of using this function rather than

function(fun, thisp)
{
  // same code, just without the "var thisp = arguments[1];" line:
  "use strict";

  if (this === void 0 || this === null)
    throw new TypeError();

  var t = Object(this);
  var len = t.length >>> 0;
  if (typeof fun !== "function")
    throw new TypeError();

  var res = new Array(len);
  for (var i = 0; i < len; i++)
  {
    if (i in t)
      res[i] = fun.call(thisp, t[i], i, t);
  }

  return res;
}

, var t = Object(this); rather than var t = this; and var len = t.length >>> 0; rather than var len = t.length;?

like image 645
some student Avatar asked May 11 '11 18:05

some student


2 Answers

var len = t.length >>> 0;    

This was covered pretty well in this question Basically it makes sure the number is a non-negative 32 bit int.

As for the Object constructor:

var t = Object(this);

If this is null or undefined, it'll return an empty object. From the MDC Docs on Object

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 type that corresponds to the given value.

They're both just quick ways to do error correction.

EDIT: I was thinking way too hard about the thisp part. I was assuming that using the arguments array was a way to ensure arguments were defaulting to undefined, but they do that on their own anyway. Mike Hofer got it right in the comments. It's Mozilla's coding style to indicate optional parameters. Function.call defaults to global if null or undefined is passed in as the first argument. From the MDC Docs on Function.call

thisArg: Determines the value of this inside fun. If thisArg is null or undefined, this will be the global object. Otherwise, this will be equal to Object(thisArg) (which is thisArg if thisArg is already an object, or a String, Boolean, or Number if thisArg is a primitive value of the corresponding type). Therefore, it is always true that typeof this == "object" when the function executes.

like image 196
Tom Chandler Avatar answered Sep 17 '22 12:09

Tom Chandler


var t = Object(this);

Is based on the ES5 specification. It specifically state that this should be passed into the Object constructor.

If you look closely at the exact algorithm specified in the ES5 spec then the method provided by Mozilla mirrors it almost exactly (it's limited by ES3 features). This is the reason this code seems to have a few quirks.

Here is the ES5 specification:

When the map method is called with one or two arguments, the following steps are taken:

  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 A be a new array created as if by the expression new Array(len) where Array is the standard builtin constructor with that name and len is the value of len.
  7. Let k be 0.
  8. Repeat, while k < len

    1. Let Pk be ToString(k).
    2. Let kPresent be the result of calling the [[HasProperty]] internal method of O with argument Pk.
    3. If kPresent is true, then
      • Let kValue be the result of calling the [[Get]] internal method of O with argument Pk.
      • Let mappedValue be the result of calling the [[Call]] internal method of callbackfn with T as the this value and argument list containing kValue, k, and O.
      • Call the [[DefineOwnProperty]] internal method of A with arguments Pk, Property Descriptor {[[Value]]: mappedValue, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and false.
      • Increase k by 1.
  9. Return A.

Let O be the result of calling ToObject passing the this value as the argument.

Notice that step one specifically says you should call Object(this)

Let lenValue be the result of calling the [[Get]] internal method of O with the argument "length".

Let len be ToUint32(lenValue).

Step 2 and 3 specifically say get t.length then call ToUint32 which is implemented as >>> 0 here.

The actual signature mentioned in the specification is

Array.prototype.map ( callbackfn [ , thisArg ] )

In the above signature callbackfn is a required argument and [ ] is an array of optional arguments which only contains one thisArg.

Mozilla have mirrored this in their definition of function(fun /*, thisp */) { to specify that thisp is an optional argument and it's clear this is the case from the function signature rather then from looking at the code.

like image 20
Raynos Avatar answered Sep 19 '22 12:09

Raynos