10

I have node v0.10.28 installed along with V8 v3.14.5.9 on Fedora 19. The issue I am having is with methods that have a thisArg optional argument, like Array.prototype.forEach.

If I execute the following code on Chromium v33 or Firefox v28 - jsFiddle

var y = [1, 2, 3];

y.forEach(function (element) {
    console.log(this);
}, 'hej');

I get an output of

String {0: "h", 1: "e", 2: "j", length: 3}
String {0: "h", 1: "e", 2: "j", length: 3}
String {0: "h", 1: "e", 2: "j", length: 3}

And then the same code but in strict mode - jsFiddle

var y = [1, 2, 3];

y.forEach(function (element) {
    'use strict';
    console.log(this);
}, 'hej');

I get an output

hej
hej
hej

These are the results that I would expect as per the ECMA5 specification sec-function.prototype.call.

The thisArg value is passed without modification as the this value. This is a change from Edition 3, where an undefined or null thisArg is replaced with the global object and ToObject is applied to all other values and that result is passed as the this value. Even though the thisArg is passed without modification, non-strict mode functions still perform these transfromations upon entry to the function.

and for example sec-array.prototype.foreach

If a thisArg parameter is provided, it will be used as the this value for each invocation of callbackfn. If it is not provided, undefined is used instead.

and relevant pseudo code

Let funcResult be the result of calling the [[Call]] internal method of callbackfn with T as thisArgument and a List containing kValue, k, and O as argumentsList.

On node, however, both the above snippets return

{ '0': 'h', '1': 'e', '2': 'j' }
{ '0': 'h', '1': 'e', '2': 'j' }
{ '0': 'h', '1': 'e', '2': 'j' }

Can anyone confirm whether this is an issue with my node environment, or if this is a problem with node?

Update: just to confirm, in both cases on node typeof this returns object.

Xotic750
  • 22,914
  • 8
  • 57
  • 79
  • `console.log(typeof this);` – cookie monster May 06 '14 at 02:09
  • @cookiemonster In both cases it shows as `object` rather than (strict) `string` and (non-strict) `object`, which is not what I would expect. – Xotic750 May 06 '14 at 02:13
  • Yep, just did the same test. `this instanceof String` is `true`. Odd. I can't imagine Node itself having anything to do with it, unless there's some way in V8 to entirely disable certain strict mode features even in nested functions. – cookie monster May 06 '14 at 02:15
  • Other strict mode features seem to be working properly. – cookie monster May 06 '14 at 02:17
  • I wonder if they've overwritten the native `.forEach` for some reason, and messed up the implementation. The `.toString()` does show `[native code]`, but that can be faked. – cookie monster May 06 '14 at 02:24
  • Or perhaps the issue is with V8 rather than node itself? This is where I am unsure/unable to find information. – Xotic750 May 06 '14 at 02:25
  • Except that if it was in V8, you'd think it would show up in Chrome too, unless it's a different version. But I just tested with `null` as the `this` value, and it works properly. Weird. – cookie monster May 06 '14 at 02:30
  • I came across this because of the [Principles of Writing Consistent, Idiomatic JavaScript 6.C](https://github.com/rwaldron/idiomatic.js) by R.Waldron, where he states "Several prototype methods of ES 5.1 built-ins come with a special thisArg signature, which should be used whenever possible" then upon testing this cropped up. I looked through the [node issues](https://github.com/joyent/node/issues) but didn't notice any reports about it. My first assumption was that something was wrong on my end. – Xotic750 May 06 '14 at 02:39
  • 1
    Check out this V8 bug report: https://code.google.com/p/google-caja/issues/detail?id=1501 Seems like this was fixed last year. My version of Node must have an old V8. I'm running Node 0.10.24, so it's pretty close to your version, which seems to be the latest. – cookie monster May 06 '14 at 02:44
  • It seems that the V8 fix was committed [Apr 5th 2013 r14149](https://code.google.com/p/v8/source/detail?r=14149) but I don't know what the V8 version number would be that first includes it. – Xotic750 May 06 '14 at 02:54
  • I wasn't paying attention. Here's the [V8 report](https://code.google.com/p/v8/issues/detail?id=2273), which you've probably already seen since it I found it via the links in the commit you found. – cookie monster May 06 '14 at 03:03
  • According to their [change log](http://nodejs.org/changelog.html), it seems like the last time they upgraded the V8 engine was in May 2013, which is pretty close to that commit date, so I think that's likely the issue. – cookie monster May 06 '14 at 03:09
  • Ok, I just tried Chrome v27 on browserling, and it has the issue that I described. It was running V8 v3.16.something, so clearly the V8 on my node is broken wrt this. So I guess this leads to the question "can I update the V8 version that is used by node?" – Xotic750 May 06 '14 at 03:09
  • They have "Unstable" releases you can use. Not sure *how* unstable they are. Or maybe file a bug requesting that they update V8 in their next Stable release. – cookie monster May 06 '14 at 03:13
  • You should summarize this in an answer. – cookie monster May 06 '14 at 03:15
  • I will do that, but will have to wait until after sleep. :) Thanks for your help. – Xotic750 May 06 '14 at 03:17
  • Send me a notification when you post it so I can give it a +1. That was a good find. Surprised that it hasn't come up before. – cookie monster May 06 '14 at 03:19

1 Answers1

3

The problem does exist with node v0.10.28 (latest stable) installed along with V8 v3.14.5.9 (and earlier versions), but the problem is not node itself but with V8, which has a bug.

The bug report can be found in issue 2273 posted on Aug 5, 2012.

A strict mode function should receive a non-coerced 'this' value. That is, 'this' can be undefined/null instead of the global object, and primitive values instead of boxed values.

It does not matter whether the caller function is in strict mode or not. However, built-in functions such as 'Array.prototype.forEach' incorrectly do the coercion even though the function to be called is in strict mode.

Test case:

(function() {
  var logger = function() {
    "use strict";
    console.log(this);
  };

  var strictCaller = function() {
    "use strict";
    logger.call("foo");
  };

  var nonStrictCaller = function() {
    logger.call("foo");
  };

  var forEachCaller = function() {
    [123].forEach(logger, "foo");
  };


  // call from strict function: logs primitive value
  strictCaller();

  // call from non-strict function: logs primitive value
  nonStrictCaller();

  // call through forEach: logs *boxed* value (WRONG)
  forEachCaller();
})();

The bug fix was committed to the V8 source code in revision r14149 on Apr 5, 2013

So the problem was long standing and affected all environments that were based on the V8 engine.

I was able to confirm that Chrome v27 was still affected with this issue and it was running V8 v 3.16, and can confirm that Chrome v34 with V8 v3.24.35.33 is no longer affected. So somewhere between these 2 the fix for V8 went mainstream.

A suggestion by @cookiemonster A solution may be to use a later version of node (from their unstable repo) but I can not confirm this.

I have not been able to find any report of this issue in the node issues list.

The only other solution is to test for this bug (code given above) and to shim the affected methods yourself. I have tested this solution and it works, here is the shim that I tested with. (taken from the es5-shim project)

Array.prototype.forEach = function forEach(fun /*, thisp*/ ) {
    'use strict';
    var object = Object(this),
        thisp = arguments[1],
        i = -1,
        length = object.length >>> 0;

    // If no callback function or if callback is not a callable function
    if (Object.prototype.toString.call(fun) !== '[object Function]') {
        throw new TypeError(); // TODO message
    }

    while (++i < length) {
        if (i in object) {
            // Invoke the callback function with call, passing arguments:
            // context, property value, property key, thisArg object
            // context
            fun.call(thisp, object[i], i, object);
        }
    }
};

The issue has been taken up with:

  1. idiomatic.js
  2. es5-shim
  3. nodejs
Xotic750
  • 22,914
  • 8
  • 57
  • 79