2

I have the following test case in a coding kata for javascript:

*word-count_test.js

var words = require('./word-count');

describe("words()", function() {

  it("handles properties that exist on Object’s prototype", function() {
    var expectedCounts = { reserved: 1, words : 1, like :1,  prototype: 1, and : 1, toString: 1,  "ok?": 1};
    expect(words("reserved words like prototype and toString ok?")).toEqual(expectedCounts);
  });
});

The following code will not pass this:

code v1

var words = function(phrase) {
    var wordCountAry = {};
    // split on whitespace, including newline
    phrase.split(/\s/).forEach(function(oneWord) {
        if (!wordCountAry[oneWord]) {
            wordCountAry[oneWord] = 1;
        } else {
            wordCountAry[oneWord]++;
        }
    });
    return wordCountAry;
};

module.exports = words;

But something like the following counter line will not trigger the error:

code v2

wordCountary[word] = (+wordCountary[word] || 0) + 1

So what is so special about that unary "+" operator?

Nona
  • 5,302
  • 7
  • 41
  • 79

3 Answers3

1

A standard JavaScript object prototype has predefined properties, such as:

  • toString()
  • constructor()

When you test those property names using typical conditional logic, they will appear as existent, e.g.

var x = {};
if (x['toString']) {
  // this gets executed
}

The second code sample works around that issue with two tricks:

+x['toString'] || 0;

First, the property is coerced into a numeric value by using the unary plus operator. For functions or undefined values, the coercion yields NaN.

Secondly, the logical or operator is used to coalesce the left hand and right hand expression; if the right hand operand evaluates to false (that includes NaN) it will yield the right hand operand, otherwise it returns the left hand operand.

In this manner, either an undefined property value or a function value will yield 0 and therefore it will work as expected.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
0

It's not really about "reserved" names (those are another matter in JavaScript, e.g. class although it's not reserved anymore in ES6), but about inherited properties.

wordCountAry is an object, so it already has a toString method. So when you test

if (!wordCountAry[oneWord])

with the words "toString" the condition will be false (because wordCountAry["toString"] is trueish (it's a function).

So you need to use a condition that will work when the property is not a number (which is the case with the Number coercion of a function with unary +), or, to be safer, also check if it is inherited (hasOwnProperty).

Touffy
  • 6,309
  • 22
  • 28
0

When you are creating your wordCountAry object, you can can pass null as the prototype, and this should solve your problem. So, instead of initializing it to {}, see how you can set it's prototype to null.

SherMM
  • 619
  • 6
  • 14