The xPack eXtensible Component Definition Language framework
Last modified on Mon Mar 27 19:46:14 2017 UTC.
Contents |
After many years dealing with style guides for various languages, the conclusion is that the style itself is less important than applying it consistently.
So, for the xPack JavaScript source files to be consistent, the first requirement is to pass the Standard JS validation tools.
After this, the main recommendations are:
The main specifications to be followed are those of ES 6; they override all other older specifications and style guides.
Use const
by default, and only use let
when you know a variable’s value needs to change
Use default parameters.
const makeRequest = function (url, timeout = 2000, callback = function() {}) {
// the rest of the function
}
const add = function (first, second = getValue(first)) {
return first + second
}
Use rest parameters.
const pick = function (object, ...keys) {
let result = Object.create(null)
for (let i = 0, len = keys.length; i < len; i++) {
result[keys[i]] = object[keys[i]]
}
return result
}
The Function
constructor.
var add = new Function("first", 'second = first',
'return first + second')
console.log(add(1, 1)) // 2
console.log(add(1)) // 2
JavaScript has two different internal-only methods for functions: [[Call]]
and [[Construct]]
. When a function is called without new, the [[Call]]
method is executed, which executes the body of the function as it appears in the code. When a function is called with new, that’s when the [[Construct]]
method
is called. The [[Construct]]
method is responsible for creating a new object, called the instance, and then executing the function body with this set to the instance. Functions that have a [[Construct]]
method are called constructors.
const Person = function (name) {
if (this instanceof Person) {
this.name = name
} else {
throw new Error('You must use new with Person.')
}
}
var person = new Person('Nicholas')
var notAPerson = Person.call(person, 'Michael') // works!
Block-level functions
'use strict'
if (true) {
console.log(typeof doSomething) // throws an error
let doSomething = function () {
// empty
}
doSomething()
}
console.log(typeof doSomething)
Arrow functions are functions defined with a new syntax that uses an arrow (=>
)
this
, super
, arguments, and new.target inside the function are defined by the closest containing non-arrow functionlet sum = (num1, num2) => num1 + num2
// effectively equivalent to:
let sum = function(num1, num2) {
return num1 + num2
};
It’s now possible to modify an object’s prototype after it’s been created thanks to ECMAScript 6’s Object.setPrototypeOf()
method.
In addition, you can use the super
keyword to call methods on an object’s prototype. The this
binding inside a method invoked using super
is set up to automatically work with the current value of this
.
As the name implies, some of them are funny, but some are still useful.
Keep lines shorter than 80 characters
Indentation is two spaces
Curly braces belong on the same line
const f = function () {
while (foo) {
bar()
}
}
Don’t use semicolons, except when required; for example to prevent the expression from being interpreted as a function call or property access, respectively.
;(x || y).doSomething()
;[a, b, c].forEach(doSomething)
Put the comma at the start of the next line, directly below the token that starts the list
const magicWords = [ 'abracadabra'
, 'gesundheit'
, 'ventrilo'
]
, spells = { 'fireball' : function () { setOnFire() }
, 'water' : function () { putOut() }
}
, a = 1
, b = 'abc'
, etc
, somethingElse
Use single quotes for strings except to avoid escaping
const ok = 'String contains "double" quotes'
const alsoOk = "String contains 'single' quotes or apostrophe"
const paramOk = `Back quotes string with ${parameter}`
Put a single space in front of (
for anything other than a function call
Use named functions.
Error
object with your message (new Error('msg')
)npmlog
utilitytrue
or false
null
undefined
For the native Node.js callback usage, never to ever ever throw anything. It’s worse than useless. Just send the error message back as the first argument to the callback.
But for the modern ES 6 promise usage, exceptions are fine.
Modules are a way of preventing multiple JavaScript units to polute the global namespace.
Objects defined at root level in a module are not global, but belong to the module; the usual name for this is module-global.
Modules are cached after the first time they are loaded. This means (among other things) that every call to require(‘foo’) will get exactly the same object returned, if it would resolve to the same file. Multiple calls to require('foo')
will not cause the module code to be executed multiple times.
From this point of view, modules behave like singletons; they can also be compared to static classes in C++.
Leaving status at the module level can be either a blessing or a curse, depending on the environment used to run the module. In server environments, using module-global variables is like using static variables in a multi-threaded environment, if not handled correctly it may have unexpected results.
To make some functions and objects visible outside the module, you can add them as properties to the special modules.exports
object:
const PI = Math.PI
module.exports.area = (r) => PI * r * r
module.exports.circumference = (r) => 2 * PI * r
Although you can rewrite the module.export
to be a sinle function (such as a constructor), still prefer to add them as properties to the object and refer to them explicitly in the require()
line:
const square = require('./square.js').square
const mySquare = square(2)
console.log(`The area of my square is ${mySquare.area()}`)
module.exports.area = (width) => {
return {
area: () => width * width
}
}
If you want to export a complete object in one assignment instead of building it one property at a time, assign it to module.exports
.
When a file is run directly from Node.js, require.main
is set to its module. That means that you can determine whether a file has been run directly by testing
require.main === module
Before a module’s code is executed, Node.js will wrap it with a function wrapper that looks like the following:
module = ... // an object for the current module
module.export = {} // an empty object
exports = module.export // a reference to the exports; avoid using it
__filename = '/x/y/z/abc.js'
__dirname = '/x/y/z'
(function (exports, require, module, __filename, __dirname) {
// Your module code actually lives in here
});
In each module, the module
variable is a reference to the object representing the current module. module
isn’t actually a global but rather local to each module.
The module.exports
object is created by the Module system. Sometimes this is not acceptable; many want their module to be an object of their own. To do this, assign the desired export object to module.exports
.
For convenience, module.exports
is also accessible via the exports
module-global. Note that assigning a value to exports
will simply rebind the local exports variable, which is probably not what you want to do; if the relationship between exports
and module.exports
seems like magic to you, ignore exports
and only use module.exports
.
Note that assignment to module.exports
must be done immediately. It cannot be done in any callbacks.
These are really objects, available in all modules. (see Node.js Globals)
global
process
console
console.log('msg')
- writes ‘msg’ to stdoutconsole.warn('msg')
- writes ‘msg’ to stderrconsole.error('msg')
- writes ‘Error: msg’ to stderrconsole.assert(value, 'msg')
This is Rule no. 1, that overrides all other rules. Definitely avoid using old style code.
Even if the new syntax is mostly syntactic sugar, and internally things behave as strangly as they did in the first JavaScript versions, still use the new class syntax at large; it is much cleaner and improves readability.
Really. No callbacks at all. Use promises.
Once async
/await
became standard, and the V8 engine added support for them, there is no reason for not using async
/await
.
Wrap old legacy code using callbacks into promises and execute them with await
.
Modules are singletons; using module variables is like using static variables in a multi-threaded environment; they may provide a way of sharing common data between multiple instances of objects created inside the same module, but if not handled correctly this may have unexpected results.
The general recommendation is to make the modules reentrant. In practical terms, do not use module-global variables at all; make the module export a class, and create instances of it whenever needed; for sharing data between instances, use static class members.
Bad style:
module.exports = function () {
return /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
};
...
const func = require('name')
Apart from being unnamed, returning a single function prevents future extensions, for example exporting a second function from the same module would mandate all modules that use the first function via require()
to be updated to require().func1
, which may cause many headaches to developers.
module.exports.func1 = function () {
return /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
};
module.exports.func2 = function () { ... }
...
const func = require('name').func1
The recommendation is to always return functions or preferably classes as properties of the module.exports
object, and get them individually by name.
Prepare your module to export multiple functions; group them (by functionality) either below a parent object, or, even better, in classes with static members.
The main advantage of this scheme is that adding new exports will only change the interface incrementaly, minimising the risk to break backward compatibility.
Assuming classes are prefered, the EC6 syntax for export/import would look like:
export class WscriptAvoider { ... }
...
import { WscriptAvoider } from 'wscript-avoider.js'
So, to stay close to this syntax, the recommendation is to preserve the original module.exports
object, and add properties to it, preferably classes, even if they have only static members.
To import them, the syntax uses the explicit classs name:
const WscriptAvoider = require('wscript-avoider').WscriptAvoider
WscriptAvoider.quitIfWscript(appName)
Cases like import { WscriptAvoider as Xyz } from 'wscript-avoider.js'
would be naturaly represented as:
const Xyz = require('wscript-avoider').WscriptAvoider
Xyz.quitIfWscript(appName)
In case the class is not static, instantiate it as usual.
Prefered:
Other links:
Prefered (used automatically by Standard):
Other links: