JavaScript: It's Up Your Sleeve
If you come to JavaScript from C#, it looks familiar for about five minutes. Braces, semicolons, if and for. Then something strange happens: a variable changes meaning, this points somewhere unexpected, and a function turns out to be an object. The good news is that a handful of ideas explain most of it. Learn these and the language starts working for you.
Functions are values
In C# you know delegates and lambdas. In JavaScript, every function is already that. You can store one in a variable, pass it as an argument and return it from another function.
function add(a, b) { return a + b; }
var operation = add; // no parentheses: refer to the function
console.log(operation(2, 3)); // 5
function applyTwice(fn, value) {
return fn(fn(value));
}
console.log(applyTwice(function (x) { return x * 2; }, 5)); // 20
That is the same idea as passing a Func<int,int>, minus the type declarations.
Closures
A closure is a function that remembers the variables around it when it was created. C# lambdas do the same, so this should feel familiar.
function makeCounter() {
var count = 0;
return function () {
count = count + 1;
return count;
};
}
var next = makeCounter();
next(); // 1
next(); // 2
The variable count is private. Nothing outside can touch it, yet the returned function keeps using it. This is the foundation of data hiding in JavaScript.
The loop trap
A classic gotcha: functions created in a loop share one variable, because var is scoped to the function, not the block.
var handlers = [];
for (var i = 0; i < 3; i++) {
handlers.push(function () { return i; });
}
handlers[0](); // 3, not 0
The fix is to give each iteration its own scope:
for (var i = 0; i < 3; i++) {
(function (n) {
handlers.push(function () { return n; });
})(i);
}
Newer versions of the language add block-scoped declarations that remove the need for this trick, but you will meet the pattern in a great deal of existing code.
Objects are bags of properties
There are no classes you must define first. An object literal is just a set of named values:
var person = {
name: "Ann",
greet: function () { return "Hi, " + this.name; }
};
person.greet(); // "Hi, Ann"
person.age = 30; // add a property at any time
delete person.age; // and remove it
Think of it as a dictionary from strings to values, where some of the values happen to be functions. Square brackets let you use a dynamic key: person["name"].
Understanding this
The word this does not mean what it means in C#. It depends on how a function is called, not where it is defined. Called as a method (person.greet()), it is the object. Called as a plain function, it is the global object or undefined, depending on mode. Pass a method as a callback and it loses its object.
var g = person.greet;
g(); // this is no longer person
Two habits help: use bind (or call and apply) when you pass methods around, or capture the value up front with var self = this;.
The module pattern
Put the closure and object ideas together and you get a way to build something like a class with private state, without a class keyword.
var cart = (function () {
var items = [];
return {
add: function (item) { items.push(item); },
count: function () { return items.length; }
};
})();
cart.add("book");
cart.count(); // 1
The array is invisible to the outside. This also keeps your code out of the global scope, which matters because every script on a page shares one global namespace. Dumping variables there is the quickest route to two libraries stepping on each other.
Prototypes in one paragraph
JavaScript inheritance is based on objects pointing to other objects. If a property is not found, the engine looks at the object's prototype, then that object's prototype, and so on. Adding a function to a constructor's prototype shares it among all instances instead of copying it each time.
function Dog(name) { this.name = name; }
Dog.prototype.speak = function () { return this.name + " barks"; };
new Dog("Rex").speak();
Handy everyday tricks
- Default values:
var name = options.name || "guest";works well, but remember it also replaces empty strings and zero. - Variable arguments: every function has an
argumentsobject holding whatever was passed, regardless of declared parameters. - Array helpers:
map,filterandforEachon arrays are the counterparts of LINQ'sSelectandWhere. - JSON built in:
JSON.stringifyandJSON.parseconvert to and from text. - Truthiness: empty string, zero, null, undefined and NaN are all falsy; learn the list.
Testing and tooling
Treat JavaScript as real code. Use a linter, keep functions small and write tests with a framework of your choice. The same isolation thinking you apply in C#, such as injecting a clock instead of calling new Date() directly, carries straight over; compare it with funcy testing with delegates and isolating code with Moles and Fakes.
Where to go from here
Pick a small page you own and rewrite its script with the module pattern. Replace a loop with map. Find one place where this misbehaves and fix it with bind. In an afternoon you will have used every idea above, and the language will stop feeling like a stranger wearing C#'s clothes.
More .NET guides
- Moles and Pex: Isolating .NET Code and Generating Tests (Now Fakes and IntelliTest)
- Building a Web Framework Is the New Black
- Funcy Testing: Using Func and Action Instead of Interfaces
- MEF: Making Extensibility Fun
- A Gentle Introduction to WCF
- Tinyweb Series, Part 1: Getting Started
- Tinyweb Series, Part 2: Building APIs
- Tinyweb Series, Part 3: Dependency Injection and Filters
- Tinyweb Series, Part 4: Views, View Models and Model Binding
- Tinyweb Series, Part 5: The To-Do Demo
- The Ordered Jobs Kata: Dependency Sorting with TDD in C#
- C# News from the Future: What If...?
- What's After Unit Testing?
- The "If It Isn't Broken, Don't Fix It" Mentality
- Are You Failing Enough?
- Don't Bother Rewriting It: The Case for Gradual Refactoring
- Was Dave a Genius? Lessons from a Legacy Codebase
- Preston Tweetup: Why Developer Meetups Matter
- How Developers Really Learn
- Sustainable Side Projects: Keeping Personal Code Alive
- Parallel.For and Silver Bullets
Looking for a software partner? Browse the independent directory of French tech companies (web agencies, IT consultancies, software publishers; the directory itself is in French).