A few notes on how JavaScript works.

Source: Thibault Laurens

V8 is a JavaScript engine built in the google development center in Germany. It is open source and written in C++. It is used for both client side and server side.

V8 compiles JavaScript code into machine code at execution by implementing a JIT (Just-In-Time) compiler. There is no intermediate code in this process from JavaScript to machine code.

It’s not just about making your current application run faster, it’s about enabling things that you have never been able to do in the past

— Daniel Clifford tech lead and manager of the V8 team

JavaScript is:

  • a prototype-based language: there are no classes and objects are created by using a cloning process.
  • dynamically typed: types and type informations are not explicit and properties can be added and deleted to objects on the fly.

To access types and properties effectively, V8 creates hidden classes, at runtime, in order to have an internal representation of the type system and to improve the property access time.

For instance consider a Point() function and the creation of two Point objects:

If the layout of the new objects is the same, they belong to the same hidden class created by V8. This highlights another advantage in using hidden classes: it allows V8 to group objects whose properties are the same. Hence, in the example above, p and q use the same optimized code.

Not let’s assume that we want to add the z property to our q object, right after its declaration (which is permissible in a dynamically-typed language).

For the scenario above V8 creates a new hidden class overtime the constructor function declares a property and keeps track of the changes in the hidden class:

Overtime a new hidden class is created, the previous one is updated with a class transition indicating what hidden class has to be used instead of it.

Because V8 creates a new hidden class for each property, hidden class creation should be kept to a minimum. To do this:

  • try to avoid to add properties after the object creation and always initialize object members in the same order (to avoid the creation of three hidden classes).
  • Monomorphic operations are operations which only work on objects

To have an efficient representation of numbers and JavaScript objects, V8 represents both with a 32 bit value. It uses a bit to know if it is an object (flag=1) or an integer (flag=0) called here the SMI or SMall Integer because of its 31 bits. Then, if a numeric value is bigger than 31 bits, V8 will box the number, turning it into a double and creating a new object to put the number inside of.

Code optimization: try to use 31 bit signed numbers whenever possible to avoid the expensive boxing operation into JavaScript object.

V8 uses two different methods to handle arrays:

  1. Fast elements: designed for arrays where the set of keys are very compact. They have a linear storage buffer that can be accessed very efficiently.
  2. Dictionary elements: designed for sparse arrays which don’t have every element inside of them. It is actually a has table and is more expensive to access than fast elements.

V8 has two compilers:

  1. A ”Full” Compiler that can generate good code for any JavaScript: good but not great JIT code. The goal of this compiler is to generate code quickly. To achieve this goal, it doesn’t do any type analysis and doesn’t know anything about types. Instead, it uses the IC or Inline Caches strategy to refine knowledge about types while the program runs. IC is very efficient and brings about a 20 times speed improvement.
  2. An Optimizing Compiler that produces great code for most of the JavaScript language. It comes later and re-compiles hot functions. The optimizing compiler takes types from the Inline Cache and makes decisions about how to optimize the code better. However, some language features are not supported yet like try/catch blocks. (The workaround for try/catch blocks is to write the “non stable” code into a function and to call the function in the try block).

Code optimization: V8 also supports de-optimization: the optimizing compiler makes optimistic assumptions from the Inline Cache about the different types, de-optimization comes if these assumptions are invalid. For example, if a hidden class generated was not the one expected, V8 throws away the optimized code and comes back to the Full Compiler to get types again from the Inline Cache. This process is slow and should be avoided by trying not to change functions after they are optimized.

This keyword: a special identifier keyword that’s automatically defined in the scope of every function. But what does it actually refer to?

Any sufficiently advanced technology is indistinguishable from magic.

— Arthur C. Clarke

What this is not:

  • It is not a reference to the function itself
  • It is not a reference to the function’s scope

This is not an author-time binding but a runtime binding. It is contextual based on the conditions of the function’s invocation. this binding has nothing to do with where a function is declared, but has instead everything to do with the manner in which the function is called.

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference, which will be used for the duration of the function’s execution.

call site: the location in code where a function is called (not where it’s declared).

Finding a call-sire is generally “go locate where a function is called from,” but it’s not always that easy, as certain coding patterns can obscure the true call-site.

The call-site we care about is in the invocation before the currently executing function.

The first rule we will examine comes from the most common case of function calls: standalone function invocation. Think of this this rule as the default catch-all rule when none of the other rules apply.

consider the following code:

function foo() {
console.log( this.a );
}
var a = 2;
foo(); //2

The first thing to note, if you were not already aware, is that variables declared in the global scope, as var var a = 2; is, are synonymous with global-object properties of the same name. They’re not copies of each other, they are each other. Think of it as two sides of the same coin.

Second, we see that when foo() is called, this.a resolves to our global variable a. Why? Because in this case, the default binding for this applies to the function call, and so points this at the global object.

How do we know that the default binding rule applies here? We examine the call-site to see how foo() is called. In our snippet, foo() is called with a plain, undecorated function reference. None of the other rules we will demonstrate will pally here, so the default binding applies instead.

If strict mode is in effect, the global object is not eligible for the default binding, so the this is instead set to undefined.

function foo() {
"use strict";

console.log( this.a );
}

var a = 2;

foo(); // TypeError: 'this' is 'undefined'

A subtle but important detail is that though the overall this binding rules are entirely based on the call-site, the global object is only eligible for the default binding if the contends of foo() are not running in strict mode; the strict mode state of the call-site of foo() is irrelevant:

Another rule to consider is whether the call-site has context object, also referred to as an owning or containing object, though these alternate terms could be slightly misleading.

Consider:

function foo() {
console.log( this.a );
}

var obj = {
a: 2,
foo: foo
};

obj.foo(); //2

First, notice the manner in which foo() is declared and then later added as a reference property onto obj. Regardless of whether foo() is initially declared on obj, or is added as a reference later (as this snippet shows), in neither case is the function really “owned” or “contained” by the obj object.

However, the call-site uses the obj context to reference the function, so you could say that the obj “owns” or “contains” the function reference at the time the function is called.

When there is a context object for a function reference, the implicit binding rule says that it’s that object that should be used for the function call’s this binding. Because obj is the this for the foo() call, this.a is synonymous with obj.a.

Only the top/last level of an object property reference chain matter to the call-site.

Implicitly Lost

One of the most common frustrations that this binding creates is when an implicitly bound function loses that binding, which usually means it falls back to the default binding of either the global object or undefined, depending on strict mode.

Consider:

function foo() {
console.log( this.a );
}

var obj = {
a: 2,
foo: foo
};

var bar = obj.foo; // function reference/alias!

var a = "oops, global"; // `a` also property on global object

bar(); // "oops, global"

Even though bar appears to be a reference to obj.foo, in fact, it’s really just another reference to foo itself. Moreover, the call-site is what matters, and the call-site is bar(), which is a plain, undecorated call, and thus the default binding applies.

The more subtle, more common, and more unexpected way this occurs is when we consider passing a callback function:

function foo() {
console.log( this.a );
}

function doFoo(fn) {
// `fn` is just another reference to `foo`

fn(); // <-- call-site!
}

var obj = {
a: 2,
foo: foo
};

car a = "oops, global"; // `a` also property on global object

doFoo( obj.foo ); // "oops, global"

Parameter passing is just an implicit assignment, and since we’re passing a function, it’s an implicit reference assignment, so the end result is the same as the precious snippet.

What if the function you’re passing your callback to is not your own, but built into the language? No difference. Same outcome.

It’s quite common that our function callbacks lose their this binding, as we’ve just seen. But another way that this can surprise us is when the function we’ve passed our callback to intentionally changes the this for the call. Event handlers in popular JavaScript libraries are quite fond of forcing your callback to have a this that points to, for instance, the DOM element that triggered the event. While that may sometimes be useful, other times it can be downright infuriating. Unfortunately, these tools rarely let you choose.

Either way the this is changed unexpectedly, you are not really in control of how your callback function reference will be executed, so you have no way (yet) of controlling the call-site to give your intended binding. We’ll see shortly a way of “fixing” that problem by fixing the this.

With implicit binding, as we just saw, we had to mutate the object in question to include a reference on itself to the function, and use this property function reference to indirectly (implicitly) bind this to the object.

But what if you want to force a function call to use a particular object for the this binding, without putting a property function reference on the object?

“all” functions in the language have some utilities available to them (via their [[prototype]] — more on that later), which can be useful for this task. Specifically, functions have call(…) and apply(…) methods. Technically, JavaScript host environments sometimes provide functions that are special enough (a kind way of putting it!) that they do not have such functionality. But those are few. The vast majority of functions provided, and certainly all functions you will create, do have access to call(…) and apply(…).

How do these utilities work? They both take, as their first parameter, an object to use for the this, and then invoke the function with that this specified. Since you are directly stating what you want the this to be, we call it explicit binding.

Consider:

function foo() {
console.log( this.a );
}

var obj = {
a: 2
};

foo.call( obj ); //2

Invoking foo with explicit binding by foo.call(...) allows us to force its this to be obj.

If you pass a simple primitive value (of type string, boolean, or number) as the this binding, the primitive value is wrapped in its object-form (new String(..), new Boolean(…), or new Number(…), respectively). This is ofter referred to as “boxing.”

Note: call and apply behave identically with respect to this, the only difference is that additional arguments passed to the function are passed to call as additional arguments to it (method.call( the_this, arg1, arg2, arg3)), and with apply, the additional arguments to pass to the method are passed to apply in the form of an array (method.apply( the_this, [arg1, arg2, arg3, ...])).

Unfortunately, explicit binding alone still doesn’t offer any solution to the issue mentioned previously, of a function “losing” its intended this binding, or just having it paved over by a framework, etc.

Hard Binding

But a variation pattern around explicit binding actually does the trick. Consider:

function foo() {
console.log( this.a );
}

var obj = {
a: 2
};

var bar = function() {
foo.call( obj );
};

bar(); // 2
setTimeout( bar, 100 ); // 2

// hard-bound `bar` can no longer have its `this` overridden
bar.call( window ); // 2

Let’s examine how this variation works. We create a function bar() which, internally, manually calls foo.call(obj), thereby forcibly invoking foo with obj binding for this. No matter how you later invoke the function bar, it will always manually invoke foo with obj. This binding is both explicit and strong, so we call it hard binding.

I like coding, philosophy, and coffee. 👋