Reading AngularJS (1)

Today I want to begin a series based on an idea I had the other day. I’m going to begin reading the great programming literature of our time. I encourage you to read along. I will start with AngularJS. The source is freely available on GitHub. I forked it to make an unchanging historical copy that we can refer to. Today I’ll be referring to the document src/Angular.js which is available here: https://github.com/xerocross/angular.js/blob/xero-6-2019/src/Angular.js. I suggest you refer to my fork copy because the real source might change after I have written this. As I study the code, I will write about anything noteworthy. Along the way, hopefully we will learn some new JavaScript techniques.

Take a quick glance over this document and you will see that it is heavily commented. This code doesn’t even look like JavaScript as I know it. They are using JSDocs comments to describe the inputs and output of every function, much like the Javadocs style. The thoroughness and perfection of this commenting style looks like something that was compiled from a higher-level source, but I see no evidence of that. Instead, they may be using a highly customized eslint configuration to enforce this commenting behavior.

Starting on line 149 we see them using the var a, b, c = [something] syntax. JavaScript supports this syntax for defining a lot of variables all on the same line, but I personally don’t like to use it.

Starting on line 179 they define a function called isArrayLike. It contains an interesting line of code: var length = 'length' in Object(obj) && obj.length; This is an example of the JavaScript in operator. According to the Mozilla docs, “The in operator returns true if the specified property is in the specified object or its prototype chain.” But why are they checking if its in Object(obj) instead of obj itself. First of all, this is a usage I’ve never seen before. Object is a constructor function, but according to Mozilla, “when called in a non-constructor context, Object behaves identically to new Object().” Thus, if you call Object(obj) where obj is already a JavaScript object, then it just returns the same object. If obj is, say, a string primitive, then Object wraps it in an object, which means you get back a JavaScript String object. Therefore, I think the use of Object() here is a defense against the value of obj not being any kind of object at all, in which case asking “length in obj” would throw an error. The in operator requires an object on the right.

I also notice that the AngularJS developers seem to prefer using the classic function name() { } notation for declaring and defining functions. An alternative is to do something like this var name = function() { }. Both of these will declare the function and define it, but the difference is that the first option causes the function definitions to be “hoisted”. That means all the functions get defined before anything is actually executed. One consequence of this is that if one function calls another, you don’t have to worry about the order in which they are defined on the page.

Starting on line 376 we find a very strange little function defined as follows.

function extend(dst) {
  return baseExtend(dst, slice.call(arguments, 1), false);
}

The comments say that this function “extends the destination object dst by copying own enumerable properties from the src object(s) to dst.” You can use it like this for example: var object = extend({}, object1, object2). Recall that in JavaScript it’s perfectly legal to pass a function any number of arguments. Your function call does not have to match the signature of the function where it is defined. This is why polymorphism in JavaScript is impossible. What exactly is going on in this strange one-liner function?

Up near the top we find that they defined the slice function as slice = [].slice. They plucked the array slice method off an array so they could use it anywhere. Now let’s recall how the call method works. The call method allows us to pass the context of slice into the slice method as the first argument of the call method. So, for some reason they chose to write slice.call(arguments, 1) instead of arguments.slice(1). Either way, the return value of that expression is just an array containing all the arguments that were passed into extend except the first one, dst. Frankly I see no reason why they might prefer slice.call instead of arguments.slice. Let’s check that out.

Ok, it didn’t take long to figure that out. The arguments object that JavaScript makes available inside every function is an array-like object, but it does not have the slice method. There is no such method as arguments.slice. I did not see that coming. This makes me wonder if [].slice.call(arg, 1) will accept any array-like object in the first position. I leave that exercise to the reader. It’s plausible, but I don’t know yet.

The real work gets done in the baseExtend function, which starts on line 324.

That baseExtend function contains an interesting loop. It begins for (var j = 0, jj = keys.length; j < jj; j++) {. In this context, the keys variable came from var keys = Object.keys(obj). This value gets defined before the start of the loop. What if any value is gained by storing keys.length in this variable jj? There is a valuable answer to that question here: https://stackoverflow.com/​​questions/9592241/​​javascript-is-the-length-method-efficient. Basically, for native arrays browsers usually optimize the length method so that it’s about as fast as retrieving a stored number. But the same might not be true for other kinds of “array-like” objects. This is a subtle optimization—one I might add to my own programming style.

By the way, as I read further down the page, I’m beginning to see functions that are defined without the JSdocs comments I see on most of the functions here. I’m not saying that is a good or bad thing, but it does make this look more like it was actually written by and for humans.

This is funny. They have formally defined the do-nothing function: function noop() {}. I’ve used that function on many occasions, but I typically just write it directly into the code using anonymous function notation—mostly when I’m writing tests. But I like noop.

On line 630 we have another interesting function defined as follows.

function isError(value) {
  var tag = toString.call(value);
  switch (tag) {
    case '[object Error]': return true;
    case '[object Exception]': return true;
    case '[object DOMException]': return true;
    default: return value instanceof Error;
  }
}

The toString function is an alias of the Object.prototype.toString method, defined earlier in the file. All JavaScript objects that have the Object object as their prototype inherit a toString method. Usually it’s just useless. It’s why the useless “[object Object]” string pops up so often. This default function just returns “[object type]” where type is the object type. You can override toString by defining your own method of course. We can see that this isError function exploits that string response of the default toString method. The hacky nature of that—using a string representation to detect type—probably represents a flaw in JavaScript.

Lets play with this method on a modern browser and see what happens.

toString = Object.prototype.toString;
toString.call([2,3]); // "[object Array]"
toString.call("apple");// "[object String]"
toString.call({fruit: "apple"}); // "[object Object]"
toString.call(4); // "[object Number]"

I see one distinction here between using toString as defined above versus using the toString method defined directly on an object. Like I said above, an object can override the inherited toString method. For example, consider the following.

let e = new Error("ha");
e.toString(); // "Error: ha"
Object.prototype.toString.call(e); // "[object Error]"

That is all pre-defined behavior. Evidently, the native Error class overrides the toString method so that instead we get “Error:” plus the message text I used when I created the error object. If we want to use the default toString method that comes from Object.prototype, then we have to explicitly grab it and apply it to the error object. And that is why you see this Object.prototype.toString.call construction.

I’ve only read down the page to about line 700, but I’m going to call it a day for now. I found this exercise rewarding, and so far we haven’t even gotten into the mechanisms of AngularJS—the specifics of how it works. I think learning that by studying the source will prove very interesting. But good night for now. There will be more.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s