Skip to content

A real $.push over

How do we add an element to an array in JavaScript? If arr is an array, then arr.push(element) adds the element to the array. Nice and simple.

But what if we have obj.arr? Well, then obj.arr.push(element) works.

But what if arr might not exist? If we do the above and arr does not exist, then we get an error. Of course arr will exist; we initialize it.

This might generally be the case for stand alone arrays. But what if this is part of a process that is grabbing a bunch of input and creating a variety of arrays in this big object and we want to create arrays when adding the first element to a category. So we need to test the existence of an array and then add the element if it exists or create the array and then add the element.

Example code:

if (!(obj.arr)) {obj.arr = []};
obj.arr.push(element); 

or we could

if (obj.arr) {
   obj.arr.push(element); 
} else {
   obj.arr = [element];
}

Both of these are fine constructions, but they seem to obscure the point just like the for loops do.
There are a couple of cryptic solutions:

(obj.arr) ? obj.arr.push(element) : (obj.arr = [element]);

Here we are compactifying the if statement into a one-liner using the ternary operator. I don’t like the ternary operator. Notice the parentheses around the assignment.

Another way is with the short-circuit operators. I enjoy using them, but admit that they really obscure what is going on:

(obj.arr) &&  (obj.arr.push(element)) || (obj.arr = [element]);

Notice if the obj.arr exists, then the && operator proceeds to the next, doing the operation and giving a result of true. With true, the or || is done evaluating. If obj.arr does not exist, we get a false value. This short circuits and we never get to the second term. Instead, we now move onto the or|| term and create the array.

As I said, I like this conceptually because it is fun. But for programming style, I would have to say it is poor. I bet the ternary operator was invented just to avoid people using this construct.

Now none of this pleases me. Why? Well, it is the same convoluted mental construct each time. One has to keep asking why am I doing this. And of course it is easy enough to forget to check for existence and have an error pop up on debugging (easy enough to fix, but …)

My solution? Invent a function that can do this. So the syntax I would like to use is something like
$.push(obj, “arr”, element)
So we have the object and the property name that corresponds to the array. This function would check existence, create if necessary, and push the value if not. Notice the dollar sign. Yes, I like to extend jQuery with my utility functions. It is convenient conceptually to put them all there.
So a simple implementation:

$.push = function (obj, propName, element) {
                if (obj[propName]) {
                    obj[propName].push(element);
                } else {
                    obj[propName] = [element];
                }
}; 

This does the same thing as done before, but now abstracts it away. Which also allows me to optimize it if I find out that this construct is very bad.
I have no error code in here; considering what we are doing is dealing with something of a dodgy nature, it would be good to add that. We could also do something similar for objects.

For an object, the analog would be obj.top.key = value where we need to know the top object exists. So the syntax of interest would be
$.push(obj, top, value, key)
Why do I have the value first? Because that aligns with the array and the presence of key tells us that we are dealing with an object.
So my push function:

$.push = function (obj, propName, element, key)  {
        try {
            if (key){
                if (!(obj[propName])) {obj[propName] = {};}
                obj[propName][key] = element;
            } else {
                if (obj[propName]) {
                    obj[propName].push(element);
                } else {
                    obj[propName] = [element];
                }
            }
        } catch (e) {
            throw "error in $.push"+e; 
        }
    };

Why the not and object creation that way with the assignment below it, but nor for arrays? Because {key:element} would have the property name be “key”, not the string value stored in key. It is equivalent to obj.key = element. What we want is obj[key] = element as the property should be the string in key, not the literal “key”.

Another thing of interest for me is a deeper protection against creation. For example, if traversing file hierarchies, we might be 5 folders deep and need to construct all the intervening objects. I call this $.deepPush(obj, propNames, propName, element, key) where propNames is all the intervening middle objects before we get to the final object that will contain element/key (or the element array):

    $.push = function (obj, propName, element, key)  {
        try {
            if (key){
                if (!(obj[propName])) {obj[propName] = {};}
                obj[propName][key] = element;
            } else {
                 if (obj[propName]) {
                    obj[propName].push(element);
                } else {
                    obj[propName] = [element];
                }
            }
        } catch (e) {
            throw "error in $.push"+e; 
        }
    };
    $.deepPush = function (obj, propNames, propName, element, key) {
        try {
            $.each(propNames, function(ind, propName){
                        if (!(obj[propName])) {
                            obj[propName] = {};
                        }
                        obj = obj[propName];
            });
            $.push(obj, propName, element, key);
        } catch (e) {
            throw "error in $.deepPush"+e; 
        }
    };

Note I would recommend that one use a try{ full path} catch (e) {$.deepPush} kind of construct if one was doing something with repeated very deep construction. caching it/memoizing it might be useful as an alternative. That is, attach a hash to the $.deepPush function that keeps the propNames for look up value. But profile first. I imagine by the time this is needed, a total redesign of the approach might be needed.

And yes, this passes JSLint. In fact, JSLint frowned on my obscure solutions which is what forced me to think about this and write a better solution.

Enjoy this free to use code and the freedom of JavaScript!

Post a Comment

Your email is never published nor shared. Required fields are marked *