Telerik blogs
js_pro_header

JavaScript hasn't always received much respect from developers. For many years, it was viewed simply as a scripting language. However, in recent years, JavaScript has seen a surge of new developers, enormous growth in frameworks and libraries, has garnered popularity as a server-side solution and even has significant enhancements in the works. JavaScript has become firmly entrenched in enterprise application development.

Developers who approach JavaScript as a simple scripting language to "get the job done" don't respect or understand its full potential. Programmers who try to force JavaScript into an object-oriented paradigm lose the power that comes with the flexibility and prototypal nature of the language.

In this article, we'll discuss a few concepts and tips for JavaScript development that are intended to help a developer progress from "meddling in scripts" to authoring powerful line-of-business solutions leveraging the built-in features of JavaScript. This article focuses on the ECMAScript 5 version that has ubiquitous support in modern browsers, but it is important to understand that ECMAScript 2015 (formerly version 6.0 or ES6, but everyone wants to version with years these days) is just around the horizon. ECMAScript 2015 features are also available through solutions like TypeScript and Babel.

The Type System

Many seasoned JavaScript developers still can't name the basic built-in types. JavaScript doesn't support integers, just numbers, and arrays aren't types but implementations. The types you have to work with in JavaScript include:

  • Undefined - this represents the state when a variable or property has not been assigned a value;

  • Null - this is its own type, and indicates a variable or property has been explicitly assigned a value that means "nothing" (as an interesting side note, this does not behave like SQL's null which means unknown so in JavaScript null is equal to null);

  • Boolean - either true or false;

  • String - this is technically a sequence of 0..n 16-bit unsigned integer values, but the implementation most commonly used is to store textual data;

  • Number - a double-precision 64-bit IEEE Standard for Binary Floating-Point Arithmetic (this type contains a special value called NaN which means "not a number" even though its type is, er, Number);

  • Object - a collection of properties.

Interestingly enough, if you run:

console.log(typeof []);

The result is object. And if you run:

function my() {}

console.log(typeof my);

You will get function, but this doesn't mean we are missing a type definition above.

According to the specification, a function is essentially a member of the Object type that is an instance of the standard built-in Function constructor and that may be invoked as a subroutine. You can deal with functions just like you would objects, as illustrated here.

function foo() {
    return "foo run";
}

foo.bar = "temp";

var myFoo = {
    bar: "tempbar",
    barfoo: foo
};

console.log(foo()); // foo run
console.log(foo.bar); // temp
console.log(myFoo.bar); // tempbar
console.log(myFoo.barfoo()); // foo run

Contrary to popular belief, you don't have to assume every variable is dynamic and ignore types altogether. In fact, it is a good standard to compare types and values using the === operator (strict) rather than == (abstract). As you may be aware, the former compares type and value, while the latter will coerce types. That is why 1 == "1" is true (the "1" is coerced to Number("1") and therefore 1), and 1 === "1" is false.

For a full list of ways values are compared with the abstract comparison operator, read the specification here.

If you are expecting a number, why not convert first (explicitly) and then compare? Here's an example function that converts to the numeric type then uses ===:

function isTwo(test) {
    var testNumber = Number(test);
    return testNumber === 2;
}

console.log(isTwo("two")); //false
console.log(isTwo("2")); //true
console.log(isTwo(2)); //true

There is also a neat trick for Booleans that I call, for lack of a better term, "whack, whack." It simply uses the not operator to convert any value to Boolean. The first ! takes the value and turns it into a Boolean value based on whether it is not truthy or falsy, the second simply flips it back to a truth operation.

Take a look at this simple set of tests:

function test(value) {
    return !!value;
}

console.log(test("")); //false
console.log(test("something")); //true
console.log(test("0")); //true
console.log(test(0)); //false
console.log(test(1)); //true

The last tip here is when you want to make a value unknown, set it to null, please, not undefined. To remove a property you use the delete keyword and it becomes undefined, a type intended to represent lack of initialization. If you have initialized your code and simply don't know a value yet, null denotes that you are aware of the property and have initialized it explicitly as an unknown or unset value. The distinction is subtle but necessary to ensure frameworks and developers have a consistent understanding of what it means.

Prototypes

JavaScript is not purely object-oriented, but it is object-based. I've seen more confusion over this concept than anything else, especially when developers with a C# or Java background try to tackle the language. A full essay on prototypes is outside of the scope of this article, but here is a quick example to get you started.

Every function has the potential to create new objects using the new operator. When you set a property on the function, it is like a static property because it is only available on the function definition, and not instances created from the function. This is demonstrated here:

function foo() {}
foo.bar = "this";
var bar = new foo();
console.log(bar.bar); //undefined
console.log(foo.bar); //"this"

Now let's look at an example of extending an object using its prototype. Note that in the following example, we initially explicitly assign bar on the bar1 object. But when we apply the property to the prototype, it gets picked up by the bar2 object but does not overwrite bar1. This is important because it illustrates the way inheritance works. When a property is accessed, it is first checked on the object, and barring that (pardon the pun) is referenced on the prototype.

function foo() {}

var bar1 = new foo(),
    bar2 = new foo();

bar1.bar = "that";

console.log(bar1.bar); // "that"
console.log(bar2.bar); // undefined

foo.prototype.bar = "this";

console.log(bar1.bar); // "that"
console.log(bar2.bar); // "this"

Finally, you may be thinking "But I can just use this." In other words:

function foo() {
    this.bar = "that";
}

This is perfectly fine, but keep in mind it will only create a property on each local instance. In fact, it's important to note that if you define a function in the constructor, a new function is declared for every instance that is created. On the other hand, if you use the prototype instead, you only declare the function once and each instance uses the prototypal definition.

This is subtle but important. You can think of the constructor functions as "private, instance based" and the prototypes as "public, prototype-based." Understanding prototypes is key to building quality, scalable, and maintainable JavaScript code.

Scope, Closures, and Captures

Although there are myriad concepts to learn in JavaScript, I believe the third most important group of concepts to understand are closures, captures, and the way JavaScript handles scope. The canonical example is this code:

for (x = 0; x < 10; x += 1) {
    setTimeout(function () {
        console.log(x); // always logs 10
    }, 0);
}

This demonstrates a few concepts. First, x is scoped to the outer function, so in the for loop it has a context, while setTimeout occurs sometime in the future. Therefore, x is said to be captured which means a reference to the outer "x" is preserved for the setTimeout call.

In addition, this code is subject to the JavaScript event loop. To oversimplify, the for loop is run first before any of the setTimeout calls are invoked. Even though the timeout value is 0ms, the calls are queued and wait for the main process to provide an opening for execution before they are pulled off the queue. (For the less simple but more thorough explanation take a look at How JavaScript Timers Work). Therefore, you end up with ten queued calls that are all accessing the same referenced value of x. Because x has been set to 10 by the loop, every call logs 10 to the console.

Let's refactor the code to this:

function makeTimeout(x) {
    setTimeout(function () {
        console.log(x);
    }, 0);
}

for (x = 0; x < 10; x += 1) {
    makeTimeout(x);
}

This example just does one thing: it changes the scope of x. In this case, x is passed to the makeTimeout function. I intentionally confused things by naming the parameter the same, so that I can illustrate it is indeed the value you expect but in a different context.

The first time the loop runs, 0 is passed to makeTimeout. Now that local parameter is in the makeTimeout scope for that call. That local value of 0 is captured by the setTimeut call. When the loop iterates to 1, the function is called again. This call, however, is separate, so we have a new closure around a new local parameter with a value of 1. That new instance is then captured, and so forth. Therefore, the queued calls all render the expected value.

You can also refactor the code to use the immediately-invoked function expression pattern, or IIFE. This is an anonymous function that is invoked right away. Take a look here:

for (x = 0; x < 10; x += 1) {
    (function (y) {
        setTimeout(function () {
            console.log(y);
        }, 0);
    })(x);
}

This is a way to take advantage of how scope and captures work. The anonymous function is used to wrap the call so a local instance of x is captured in the y parameter and the call will work as expected.

The IIFE pattern is a powerful tool in your toolbox. It allows you to author code without impacting other scripts or modules on the same page. I've formed a habit of writing all of my code inside of IIFEs and then pass in the values that make sense.

For example, you may use jQuery to resolve a Kendo UI widget. Instead of resolving it multiple times, you can pass it in and capture it using an easy to remember variable. This example demonstrates the concept using a DOM element:

<div id="myDiv">Hello!</div

(function (myDiv, setVal) {
    var x = 10;
    for (; x; x -= 1) {
        (function (y) {
            setTimeout(function () {
                setVal(myDiv, y);
            }, (10 - x) * 1000);
        })(x);
    }
})(document.getElementById("myDiv"),
function (divider, val) {
    divider.innerHTML = val;
});

Note that the outer function captures a reference to the target element and also defines a function to update it. The inner code doesn't reference the DOM at all, instead it uses a generic setVal call and passes in the element reference and new value.

Using this approach allows you to further abstract logic from presentation and enable a better designer/developer workflow. In fact, for testing you could inject a mock object and/or a mock function and the inner code will work the same. These concepts are just as useful when applied to projects using jQuery, Kendo UI, Angular, or any other framework.

What is this?

Finally, I urge you to spend some time understanding JavaScript's special this value, especially if you are writing reusable APIs. It can be the source of very annoying production defects if not handled properly. In short, a function call is passed a special value based upon the parent object it is called from. You can create some confusing behaviors if you think this is always the object the method is defined on.

Consider these three objects:

var obj1 = {
    foo: "this",
    bar: function () {
        return this.foo;
    }
}, obj2 = {
    foo: "that",
    bar: obj1.bar
},
obj3 = {
    foo: "the other"
};

obj3.bar = obj1.bar.bind(obj1);

Basically, the second object has a direct reference to the first, whereas the third is explicitly bound. The bind function is important to understand because it allows you to explicitly set the context of a method. In this case, even though the method will be called on obj3 the context has been set to obj1. You can see the result here:

console.log(obj1.bar()); //"this"
console.log(obj2.bar()); //"that"
console.log(obj3.bar()); //"this"
console.log(obj1.bar.call(obj3)); //"the other"

Notice the first three calls behave as expected, either defaulting this to the parent or setting it explicitly based on the call to bind. The last example demonstrates how to reuse a function in a different context.

Here it was explicitly set to the context of the third object and therefore accessed the obj3.foo property even though the method was called on obj1. This is more than just a cool trick. If you look at the source code for most frameworks and APIs, they rely heavily on these concepts.

For example, imagine creating a utility object widget with a method magic and binding it to the click event of a called element. If you want magic to understand the widget context, you'll need to override this because by default it will be the parent DOM element instead.

Summary

The goal of this article was to provide you with some specific, targeted concepts and tips that demonstrate both the power of JavaScript and unique features that set it apart from traditional object-oriented languages. These examples are the tip of the iceberg and the next step is to learn more about how these powerful concepts empower developers to design libraries, frameworks, and applications.

My suggestion is to take advantage of open source projects. You'll learn a lot from just examining how many developers use IIFEs to wrap their module definitions and map shims for common objects like the window and browser to enable cross-platform code that can run in Chrome as easily as on a NodeJS server. Make no mistake: JavaScript is here to stay and will be a fundamental part of your line of business applications, so why not focus on becoming an expert in the language that drives the operating system of the Internet?


jeremy likness
About the Author

Jeremy Likness

Jeremy is a senior cloud developer advocate for Azure at Microsoft and a former 8-year Microsoft MVP. Jeremy is an experienced entrepreneur and technology executive who has successfully helped ship commercial enterprise software for 20 years. You can read more from him at his blog or find him on GitHub.

Comments

Comments are disabled in preview mode.