This site has been retired. For up to date information, see handbook.gnome.org or gitlab.gnome.org.


[Home] [TitleIndex] [WordIndex

For updated documentation, please see the GJS repository.

Coding style

Our goal is to have all JavaScript code in GNOME follow a consistent style. In a dynamic language like JavaScript, it is essential to be rigorous about style (and unit tests), or you rapidly end up with a spaghetti-code mess.

Semicolons

JavaScript allows omitting semicolons at the end of lines, but don't. Always end statements with a semicolon.

js2-mode

If using Emacs, try js2-mode. It functions as a "lint" by highlighting missing semicolons and the like.

Imports

Use CamelCase when importing modules to distinguish them from ordinary variables, e.g.

const Big = imports.big;
const GLib = imports.gi.GLib;

Variable declaration

Always use one of "const", "var", or "let" when defining a variable. Always use "let" when block scope is intended; in particular, inside for() and while() loops, let is almost always correct.

// Iterating over an array
for (let i = 0; i < 10; i++) {
    let foo = bar(i);
}

// Iterating over an object's properties
for (let prop in someobj) {
    ...
}

If you don't use "let" then the variable is added to function scope, not the for loop block scope. See What's new in JavaScript 1.7

A common case where this matters is when you have a closure inside a loop:

for (let i = 0; i < 10; i++) {
    mainloop.idle_add(function() { log('number is: ' + i); });
}

If you used "var" instead of "let" it would print "10" a bunch of times.

Inside functions, "let" is always correct instead of "var" as far as we know. "var" is useful when you want to add something to the with() object, though... in particular we think you need "var" to define module variables, since our module system loads modules with the equivalent of "with (moduleObject)"

"this" in closures

"this" will not be captured in a closure; "this" is relative to how the closure is invoked, not to the value of this where the closure is created, because "this" is a keyword with a value passed in at function invocation time, it is not a variable that can be captured in closures.

To solve this, use Lang.bind, eg:

const Lang = imports.lang;

let closure = Lang.bind(this, function() { this._fnorbate() });

A more realistic example would be connecting to a signal on a method of a prototype:

const Lang = imports.lang;

MyPrototype = {
    _init: function() {
        fnorb.connect('frobate', Lang.bind(this, this._onFnorbFrobate));
    },

    _onFnorbFrobate: function(fnorb) {
        this._updateFnorb();
    },
};

Object literal syntax

JavaScript allows equivalently:

foo = { 'bar': 42 };
foo = { bar: 42 };

and

var b = foo['bar'];
var b = foo.bar;

If your usage of an object is like an object, then you're defining "member variables." For member variables, use the no-quotes no-brackets syntax, that is, { bar: 42 } and foo.bar.

If your usage of an object is like a hash table (and thus conceptually the keys can have special chars in them), don't use quotes, but use brackets, { bar: 42 } and foo['bar'].

Variable naming

Whitespace

JavaScript "classes"

Keep in mind that JavaScript does not "really" have classes in the sense of C++ or Java; you can't create new types beyond the built-in ones (Object, Array, RegExp, String). However, you can create object instances that share common properties, including methods, using the prototype mechanism.

Each JavaScript object has a property __proto__; if you write obj.foo and "foo" is not in "obj", JavaScript will look for "foo" in __proto__. If several objects have the same __proto__, then they can share methods or other state.

You can create objects with a constructor, which is a special function. Say you have:

function Foo() {}
let f = new Foo();

For "new Foo()" JavaScript will create a new, empty object; and execute Foo() with the new, empty object as "this". So the function Foo() sets up the new object.

"new Foo()" will also set __proto__ on the new object to Foo.prototype. The property "prototype" on a constructor is used to initialize __proto__ for objects the constructor creates. To get the right __proto__ on objects, we need the right prototype property on the constructor.

You could think of "f = new Foo()" as:

let f = {}; // create new object
f.__proto__ = Foo.prototype; // doing this by hand isn't actually allowed
Foo.call(f); // invoke Foo() with new object as "this"

Our pattern for writing classes is:

function Foo(arg1, arg2) {
    this._init(arg1, arg2);
}

Foo.prototype = {
    _init: function(arg1, arg2) {
        this._myPrivateInstanceVariable = arg1;
    },
    myMethod: function() {

    },
    myClassVariable: 42,
    myOtherClassVariable: 'Hello'
}

This pattern means that when you do "let f = new Foo()", f will be a new object, f.__proto__ will point to Foo.prototype, and Foo.prototype._init will be called to set up the object.

JavaScript "inheritance"

There are lots of ways to simulate "inheritance" in JavaScript. In general, it's a good idea to avoid class hierarchies in JavaScript. But sometimes it's the best solution.

Our preferred approach is to use a Spidermonkey-specific extension and directly set the __proto__ member of the subclass's prototype to point to the prototype of the base class. Looking up a property in the subclass starts with the properties of the instance. If the property isn't there, then the prototype chain is followed first to the subclass's prototype and then to the base class's prototype.

const Lang = imports.lang;

function Base(foo) {
    this._init(foo);
}

Base.prototype = {
    _init: function(foo) {
        this._foo = foo;
    },
    frobate: function() {
    }
};

function Sub(foo, bar) {
    this._init(foo, bar);
}

Sub.prototype = {
    __proto__: Base.prototype,

    _init: function(foo, bar) {
        // here is an example of how to "chain up"
        Base.prototype._init.call(this, foo);
        this._bar = bar;
    }

    // add a newMethod property in Sub.prototype
    newMethod: function() {
    }
}

In portable JavaScript code you'll often see a different technique used to get this prototype chain:

function Base(foo) ...

Base.prototype = ...

function Sub(foo, bar) ...

// Do NOT do this - do not use an instance of Base as the Sub prototype
Sub.prototype = new Base();

The problem with this pattern is that you might want to have side effects in the Base() constructor. Say Base() in its constructor creates a window on the screen, because Base() is a dialog class or something. If you use the pattern that some instances of Base() are just prototypes for subclasses, you'll get extra windows on the screen.

The other problem with this pattern is that it's just confusing and weird.

JavaScript attributes

Don't use the getter/setter syntax when getting and setting has side effects, that is, the code:

foo.bar = 10;

should not do anything other than save "10" as a property of foo. It's obfuscated otherwise; if the setting has side effects, it's better if it looks like a method.

In practice this means the only use of attributes is to create read-only properties:

get bar() {
    return this._bar;
}

If the property requires a setter, or if getting it has side effects, methods are probably clearer.


2024-10-23 10:59