The documentation below is the standard code style for all official Grind projects. Of course, there’s no requirement to use it in your own projects, but when contributing code to the Grind packages, please adhere to this standard.
You can find eslint settings for Grind at github.com/grindjs/eslint-config-grind.
The style guide has been ported and modified from the Airbnb JavaScript Style Guide to reflect Grind’s style.
Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.
// badvar a = 1var b = 2// goodconst a = 1const b = 2
Why?
let
is block-scoped rather than function-scoped likevar
.
// badvar count = 1if truecount += 1// good, use the let.let count = 1if truecount += 1
// const and let only exist in the blocks they are defined in.let a = 1const b = 1console // ReferenceErrorconsole // ReferenceError
// badconst item =// goodconst item = {}
Why? They allow you to define all the properties of an object in one place.
{return `a key named `}// badconst obj =id: 5name: 'San Francisco'obj = true// goodconst obj =id: 5name: 'San Francisco': true
// badconst atom =value: 1{return atomvalue + value}// goodconst atom =value: 1{return atomvalue + value}
Why? It is shorter to write and descriptive.
const lukeSkywalker = 'Luke Skywalker'// badconst obj =lukeSkywalker: lukeSkywalker// goodconst obj = lukeSkywalker
Why? It’s easier to tell which properties are using the shorthand.
const anakinSkywalker = 'Anakin Skywalker'const lukeSkywalker = 'Luke Skywalker'// badconst obj =episodeOne: 1twoJediWalkIntoACantina: 2lukeSkywalkerepisodeThree: 3mayTheFourth: 4anakinSkywalker// goodconst obj =lukeSkywalkeranakinSkywalkerepisodeOne: 1twoJediWalkIntoACantina: 2episodeThree: 3mayTheFourth: 4
Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.
// badconst bad ='foo': 3'bar': 4'data-blah': 5// goodconst good ='foo': 3'bar': 4'data-blah': 5
Why? These methods may be shadowed by properties on the object in question - consider
{ hasOwnProperty: false }
- or, the object may be a null object (Object.create(null)
).
// badconsole// goodconsole// bestconst has = ObjectprototypehasOwnProperty // cache the lookup once, in module scope.console
Object.assign
to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.
// very badconst original = a: 1 b: 2const copy = Object // this mutates `original` ಠ_ಠdelete copya // so does this// badconst original = a: 1 b: 2const copy = Object // copy => { a: 1, b: 2, c: 3 }// goodconst original = a: 1 b: 2const copy = ...original c: 3 // copy => { a: 1, b: 2, c: 3 }const a ...noA = copy // noA => { b: 2, c: 3 }
// badconst items =// goodconst items =
Array#push instead of direct assignment to add items to an array.
const someStack =// badsomeStacksomeStacklength = 'abracadabra'// goodsomeStack
// very badconst len = itemslengthconst itemsCopy =let ifor i = 0; i < len; i += 1itemsCopyi = itemsi// badconst itemsCopy = items// goodconst itemsCopy = ...items
Array.from.
const foo = document// goodconst nodes = Array// bestconst nodes = ...foo
// badconst baz = ...foo// goodconst baz = Array
// good;1 2 3// good1 2 3// bad - no returned value means `memo` becomes undefined after the first iterationconst flat = {}0 1 2 3 4 5// goodconst flat = {}0 1 2 3 4 5// badinbox// goodinbox
// badconst arr =0 12 34 5const objectInArray =id: 1id: 2const numberInArray = 1 2// goodconst arr =0 12 34 5const objectInArray =id: 1id: 2const numberInArray = 1 2const numberInArray = 1 2
Why? Destructuring saves you from creating temporary references for those properties.
// bad{const firstName = userfirstNameconst lastName = userlastNamereturn ` `}// good{const firstName lastName = userreturn ` `}// best{return ` `}
const arr = 1 2 3 4// badconst first = arr0const second = arr1// goodconst first second = arr
Why? You can add new properties over time or change the order of things without breaking call sites.
// bad{// then a miracle occursreturn left right top bottom}// the caller needs to think about the order of return dataconst left __ top =// good{// then a miracle occursreturn left right top bottom}// the caller selects only the data they needconst left top =
// badconst name = 'Capt. Janeway'// bad - template literals should contain interpolation or newlinesconst name = `Capt. Janeway`// goodconst name = 'Capt. Janeway'
Why? Broken strings are painful to work with and make code less searchable.
// badconst errorMessage ='This is a super long error that was thrown because \of Batman. When you stop to think about how Batman had anything to do \with this, you would get nowhere \fast.'// badconst errorMessage ='This is a super long error that was thrown because ' +'of Batman. When you stop to think about how Batman had anything to do ' +'with this, you would get nowhere fast.'// goodconst errorMessage ='This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'
Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.
// bad{return 'How are you, ' + name + '?'}// bad{return 'How are you, ' name '?'}// bad{return `How are you, ?`}// good{return `How are you, ?`}
Why? Backslashes harm readability, thus they should only be present when necessary.
// badconst foo = '\'this\' is "quoted"'// goodconst foo = '\'this\' is "quoted"'const foo = `my name is ''`
Why? An immediately invoked function expression is a single unit - wrapping both it, and its invocation parens, in parens, cleanly expresses this. Note that in a world with modules everywhere, you almost never need an IIFE.
// immediately-invoked function expression (IIFE); {console}
ECMA-262 defines a
block
as a list of statements. A function declaration is not a statement. Read ECMA-262’s note on this issue.
// badif currentUser{console}// goodlet testif currentUserconsole
// bad{// ...}// good{// ...}
Why?
...
is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like likearguments
.
// bad{const args = Arrayprototypeslicereturn args}// good{return args}
// really bad{// No! We shouldn’t mutate function arguments.// Double bad: if opts is falsy it'll be set to an object which may// be what you want but it can introduce subtle bugs.opts = opts || {}// ...}// still bad{if opts === void 0opts = {}// ...}// good{// ...}
Why? They are confusing to reason about.
var b = 1// bad{console}// 1// 2// 3// 3
// bad{// ...}// good{// ...}
Why? Creating a function in this way evaluates a string similarly to eval(), which opens vulnerabilities.
// badconst add = 'a' 'b' 'return a + b'// still badconst subtract = Function'a' 'b' 'return a - b'
// badconst f = {}const g = {}const h = {}// goodconst x = {}const y = {}
Why? Manipulating objects passed in as parameters can cause unwanted variable side effects in the original caller.
// bad{objkey = 1}// good{const key = ObjectprototypehasOwnProperty ? objkey : 1}
Why? It’s cleaner, you don’t need to supply a context, and you can not easily compose
new
withapply
.
// badconst x = 1 2 3 4 5consolelog// goodconst x = 1 2 3 4 5console// badnew Functionprototypebind// good...2016 8 5
// bad{// ...}// good{// ...}// badconsole// goodconsole
Why? It creates a version of the function that executes in the context of
this
, which is usually what you want, and is a more concise syntax.
Why not? If you have a fairly complicated function, you might move that logic out into its own function declaration.
// bad;1 2 3// good1 2 3
expression without side effects, omit the braces and use the implicit return. Otherwise, keep the braces and use a return
statement.
Why? Syntactic sugar. It reads well when multiple functions are chained together.
// bad;1 2 3// good1 2 3// good1 2 3// good1 2 3// No implicit return with side effects{const val =if val === true// Do something if callback returns true}let bool = false// bad// good
Why? It shows clearly where the function starts and ends.
// bad;'get' 'post' 'put'// good'get' 'post' 'put'
Why? Less visual clutter.
// bad;1 2 3// good1 2 3// good1 2 3// bad1 2 3// good1 2 3
Why?
class
syntax is more concise and easier to reason about.
// bad{thisqueue = ...contents}Queueprototype {const value = thisqueue0thisqueuereturn value}// good{thisqueue = ...contents}{const value = thisqueue0thisqueuereturn value}
Why? It is a built-in way to inherit prototype functionality without breaking
instanceof
.
// badconst inherits ={Queue}PeekableQueueprototype {return thisqueue0}// good{return thisqueue0}
// badJediprototype {thisjumping = truereturn true}Jediprototype {thisheight = height}const luke =luke // => trueluke // => undefined// good{thisjumping = truereturn this}{thisheight = heightreturn this}const luke =luke
{thisname = name}{return thisname}{return `Jedi - `}
// bad{}{return thisname}// bad{super...args}// good{super...argsthisname = 'Rey'}
// badconst luke =// goodconst luke =
Why? Duplicate class member declarations will silently prefer the last one - having duplicates is almost certainly a bug.
// bad{return 1}{return 2}// good{return 1}
Why? Modules are the future, let’s start using the future now.
// badconst GrindCodeStyle =moduleexports = GrindCodeStylees6// still badconst es6 =// good
// bad// good
Why? Although the one-liner is concise, having one clear way to import and one clear way to export makes things consistent.
// bad// filename es6.js// good// filename es6.js
Why? Having multiple lines that import from the same path can make code harder to maintain.
// bad// … some other imports … //// good// good
Why? Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported.
// badlet foo = 3// goodconst foo = 3// bestconst foo = 3
// bad{}// good{}
Why? Since
import
s are hoisted, keeping them all at the top prevents surprising behavior.
// badfoo// goodfoo
Why? The curly braces follow the same indentation rules as every other curly brace block in the style guide.
// bad// good
Use
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ … to iterate over arrays, andObject.keys()
/Object.values()
/Object.entries()
to produce arrays so you can iterate over objects.
const numbers = 1 2 3 4 5// badlet sum = 0numberssum === 15// goodlet sum = 0for const num of numberssum += numsum === 15// best (use the functional force)const sum = numberssum === 15// badconst increasedByOne =for let i = 0; i < numberslength; i++increasedByOne// badconst increasedByOne =numbers// goodconst increasedByOne =for const num of numbersincreasedByOne// best (keeping it functional)const increasedByOne = numbers
const luke =jedi: trueage: 28// badconst isJedi = luke'jedi'// goodconst isJedi = lukejedi
const luke =jedi: trueage: 28{return lukeprop}const isJedi =
// badconst binary = Math// goodconst binary = 2 ** 10
// badsuperPower =// goodconst superPower =
Why? It’s easier to add new variable declarations this way, and you never have to worry about swapping out a
;
for a,
or introducing punctuation-only diffs. You can also step through each declaration with the debugger, instead of jumping through all of them at once.
// badconst items =goSportsTeam = truedragonball = 'z'// bad// (compare to above, and try to spot the mistake)const items =goSportsTeam = truedragonball = 'z'// goodconst items =const goSportsTeam = trueconst dragonball = 'z'
Why? This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables.
// badlet ilendragonballitems =goSportsTeam = true// badlet iconst items =let dragonballconst goSportsTeam = truelet len// goodconst goSportsTeam = trueconst items =let dragonballlet ilet length
Why?
let
andconst
are block scoped and not function scoped.
// bad - unnecessary function call{const name =if hasName === 'test'return falseif name === 'test'thisreturn falsereturn name}// good{if hasName === 'test'return falseconst name =if name === 'test'thisreturn falsereturn name}
Why? Chaining variable assignments creates implicit global variables.
// bad; {// JavaScript interprets this as// let a = ( b = ( c = 1 ) )// The let keyword only applies to variable a; variables b and c become// global variables.let a = b = c = 1}console // throws ReferenceErrorconsole // 1console// 1// good{let a = 1let b = alet c = a}console // throws ReferenceErrorconsole // throws ReferenceErrorconsole // throws ReferenceError// the same applies for `const`
Temporal Dead Zones (TDZ). It’s important to know why typeof is no longer safe.
// we know this wouldn’t work (assuming there// is no notDefined global variable){console // => throws a ReferenceError}// creating a variable declaration after you// reference the variable will work due to// variable hoisting. Note: the assignment// value of `true` is not hoisted.{console // => undefinedvar declaredButNotAssigned = true}// the interpreter is hoisting the variable// declaration to the top of the scope,// which means our example could be rewritten as:{let declaredButNotAssignedconsole // => undefineddeclaredButNotAssigned = true}// using const and let{console // => throws a ReferenceErrorconsole // => throws a ReferenceErrorconst declaredButNotAssigned = true}
{console // => undefined// => TypeError anonymous is not a functionvar {console}}
{console // => undefined// => TypeError named is not a function// => ReferenceError superPower is not definedvar {console}}// the same is true when the function name// is the same as the variable name.{console // => undefined// => TypeError named is not a functionvar {console}}
{// => Flying{console}}
JavaScript Scoping & Hoisting by Ben Cherry.
Conditional statements such as the if
statement evaluate their expression using coercion with the ToBoolean
abstract method and always follow these simple rules:
- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evaluate to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string
''
, otherwise true
if 0 &&// true// an array (even an empty one) is an object, objects will evaluate to true
// badif isValid === true// ...// goodif isValid// ...// badif name// ...// goodif name !== ''// ...// badif collectionlength// ...// goodif collectionlength > 0// ...
For more information see Truth Equality and JavaScript by Angus Croll.
Why? Lexical declarations are visible in the entire
switch
block but only get initialized when assigned, which only happens when itscase
is reached. This causes problems when multiplecase
clauses attempt to define the same thing.
// bad// good
// badconst foo = a ? a : bconst bar = c ? true : falseconst baz = c ? false : true// goodconst foo = a || bconst bar = !!cconst baz = !c
// badif test return false// badif test return false// still badif testreturn false// goodif testreturn false// bad{return false}// good{return false}
// badif testelse// goodif testelse
// badiffoo === 123 || bar === 'abc' &&&&// badif foo === 123 && bar === 'abc'// badif foo === 123 && bar === 'abc'// goodiffoo === 123 || bar === 'abc' &&&&// goodif foo === 123 && bar === 'abc'// goodif foo === 123 && bar === 'abc'// goodif foo === 123 && bar === 'abc'
// bad// make() returns a new element// based on the passed in tag name//// @param {String} tag// @return {Element} element{// ...return element}// good/*** make() returns a new element* based on the passed-in tag name*/{// ...return element}
// badconst active = true // is current tab// good// is current tabconst active = true// bad{console// set the default type to 'no type'const type = thistype || 'no type'return type}// good{console// set the default type to 'no type'const type = thistype || 'no type'return type}// also good{// set the default type to 'no type'const type = thistype || 'no type'return type}
// bad//is current tabconst active = true// good// is current tabconst active = true// bad/***make() returns a new element*based on the passed-in tag name*/{// ...return element}// good/*** make() returns a new element* based on the passed-in tag name*/{// ...return element}
Use // FIXME:
to annotate problems.
{super// FIXME: shouldn’t use a global heretotal = 0}
Use // TODO:
to annotate solutions to problems.
{super// TODO: total should be configurable by an options paramthistotal = 0}
// bad{∙∙∙∙let name}// bad{∙let name}// good{⇥let name}
// bad{console}// good{console}// baddog// gooddog
// badif isJedi// goodif isJedi// bad{console}// good{console}
// badconst x = y + 5// goodconst x = y + 5
// bad// ...
// bad// ...↵
// good// ...↵
// bad// bad// good// badconst leds = stagedatadata// goodconst leds = stagedatadata// goodconst leds = stagedatadata
// badif fooreturn barreturn baz// goodif fooreturn barreturn baz// badconst obj ={}{}return obj// goodconst obj ={}{}return obj// badconst arr = {} {}return arr// goodconst arr = {} {}return arr
- Only classes should be padded, do not pad functions or switch blocks with blank lines.
// bad{console}// good{console}// badif bazconsoleelseconsole// goodif bazconsoleelseconsole// bad{thisbar = bar}// good{thisbar = bar}
// bad{return foo}// good{return foo}// badif fooconsole// goodif fooconsole
// badconst foo = 1 2 3console// goodconst foo = 1 2 3console
// badfoo0// goodfoo0// badfoo'data-attr'// goodfoo'data-attr'
// badconst foo =// badconst foo =// goodconst foo =
// badconst foo = clark: 'kent'// goodconst foo = clark: 'kent'
// badconst foo = {}// goodconst foo = {}
Why? This ensures readability and maintainability.
// badconst foo =jsonData &&jsonDatafoo &&jsonDatafoobar &&jsonDatafoobarbaz &&jsonDatafoobarbazquux &&jsonDatafoobarbazquuxxyzzy// goodconst foo =jsonData &&jsonDatafoo &&jsonDatafoobar &&jsonDatafoobarbaz &&jsonDatafoobarbazquux &&jsonDatafoobarbazquuxxyzzy// bad// good
// badconst story = once upon aTime// goodconst story = once upon aTime// badconst hero =firstName: 'Ada'lastName: 'Lovelace'birthYear: 1815superPower: 'computers'// goodconst hero =firstName: 'Ada'lastName: 'Lovelace'birthYear: 1815superPower: 'computers'
It’s up to you whether you use a trailing comma, however you should be consistent.
// bad; {const name = 'Skywalker'return name}// good{const name = 'Skywalker'return name}
// => this.reviewScore = 9// badconst totalScore = thisreviewScore + '' // invokes this.reviewScore.valueOf()// badconst totalScore = thisreviewScore // isn’t guaranteed to return a string// goodconst totalScore = StringthisreviewScore
Use Number
for type casting and parseInt
always with a radix for parsing strings.
const inputValue = '4'// badconst val = inputValue// badconst val = +inputValue// badconst val = inputValue >> 0// badconst val =// goodconst val = NumberinputValue// goodconst val = Number
If for whatever reason you are doing something wild and parseInt
is your bottleneck and need to use Bitshift for performance reasons, leave a comment explaining why and what you’re doing.
// good/*** parseInt was the reason my code was slow.* Bitshifting the String to coerce it to a* Number made it a lot faster.*/const val = inputValue >> 0
Be careful when using bitshift operations. Numbers are represented as 64-bit values, but bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:
2147483647 >> 0 // => 21474836472147483648 >> 0 // => -21474836482147483649 >> 0 // => -2147483647
const age = 0// badconst hasAge = age// badconst hasAge = Booleanage// badconst hasAge = !!age// goodconst hasAge = age > 0
// bad{// ...}// good{// ...}
// badconst OBJEcttsssss = {}const this_is_my_object = {}{}// goodconst thisIsMyObject = {}{}
// bad{thisname = optionsname}const bad =name: 'nope'// good{thisname = optionsname}const good =name: 'yup'
Function#bind.
// bad{const self = thisreturn {console}}// bad{const that = thisreturn {console}}// good{return {console}}
// file 1 contents// ...// file 2 contents{return 42}// file 3 contents{}// in some other file// bad// bad// good// ^ supports both insideDirectory.js and insideDirectory/index.js
{// ...}
const GrindCodeStyle =es6: {}
// bad// badconst HTTPRequests =// ...// good// bad (only classes should be PascalCase)const HttpRequests =// ...// goodconst httpRequests =// ...// best// bestconst requests =// ...
// badif !dragonreturn false// goodif !dragonreturn false
The Standard Library contains utilities that are functionally broken but remain for legacy reasons.
Why? The global
isNaN
coerces non-numbers to numbers, returning true for anything that coerces to NaN. If this behavior is desired, make it explicit.
// bad// false// true// goodNumber // falseNumber // true
Why? The global
isFinite
coerces non-numbers to numbers, returning true for anything that coerces to a finite number. If this behavior is desired, make it explicit.
Edit// bad// true// goodNumber // falseNumber // true