A mostly reasonable approach to JavaScript, inspired by the Airbnb JavaScript Style Guide.
The end-user experience always come first which means performance should always be top-of-mind. The JSPerf examples used in this guide do not use large datasets. If you find yourself working with large datasets and the suggested approach based on this guide performs slower, don't be afraid to push back on a per-project basis (see Performance vs Readability).
Use const for all of your references; avoid using var.
Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.
🚫 Nope. 🚫
vara=1;varb=2;🎉 Yep! 🎉
consta=1;constb=2;- ESLint:
If you must reassign references, use let instead of var.
Why?
letis block-scoped rather than function-scoped likevar. Function-scoped variables are hoisted which can lead to bugs if you are not careful. Using block-scoped variables makes our code more predictable by giving the variable an explicit scope.
🚫 Nope. 🚫
varcount=1;if(true){count+=1;}🎉 Yep! 🎉
letcount=1;if(true){count+=1;}- ESLint: no-var
Note that both let and const are block-scoped.
// Both `const` and `let` only exist in the blocks they are defined in.{leta=1;constb=1;}console.log(a);// ReferenceError: a is not definedconsole.log(b);// ReferenceError: b is not definedUse the literal syntax for object creation.
Why? While there are no performance differences between the two approaches, the byte savings and conciseness of the object literal form is what has made it the de facto way of creating new objects.
🚫 Nope. 🚫
constitem=newObject();🎉 Yep! 🎉
constitem={};- ESLint: no-new-object
Use object shorthand syntax for both methods and property values.
Why? ECMAScript 6 provides a concise form for defining object literal methods and properties. This syntax can make defining complex object literals much cleaner.
🚫 Nope. 🚫
constatom={value: 1,addValue: function(value){returnatom.value+value;},};🎉 Yep! 🎉
constatom={value: 1,addValue(value){returnatom.value+value;},};🚫 Nope. 🚫
constgeneralLeiaOrgana='General Leia Organa';constobj={generalLeiaOrgana: generalLeiaOrgana,};🎉 Yep! 🎉
constgeneralLeiaOrgana='General Leia Organa';constobj={
generalLeiaOrgana,};- ESLint: object-shorthand
Only quote properties that are invalid identifiers.
Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.
🚫 Nope. 🚫
constobj={'foo': 3,'bar': 4,'data-blah': 5,};🎉 Yep! 🎉
constobj={foo: 3,bar: 4,'data-blah': 5,};- ESLint: quote-props
Do not call Object.prototype methods directly, such as hasOwnProperty, propertyIsEnumerable, and isPrototypeOf.
Why? In ECMAScript 5.1,
Object.createwas added, which enables the creation of objects with a specified[[Prototype]].Object.create(null)is a common pattern used to create objects that will be used as a Map. This can lead to errors when it is assumed that objects will have properties fromObject.prototype.
🚫 Nope. 🚫
console.log(object.hasOwnProperty(key));🎉 Yep! 🎉
// Goodconsole.log(Object.prototype.hasOwnProperty.call(object,key));// Bestconsthas=Object.prototype.hasOwnProperty;// cache the lookup once, in module scope.console.log(has.call(object,key));- ESLint: no-prototype-builtins
Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.
Why? Object spread is a declarative alternative which may perform better than the more dynamic, imperative Object.assign.
🚫 Nope. 🚫
// This mutates `original` ಠ_ಠconstoriginal={a: 1,b: 2};constcopy=Object.assign(original,{c: 3});deletecopy.a;// So does this!// Works but not preferredconstoriginal={a: 1,b: 2};constcopy=Object.assign({},original,{c: 3});// copy => { a: 1, b: 2, c: 3 }🎉 Yep! 🎉
constoriginal={a: 1,b: 2};constcopy={ ...original,c: 3};// copy => { a: 1, b: 2, c: 3 }const{ a, ...noA}=copy;// noA => { b: 2, c: 3 }- ESLint: prefer-object-spread
- JSPerf: Shallow Copy Objects
Use the literal syntax for array creation.
Why? Use of the
Arrayconstructor to construct a new array is generally discouraged in favor of array literal notation because of the single-argument pitfall and because theArrayglobal may be redefined.
🚫 Nope. 🚫
constitems=newArray();🎉 Yep! 🎉
constitems=[];- ESLint: no-array-constructor
Use Array.prototype.push() instead of direct assignment to add items to an array.
🚫 Nope. 🚫
constsomeStack=[];someStack[someStack.length]='abracadabra';🎉 Yep! 🎉
constsomeStack=[];someStack.push('abracadabra');- JSPerf: Adding Array Items
Use array spread syntax... to shallow-copy arrays.
Why? Better overall performance.
🚫 Nope. 🚫
// Too slowconstanimals=['ant','bison','camel','duck','elephant'];constlen=animals.length;constanimalsCopy=[];leti;for(i=0;i<len;i++){animalsCopy[i]=animals[i];}// Works but is not preferredconstanimals=['ant','bison','camel','duck','elephant'];constanimalsCopy=animals.slice();🎉 Yep! 🎉
constanimals=['ant','bison','camel','duck','elephant'];constanimalsCopy=[...animals];- JSPerf: Shallow Copy Arrays
To convert an iterable object (e.g. NodeList) to an array, use array spread syntax... instead of Array.from.
Why? Better performance.
🚫 Nope. 🚫
constparagraphs=document.querySelectorAll('p');constnodes=Array.from(paragraphs);🎉 Yep! 🎉
constparagraphs=document.querySelectorAll('p');constnodes=[...paragraphs];If using Babel with @babel/preset-env with option loose:true, and are transpiling to older targets in a .browserlistrc, you may need to add the following to your Babel config (e.g., babel.config.js):
plugins: [
...,'@babel/plugin-transform-spread',
...
](The default option for this plugin is loose:false, which will override the global setting)
- ESLint: prefer-spread
- JSPerf: Arrays From Iterables
Use Array.from for converting an array-like object to an array.
Why? Not only is it easier to read/type but it also performs better.
🚫 Nope. 🚫
constarrLike={0: 'foo',1: 'bar',2: 'baz',length: 3};constarr=Array.prototype.slice.call(arrLike);🎉 Yep! 🎉
constarrLike={0: 'foo',1: 'bar',2: 'baz',length: 3};constarr=Array.from(arrLike);- JSPerf: Arrays from Array-Like Objects
Use array spread syntax... instead of Array.from for mapping over iterables.
Why? Overall better performance.
🚫 Nope. 🚫
constiterable='Hello there!';constupperCase=letter=>letter.toUpperCase();constupperCaseLetters=Array.from(iterable,upperCase);🎉 Yep! 🎉
constiterable='Hello there!';constupperCase=letter=>letter.toUpperCase();constupperCaseLetters=[...iterable].map(upperCase);- JSPerf: Mapping Over Iterables
Use return statements in array method callbacks. It’s okay to omit the return if the function body consists of a single statement returning an expression without side effects.
🚫 Nope. 🚫
inbox.filter(msg=>{const{ subject, author }=msg;if(subject==='Mockingbird'){returnauthor==='Harper Lee';}else{returnfalse;}});🎉 Yep! 🎉
inbox.filter(msg=>{const{ subject, author }=msg;if(subject==='Mockingbird'){returnauthor==='Harper Lee';}returnfalse;});🎉 Also good! 🎉
[1,2,3].map((x)=>{consty=x+1;returnx*y;});// The return can be omitted here.[1,2,3].map(x=>x+1);- ESLint: array-callback-return
Use object destructuring when accessing and using multiple properties of an object.
Why? Destructuring saves you from creating temporary references for those properties.
🚫 Nope. 🚫
functiongetFullName(user){constfirstName=user.firstName;constlastName=user.lastName;return`${firstName}${lastName}`;}🎉 Yep! 🎉
// GoodfunctiongetFullName(user){const{ firstName, lastName }=user;return`${firstName}${lastName}`;}// BestfunctiongetFullName({ firstName, lastName }){return`${firstName}${lastName}`;}- ESLint: prefer-destructuring
- JSPerf: Object Destructuring vs Not
- MDN Web Docs: Object Destructuring
How you destructure an array depends on your situation. Below are a couple of ways to complete the same task.
// This works!constarr=[1,2,3,4];constfirst=arr[0];constsecond=arr[1];constrest=arr.slice(2);console.log(first);// 1console.log(second);// 2console.log(rest);// [3, 4]// This works great also!constarr=[1,2,3,4];const[first,second, ...rest]=arr;console.log(first);// 1console.log(second);// 2console.log(rest);// [3, 4]Note: For performance reasons, strongly consider use of the @babel/plugin-transform-destructuring plugin when using array destructuring.
- Babel Plugin: @babel/plugin-transform-destructuring
- JSPerf: Array Destructuring vs Not
- MDN Web Docs: Array Destructuring
Use object destructuring for multiple return values, not array destructuring.
Why? You can add new properties over time or change the order of things without breaking call sites.
🚫 Nope. 🚫
functionprocessInput(input){return[left,right,top,bottom];}// the caller needs to think about the order of return dataconst[left,__,top]=processInput(input);🎉 Yep! 🎉
functionprocessInput(input){return{ left, right, top, bottom };}// the caller selects only the data they needconst{ left, top }=processInput(input);Use single quotes '' for strings. The exception is if a string includes a literal ' single quote, use double quotes " instead.
🚫 Nope. 🚫
// Should be single quote.constname="Cloud Four";// Template literals should contain interpolation or newlines.constname=`Cloud Four`;// This string has a literal single quote!constfoo='What\'s for dinner?';🎉 Yep! 🎉
constname='Cloud Four';// It's okay to use double quotes here.constfoo="What's for dinner?";- ESLint: quotes
When programmatically building up strings, use template literals instead of concatenation.
Why? Template literals (template strings) give you a readable, concise syntax with proper newlines and string interpolation features.
🚫 Nope. 🚫
functionsayHi(name){return'How are you, '+name+'?';}functionsayHi(name){return['How are you, ',name,'?'].join();}🎉 Yep! 🎉
functionsayHi(name){return`How are you, ${name}?`;}- ESLint: prefer-template
Never use eval() on a string, it opens too many vulnerabilities.
- ESLint: no-eval
Although it is possible to call functions before they are defined via hoisting we prefer to avoid this pattern in our code as it can be confusing.
Although this code works properly it may be more confusing and we generally avoid it:
logFoo();functionlogFoo(){console.log("foo");}We would prefer one of the following patterns:
// Define the function before callingfunctionlogFoo(){console.log("foo");}logFoo();// Import the functionimport{logFoo}from'./log-foo.js';logFoo();In files that call a number of helper functions it can be helpful to move those functions to modules, or start the file with a main function that provides a summary of the steps taken in the file. For example:
// The `main` function contains an overview of the file's logic.// (`main` could be switched to a more meaningful name in context.)functionmain(){thing1();thing2();thing3();thing4();}functionthing1(){}functionthing2(){}functionthing3(){}functionthing4(){}main();Another option is to move helper functions to modules:
// Import helpers so they're defined up frontimport{thing1,thing2,thing3,thing4}from'./helpers.js';thing1();thing2();thing3();thing4();Never name a function parameter arguments.
Why? This will take precedence over the
argumentsobject that is given to every function scope.
🚫 Nope. 🚫
functionfoo(name,options,arguments){// ...}🎉 Yep! 🎉
functionfoo(name,options,args){// ...}Use the rest syntax ...args instead of the arguments object.
Why? Rest arguments are a real Array, and not merely Array-like as the
argumentsobject is.
🚫 Nope. 🚫
functionconcatenateAll(){constargs=Array.prototype.slice.call(arguments);returnargs.join('');}// Slow performancefunctionconcatenateAll(){constargs=Array.from(arguments);returnargs.join('');}🎉 Yep! 🎉
functionconcatenateAll(...args){returnargs.join('');}- ESLint: prefer-rest-params
Use function default parameter syntax rather than mutating function arguments.
🚫 Nope. 🚫
functiondoThings(opts){// If opts is falsey it can introduce bugs.opts=opts||{};// ...}functiondoThings(opts){if(opts===undefined){opts={};}// ...}🎉 Yep! 🎉
functiondoThings(opts={}){// ...}Avoid side effects with function default parameters.
Why? They are confusing to reason about.
🚫 Nope. 🚫
letb=1;// Eek!functioncount(a=b++){console.log(a);}count();// 1count();// 2count(3);// 3count();// 3Never use the Function constructor to create a new function.
Why? Creating a function in this way evaluates a string similarly to
eval(), which opens vulnerabilities.
🚫 Nope. 🚫
varadd=newFunction('a','b','return a + b');varsubtract=Function('a','b','return a - b');🎉 Yep! 🎉
varx=function(a,b){returna+b;};- ESLint: no-new-func
Never mutate function parameters.
Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.
🚫 Nope. 🚫
functionfoo(bar){bar=13;}functionfoo(bar){bar++;}🎉 Yep! 🎉
functionfoo(bar){varbaz=bar;}- ESLint: no-param-reassign
Prefer the use of the spread syntax operator ... to call variadic functions (a function that accepts a variable number of arguments).
Why? It’s cleaner, you don’t need to supply a context, and it's easier to compose
newwhen compared to usingapply.
🚫 Nope. 🚫
constargs=[1,2,3,4];Math.max.apply(Math,args);new(Function.prototype.bind.apply(Date,[null,2016,8,5]));🎉 Yep! 🎉
constargs=[1,2,3,4];Math.max(...args);newDate(...[2016,8,5]);- ESLint: prefer-spread
- JSPerf: Spread Syntax for Variadic Functions
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...
TBD...