Javascript
Javascript guidelines are still a work in progress. But we already agree on some small rules.
Conventions
To keep things clean and smoothen out most points of confusion that may cause for frustrations and friction, there are some industry standard conventions we try to apply to our codebases. These are especially important for our kits as they will be reused throughout all our projects.
General
File structure
Files should be structured in folders according to their type
js/
|
|-- components/
|-- factories/
|-- managers/
|-- utils/
|-- MainApp.js
- components: Contains the component classes, classes that represent and manage a DOM element.
- factories: Contains the factory functions, functions that create and manage instances of other classes or generate new elements and data structures.
- managers: Contains the manager classes, classes that add logic to the application purely in javascript
- utils: Contains the utility functions, single function collections that add functionality to the application or website purely in javascript
- MainApp.js: The main application class, the entry point of the application. This is the class that extends from the
Appclass and is used to manage the lifecycle of the application.
Naming conventions
Following certain naming conventions helps to keep the developer working on the code informed about the type of variable they are working with.
Files should be named according to their purpose.
- Simple variable names (e.g. strings, numbers, booleans, etc.) should be written in camelCase.
- Object instances (aside from Object literals) should be written in PascalCase.
- Static variables (e.g. constants, enums, etc.) should be written in UPPERCASE.
- Boolean variables should always be prefixed with is- or has- depending on their context. It may be allowed to deviate from this rule, but these situations should be limited to an absolute minimum.
The
is-prefix is used to indicate a state/mode of the subject the boolean tries to describe.The
has-prefix is used to indicate a presence of something inside the subject the boolean tries to describe.✅ Do this ❎ Don't do this var isVisible = truevar visible = truevar hasError = falsevar error = falsevar isLoading = truevar loading = truevar hasItems = truevar items = truevar isEnabled = truevar enabled = truevar isOpen = truevar open = truevar hasItems = truevar items = true
Comparisons == vs ===
Always use === for comparisons.
if (value === otherValue) {
// do something
}
The difference between == and === in JavaScript lies in how they compare values:
==is the loose equality operator. It compares two values for equality, but performs type coercion if the types are different. This means it tries to convert one or both values to a common type before comparing. As a result,==can lead to unexpected results, such as:0 == '0' // true false == 0 // true null == undefined // true '123' == 123 // true===is the strict equality operator. It checks both value and type without performing any type conversion. Both sides must have the same type and value to be considered equal. For example:0 === '0' // false false === 0 // false null === undefined // false '123' === 123 // false 1 === 1 // true
Why use ===? Using === is strongly preferred because it enforces more predictable and reliable comparisons, making your code less error-prone and easier to debug. The == operator can lead to subtle bugs caused by implicit type conversion, which can be hard to spot and fix.
In summary: always use === unless you have a specific reason to use == and understand its behavior completely.
DOM variables
Variables that contain a reference to a Element or NodeList should be prefixed with a dollar ($) sign. This informs the reader about the type of content the variable contains.
Arrays of DOM elements that are not NodeLists don't have a dollar sign since the actual variable is not a DOM related object, it is an Array containing DOM related objects.
// pure js variable
const notElement = 'value';
// DOM related variables
const $element = document.querySelector('.my-element'); // DOM Element
const $elements = document.querySelectorAll('.my-elements'); // NodeList
// pure variable that happens to contain Dom Variables as children.
const elements = Array.from($elements);
OOP Conventions
Quick set of rules for OOP:
- Classes should be named in PascalCase
- Filename and default exported class should be the same name.
- Multiple classes allowed in single file, but only if for internal use. External use should be discouraged (unless put in separate files and exported as default).
- Constructor parameters should be passed as single object.
General class structure
export default class MyClass {
// constructor
constructor (args = {}) {
// constructor logic
}
// methods
//...
// getters & setters
//...
}
// utilities
//...
// event listeners
//...
Private vs Public properties
When working with classes it is up to the developer of the class to make a distinction in which properties are considered private and which are considered public. Private properties attached to the scope of the class should be prefixed with an underscore (_) following the convention of other object oriented programming languages.
An underscore means that the property should not be accessed outside of the class instance. If in any case the property value should be readable but not writable, expose the private variable through a getter of the same name without the underscore. This way it can be seen but not overwritten.
Position getters & setters at the bottom of the class definition.
export default class MyClass {
// constructor
constructor (args = {}) {
this.publicProperty = '';
this._privateProperty = '';
}
// methods
//...
// getters & setters
get privateProperty() {
return this._privateProperty;
}
set privateProperty(value) {
if (value === this._privateProperty) return;
this._privateProperty = value;
}
}
The current spec provides a way to apply private properties to a class instance by using the hashtag (#). But this poses some problems when using proxies, something Vue relies upon heavily (well explained by Lea Verou).
Check value before setting private property
Check the value before setting the private property to avoid unnecessary updates following the change made by the setter (unnecessary dom updates, re-renders, confusing events, etc.)
Constructor
Constructor parameters are passed as an object. This way there is no required order of passing values and no need to explicitly set null values when a parameter needs to be skipped.
export default class MyClass {
// constructor
constructor (args = {}) {
this.prop1 = args.prop1;
this._prop2 = args.prop2 ?? 'default value';
}
}
Classes that manage a DOM element are required to put the element in a $el property of the class instance.
export default class MyClass {
// constructor
constructor (args = {}) {
// put main DOM element in a private $el property of the class instance
this._$el = args.$el;
}
}
Extend from Component class to manage DOM elements
Extend from the Component class to manage DOM elements. Check the Component class for more information.
Private / Utility methods
Private and utility methods should be kept privately in the file, do not expose them to the outside world. These methods are not part of the public API of the class and should not be called from outside the class.
export default class MyClass {
// constructor
constructor (args = {}) {
privateMethod.call(this);
}
}
// util
function privateMethod() {
console.log('private method');
}
Use call to keep the scope of the method
Use call to keep the scope of the method when calling it from within the class. This way the method will have access to the class instance and its properties.
Event handling
Listeners to events should be added to the bottom of the file and its logic should be kept to a minimum. Best practice is to use these methods to call other methods depending on type of event and or passed down data.
export default class MyClass {
// constructor
constructor (args = {}) {
this._$button = args.$button;
this._$button.addEventListener('onButtonClick', onButtonClick.bind(this));
}
// methods
refresh() {
// refresh logic
}
// getters & setters
//...
}
// util
//...
// event listeners
function onButtonClick() {
this.refresh();
}
Use bind to keep the scope of the method
Use bind to keep the scope of the method when calling it from within the class. Else functions called outside the class definition loose context (this will point to the global object).
Extend from EventDispatcher
Extend from EventDispatcher to automatically provide the class with event management out of the box. Check the EventDispatcher class for more information.
EventDispatcher comes with a BoundListenerMgr instance at this._blm that can be used to manage the listeners added to the class instance. Check the BoundListenerMgr class for more information.
import EventDispatcher from '@bbc/front-end-kit/js/managers/EventDispatcher';
export default class MyClass extends EventDispatcher {
// constructor
constructor (args = {}) {
super();
this._$button = args.$button;
this._$button.addEventListener('onButtonClick', this._blm.add('onButtonClick', onButtonClick));
}
// methods
refresh() {
// refresh logic
}
destroy () {
this._$button.removeEventListener('onButtonClick', this._blm.remove('onButtonClick'));
}
}
// event listeners
function onButtonClick(e) {
e.preventDefault();
this.refresh();
}
Extending classes
When extending a class, make sure to call the parent constructor and pass down the arguments.
export default class MyClass extends ParentClass {
// constructor
constructor (args = {}) {
// modify arguments before passing them to the parent constructor
// setting a default value if custom property is not passed (possibly overriding the default value set in the parent class)
args.customProperty = args.customProperty ?? 'default value';
// pass down the arguments to the parent constructor
super(args);
// custom property now possibly processed by parent class(es)
}
// methods
//...
// getters & setters
//...
}
Call parent constructor
Call the parent constructor and pass down the arguments to ensure the parent class is initialized correctly.
Legacy
Older projects will not follow these rules yet. Try to understand the coding standards followed there and apply them only on that project.