Design Pepper

A Drip of JavaScriptAbout

JavaScript's Primitive Wrapper Objects

Originally published in A Drip of JavaScript.

We've talked before about how in JavaScript most things behave like objects even when they aren't objects. For instance, consider how we can call methods on a string even though it is a primitive:

// Outputs: "FRED FLINTSTONE"
console.log("Fred Flintstone".toUpperCase());

How does that work, though? Initially you might think that strings are really objects in disguise and try assigning properties to them.

var fred = "Fred Flintstone";
fred.favoriteFood = "Brontosaurus Steak";

// Outputs: undefined
console.log(fred.favoriteFood);

But that doesn't work. And even more strangely, it doesn't trigger an error. It turns out that in order to allow you to call methods on a primitive, JavaScript does a little bit of trickery which we'll get to shortly.

Apart from null and undefined, each primitive type (string, number and boolean) has a corresponding object equivalent which you can create by invoking its constructor. For instance:

var barney = new String("Barney Rubble");

// Outputs: "Barney Rubble"
console.log(barney);

barney.favoriteFood = "Pterodactyl Eggs";

// Outputs: "Pterodactyl Eggs"
console.log(barney.favoriteFood);

// Outputs: "object"
console.log(typeof barney);

As you can see, though, the string object can have properties assigned to it, and it reports itself to be of type "object."

The trickery I mentioned before is that any time you attempt to access a property on a primitive, JavaScript will implicitly create a temporary wrapper object. We can verify this by doing the following:

String.prototype.reportType = function () {
    return typeof this;
};

var fred = "Fred Flintstone";

// Outputs: "string"
console.log(typeof fred);

// Outputs: "object"
console.log(fred.reportType());

When we directly check the type of a string primitive we get "string" as expected, but when we check the type of this in a method executed on a string primitive we get "object".

The JavaScript engine doesn't keep this wrapper object around, though. As soon as the work of the method (or other property) is done, it is disposed of.

This explains why trying to assign properties to a primitive doesn't work, but also doesn't throw an error. Assigning the property succeeds, but the property is set on a wrapper object which is immediately destroyed. So when you go to look up the property later, there is nothing there anymore.

I hope this has helped you better learn the quirks of JavaScript primitives.

Storing Metadata on Arrays in JavaScript

Originally published in A Drip of JavaScript.

We've talked before about the fact that in JavaScript, even arrays are objects. But one thing we haven't really talked about is the sort of flexibility that this implies.

Suppose that we have a system which we can query for records, but we want to be able to capture the time at which those records were returned. We could do something like this:

var digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function filterDigits (filterFn) {
    return {
        result: digits.filter(filterFn),
        timestamp: new Date()
    };
}

var filterObj = filterDigits(function(x) {
    return (x > 8);
});

// Outputs: [9]
console.log(filterObj.result);

// Outputs: Mon Nov 04 2013 13:34:09 GMT-0500 (EST)
console.log(filterObj.timestamp);

This works okay. But if you think about it, it is a little odd having to create an entirely new object just to store metadata. Wouldn't it be nice if you could just store that metadata directly on the the resulting array? It turns out that you can.

var digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

function filterDigits (filterFn) {
    var result = digits.filter(filterFn);
    result.timestamp = new Date();

    return result;
}

var filtered = filterDigits(function(x) {
    return (x > 8);
});

// Outputs: [9]
// Visual output may vary depending on your console.
console.log(filtered);

// Outputs: Mon Nov 04 2013 13:34:09 GMT-0500 (EST)
console.log(filtered.timestamp);

Because an array is an object, you can assign arbitrary properties to it. Using this approach means that our code maintains focus on the central concern (the results array) while still carrying around the metadata for those places which need it.

Looking at the changes in the variable names can help illuminate why it is an improvement. On the one hand, the name filterObj essentially means "a collection of disparate values." The name filtered on the other hand, conveys a single concept.

It's a little improvement, but little improvements add up over time.

Ditching jQuery with `querySelectorAll`

Originally published in A Drip of JavaScript.

For many developers, jQuery serves as their first introduction to JavaScript. And jQuery does a great job of easing the learning curve by hiding away browser inconsistencies and using an intuitive chaining syntax. But probably the most distinctive feature of jQuery is its use of CSS selectors to choose which elements to manipulate.

For example, consider an HTML page that looks like this:

<!doctype html>
<html>
    <head>
        <title>Programming Languages</title>
    </head>
    <body>
        <h1>Programming Languages</h1>

        <ul>
            <li>JavaScript</li>
            <li>CoffeeScript</li>
            <li>Ruby</li>
            <li>Python</li>
        </ul>

        <script src="example.js"></script>
    </body>
</html>

If you want to use jQuery to add a class to the first and last li, that's as simple as:

$("li:first-child, li:last-child").addClass("list-end");

What many people don't realize is that modern web browsers actually support a native DOM method that can use CSS selectors in exactly the same way. Let's see how that works.

var selector = "li:first-child, li:last-child";
var listEnds = document.querySelectorAll(selector);

var listEndsArr = [].slice.call(listEnds);

listEndsArr.forEach(function (el) {
    el.className += "list-end";
});

As you can see, querySelectorAll will return a list of matching elements. However, the list that it returns is a NodeList, one of those "array-like" objects that isn't really an array. That means that we can't directly use some array methods like forEach.

To get around that problem, we use slice.call to create a real array of the matching elements. And once we have a real array we use forEach to add the class to each matching element.

There are some issues to keep in mind when using querySelectorAll. First, the CSS selectors you can use will be limited by the selectors that the browser supports. Second, while it enjoys good support among modern browsers, querySelectorAll is not available in IE7 and below.

Of course, querySelectorAll isn't a full replacement for jQuery, but it does get you one step closer to jQuery independence.

Building Up Arrays with Array#concat

Originally published in A Drip of JavaScript.

Working with arrays is the bread and butter of being a JavaScript developer. And among the most common tasks is building up a new array out of smaller ones. Let's take a look at one way to do this.

Suppose that you are implementing a registry that holds a list of metahumans. We want to be able to add to the list by giving it a list of new metahumans. A quick and simple way to do it might look like this.

// Metahuman Registry
var mhr = [];

var heroes = ["Captain Marvel", "Aquaman"];
var villains = ["Black Adam", "Ocean Master"];

mhr = mhr.concat(heroes);

// Outputs: ["Captain Marvel", "Aquaman"]
console.log(mhr);

mhr = mhr.concat(villains);

// Outputs: [
//     "Captain Marvel",
//     "Aquaman",
//     "Black Adam",
//     "Ocean Master"
// ]
console.log(mhr);

As you can see, when we pass in an array concat creates a new array which consists of the elements of both mhr and whatever we passed in, maintaining the order of the elements. This is useful on its own, but it turns out that concat can actually accept multiple arguments. So we could rewrite the example above as follows:

// Metahuman Registry
var mhr = [];

var heroes = ["Captain Marvel", "Aquaman"];
var villains = ["Black Adam", "Ocean Master"];

mhr = mhr.concat(heroes, villains);

// Outputs: [
//     "Captain Marvel",
//     "Aquaman",
//     "Black Adam",
//     "Ocean Master"
// ]
console.log(mhr);

But concat can handle more than just arrays. If concat is given a non-array value, it will just drop that value into the new array at the appropriate location.

// Metahuman Registry
var mhr = [];

var heroes = ["Captain Marvel", "Aquaman"];
var villains = ["Black Adam", "Ocean Master"];

mhr = mhr.concat(heroes, villains, "Death");

// Outputs: [
//     "Captain Marvel",
//     "Aquaman",
//     "Black Adam",
//     "Ocean Master",
//     "Death"
// ]
console.log(mhr);

So far I've been glossing over an important detail. The concat method doesn't modify the array that it is called on. Instead it creates a brand new array. That's why in the examples above I've been assigning the result to mhr.

The fact that it is a new array can be quite useful. Suppose that you want to be able to check whether mhr has been modified since the last time you checked on it. You could do something like this:

// Metahuman Registry
var mhr = [];

var original = mhr;

// Outputs: true
console.log(mhr === original);

var heroes = ["Captain Marvel", "Aquaman"];
mhr = mhr.concat(heroes);

// Outputs: false
console.log(mhr === original);

I hope this look at concat gave you a deeper understanding of how it works.

Object Equality in JavaScript

Originally published in A Drip of JavaScript.

Equality is one of the most initially confusing aspects of JavaScript. The behavior of == versus ===, the order of type coercions, etc. all serve to complicate the subject. Today we'll be looking at another facet: how object equality works.

You might suppose that if two objects have the same properties and all of their properties have the same value, they would be considered equal. Let's take a look and see what happens.

var jangoFett = {
    occupation: "Bounty Hunter",
    genetics: "superb"
};

var bobaFett = {
    occupation: "Bounty Hunter",
    genetics: "superb"
};

// Outputs: false
console.log(bobaFett === jangoFett);

The properties of bobaFett and jangoFett are identical, yet the objects themselves aren't considered equal. Perhaps it's because we're using triple equals? Let's test that theory.

// Outputs: false
console.log(bobaFett == jangoFett);

The reason for this is that internally JavaScript actually has two different approaches for testing equality. Primitives like strings and numbers are compared by their value, while objects like arrays, dates, and plain objects are compared by their reference. That comparison by reference basically checks to see if the objects given refer to the same location in memory. Here is an example of how that works.

var jangoFett = {
    occupation: "Bounty Hunter",
    genetics: "superb"
};

var bobaFett = {
    occupation: "Bounty Hunter",
    genetics: "superb"
};

var callMeJango = jangoFett;

// Outputs: false
console.log(bobaFett === jangoFett);

// Outputs: true
console.log(callMeJango === jangoFett);

On the one hand, the variables jangoFett and bobaFett refer to two objects with identical properties, but they are each distinct instances. On the other hand jangoFett and callMeJango both refer to the same instance.

Because of this, when you are trying to check for object equality you need to have a clear idea about what sort of equality you are interested in. Do you want to check that these two things are the exact same instance? Then you can use JavaScript's built-in equality operators. Or do you want to check that these two objects are the "same value?" If that's the case, then you'll need to do a bit more work.

Here is a very basic approach to checking an object's "value equality".

function isEquivalent(a, b) {
    // Create arrays of property names
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);

    // If number of properties is different,
    // objects are not equivalent
    if (aProps.length != bProps.length) {
        return false;
    }

    for (var i = 0; i < aProps.length; i++) {
        var propName = aProps[i];

        // If values of same property are not equal,
        // objects are not equivalent
        if (a[propName] !== b[propName]) {
            return false;
        }
    }

    // If we made it this far, objects
    // are considered equivalent
    return true;
}

// Outputs: true
console.log(isEquivalent(bobaFett, jangoFett));

As you can see, to check the objects' "value equality" we essentially have to iterate over every property in the objects to see whether they are equal. And while this simple implementation works for our example, there are a lot of cases that it doesn't handle. For instance:

  • What if one of the property values is itself an object?
  • What if one of the property values is NaN (the only value in JavaScript that is not equal to itself?)
  • What if a has a property with value undefined, while b doesn't have this property (which thus evaluates to undefined?)

For a robust method of checking objects' "value equality" it is better to rely on a well-tested library that covers the various edge cases. Both Underscore and Lo-Dash have implementations named _.isEqual which handle deep object comparisons well. You can use them like this:

// Outputs: true
console.log(_.isEqual(bobaFett, jangoFett));

I hope that this drip of JavaScript has helped you get a better grasp of how object equality works.