“Basic JavaScript” … [is the] most complete and concise write up I am aware of.
This blog post enables you to get started with JavaScript as quickly as possible – if you already know how to program. It describes the smallest subset of the language that allows you to be productive. I call that subset “Basic JavaScript” and recommend to program in it for a while, before moving on to more details and advanced topics. Learning everything at once is too confusing. The post concludes with tips for what to learn next.
Warning: Below, I’m describing rules of thumbs and best practices. I favor clarity over precision (e.g., whenever you see the word “roughly”). The rules are safe, but – unavoidably – a matter of taste.
> 3 + 4 7The text after the greater-than character is the input, typed by a human. Everything else is output by the JavaScript engine. Additionally, the method console.log() is sometimes used to print data to the console (this method works in most JavaScript environments, including Node.js).
mdn array push
On one hand, JavaScript has quirks and is missing quite a bit of functionality (block-scoped variables, modules, support for subtyping, etc.). On the other hand, it has several powerful features that allow you to work around these problems. In other languages, you learn language features. In JavaScript, you often learn patterns, instead.
var foo;
3 * 7
var x; if (y >= 0) { x = y; } else { x = -y; }Or as an expression:
var x = y >= 0 ? y : -y;You can use the latter as a function argument (but not the former):
myFunction(y >= 0 ? y : -y)Lastly, wherever JavaScript expects a statement, you can also use an expression. For example:
foo(bar(7, 1));foo(...); is a statement (a so-called expression statement), bar(7, 1) is an expression. Both are function calls.
if (obj !== null) obj.foo(); while (x > 0) x--;However, any statement can always be replaced by a block, curly braces containing zero or more statements. Thus, you could also write:
if (obj !== null) { obj.foo(); } while (x > 0) { x--; }In this blog post, we only use the latter form of control flow statements.
As you can see above, semicolons terminate statements, but not blocks. There is one case where you will see a semicolon after a block: A function expression is an expression that ends with a block. If such an expression comes last in a statement, it is followed by a semicolon:
var x = 3 * 7; var f = function () { };
x++; // single-line commentMulti-line comments are delimited by /* and */
/* This is a multi-line comment. */
var foo; // declare variable `foo`
var foo = 6;You can also assign a value to an existing variable:
foo = 4; // change variable `foo`
x += 1; x = x + 1;
Roughly, the first character of an identifier can be any Unicode letter, a dollar sign ($) or an underscore (_). Later characters can additionally be any Unicode digit. Thus, the following are all legal identifiers:
arg0 _tmp $elem πSeveral identifiers are “reserved words” – they are part of the syntax and can’t be used as variable names:
arguments break case catch class const continue debugger default delete do else enum eval export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yieldTechnically, the following three identifiers are not reserved words, but shouldn’t be used as variable names, either:
Infinity NaN undefined
value.propKeyAn example: The string 'abc' has the property length.
> var str = 'abc'; > str.length 3The above can also be written as:
> 'abc'.length 3The dot operator is also used to assign a value to a property:
> var obj = {}; // empty object > obj.foo = 123; // create property `foo`, set it to 123 123 > obj.foo 123And you can invoke methods via it:
> 'hello'.toUpperCase() 'HELLO'Above, we have invoked the method toUpperCase() on the value 'hello'.
> var obj1 = {}; // an empty object > var obj2 = {}; // another empty object > obj1 === obj2 false > obj1 === obj1 trueIn contrast, all primitive values encoding the same value are considered the same:
> var prim1 = 123; > var prim2 = 123; > prim1 === prim2 trueThe following two sections explain primitive values and objects in more detail.
> 3 === 3 true > 'abc' === 'abc' true
> var str = 'abc'; > str.foo = 3; // try to create property `foo` ⇒ no effect > str.foo // unknown property undefined(Reading an unknown property always returns undefined.)
{ firstName: 'Jane', lastName: 'Doe' }The above object has two properties: The value of property firstName is 'Jane', the value of property lastName is 'Doe'.
[ 'apple', 'banana', 'cherry' ]The above array has three elements that can be accessed via numeric indices. For example, the index of 'apple' is 0.
/^a+b+$/
> {} === {} // two different empty objects false > var obj1 = {}; > var obj2 = obj1; > obj1 === obj2 true
> var obj = {}; > obj.foo = 123; > obj.foo 123
> var foo; > foo undefinedIf you read a non-existant property, you also get undefined:
> var obj = {}; // empty object > obj.foo undefinedMissing parameters are undefined, too:
> function f(x) { return x } > f() undefined
if (x === undefined || x === null) { ... }Another way is to exploit the fact that both undefined and null are considered false:
if (!x) { ... }Warning: false, 0, NaN and '' are also considered false.
> [].push === Array.prototype.push trueIn contrast, primitives have no types you can access in the language, so each primitive type has an associated type, a so-called wrapper type:
> true.toString === Boolean.prototype.toString trueNote that the name of the wrapper type starts with a capital B. If the type of booleans was accessible in JavaScript, it would likely be called boolean.
> Number('123') 123 > String(true) 'true'
typeof looks like this:
typeof «value»It returns a string describing the “type” of value. Examples:
> typeof true 'boolean' > typeof 'abc' 'string' > typeof {} // empty object literal 'object' > typeof [] // empty array literal 'object'
The following table lists all results of typeof.
Operand | Result |
undefined | 'undefined' |
null | 'object' |
Boolean value | 'boolean' |
Number value | 'number' |
String value | 'string' |
Function | 'function' |
All other values | 'object' |
Two things contradict what we have said about primitives versus objects:
instanceof looks like this:
«value» instanceof «Constr»It returns true if value is an object that has been created by the constructor Constr (think: class). Examples:
> var b = new Bar(); // object created by constructor Bar > b instanceof Bar true > {} instanceof Object true > [] instanceof Array true > [] instanceof Object // Array is a subtype of Object true
> Boolean(undefined) false > Boolean(0) false > Boolean(3) true
false && foo() true || foo()Furthermore, binary logical operators return either one of their operands – which may or may not be a boolean. A check for truthiness is used to determine which one:
> NaN && 'abc' NaN > 123 && 'abc' 'abc'
> 'abc' || 123 'abc' > '' || 123 123
> 1 === 1.0 trueSpecial numbers:
> Number('xyz') // 'xyz' can’t be converted to a number NaN
> 3 / 0 Infinity > Math.pow(2, 1024) // number too large InfinityInfinity is sometimes useful, because it is larger than any other number. Similarly, -Infinity is smaller than any other number.
> +0 0 > -0 0Therefore, it is best to pretend that there is only a single zero (as we have done when we looked at falsy values: both -0 and +0 are falsy).
JavaScript also has operators for bitwise operations (e.g. bitwise And).
'abc' "abc" 'Did she say "Hello"?' "Did she say \"Hello\"?" 'That\'s nice!' "That's nice!" 'Line 1\nLine 2' // newline 'Backlash: \\'Single characters are accessed via square brackets:
> var str = 'abc'; > str[1] 'b'The property length counts the number of characters in the string:
> 'abc'.length 3Reminder: strings are immutable, you need to create a new string if you want to change an existing one.
> var messageCount = 3; > 'You have '+messageCount+' messages' 'You have 3 messages'To concatenate strings in multiple steps, use the += operator:
> var str = ''; > str += 'Multiple '; > str += 'pieces '; > str += 'are concatenated.'; > str 'Multiple pieces are concatenated.'
> 'abc'.slice(1) // copy a substring 'bc' > 'abc'.slice(1, 2) 'b' > '\t xyz '.trim() // trim whitespace 'xyz' > 'mjölnir'.toUpperCase() 'MJÖLNIR' > 'abc'.indexOf('b') // find a string 1 > 'abc'.indexOf('x') -1
if (myvar === 0) { // then } if (myvar === 0) { // then } else { // else } if (myvar === 0) { // then } else if (myvar === 1) { // else-if } else if (myvar === 2) { // else-if } else { // else }In the following switch statement, the value of fruit decides which case is executed.
switch (fruit) { case 'banana': // ... break; case 'apple': // ... break; default: // all other cases // ... }
for(initialization; loop while this condition holds; next step)Example:
for (var i=0; i < arr.length; i++) { console.log(arr[i]); }The while loop continues looping over its body while its condition holds.
// Same as for loop above: var i = 0; while (i < arr.length) { console.log(arr[i]); i++; }The do-while loop continues looping over its body while its condition holds. As the condition follows the body, the body is always executed at least once.
do { // ... } while(condition);In all loops:
function add(param1, param2) { return param1 + param2; }The above code defines a function add that has two parameters param1 and param2 and returns the sum of both parameters. This is how you call that function:
> add(6, 1) 7 > add('a', 'b') 'ab'
Another way of defining add() is via a function expression:
var add = function (param1, param2) { return param1 + param2; };A function expression produces a value and can thus be used to directly pass functions as arguments to other functions:
someOtherFunction(function (p1, p2) { ... });
function foo() { bar(); // OK, bar is hoisted function bar() { ... } }Note that while var declarations are also hoisted, assignments performed by them are not:
function foo() { bar(); // Not OK, bar is still undefined var bar = function () { // ... }; }
> function f() { return arguments } > var args = f('a', 'b', 'c'); > args.length 3 > args[0] // read element at index 0 'a'
function f(x, y) { console.log(x, y); console.log(toArray(arguments)); }Additional parameters will be ignored (except by arguments):
> f('a', 'b', 'c') a b [ 'a', 'b', 'c' ]Missing parameters will get the value undefined:
> f('a') a undefined [ 'a' ] > f() undefined undefined []
function pair(x, y) { x = x || 0; // (*) y = y || 0; return [ x, y ]; }In line (*), the || operator returns x if it is truthy (not: null, undefined, etc.). Otherwise, it returns the second operand.
> pair() [ 0, 0 ] > pair(3) [ 3, 0 ] > pair(3, 5) [ 3, 5 ]
function pair(x, y) { if (arguments.length !== 2) { throw new Error('Need exactly 2 arguments'); } ... }
function toArray(arrayLikeObject) { return [].slice.call(arrayLikeObject); }
function throwException() { throw new Error('Problem!'); } try { throwException(); } catch (e) { console.log(e); // Error: Problem! console.log(e.stack); // non-standard, but often supported }The try clause surrounds critical code, the catch clause is executed if an exception is thrown inside the try clause.
'use strict';You can also switch on strict mode per function, by putting the above code at the beginning of a function:
function functionInStrictMode() { 'use strict'; }The following two sub-sections look at the three great benefits of strict mode.
function f() { 'abc'.length = 5; }When you call that function, it fails silently, the assignment is simply ignored. Let’s change f() so that it runs in strict mode:
function f_strict() { 'use strict'; 'abc'.length = 5; }Now we get an error:
> f_strict() TypeError: Cannot assign to read only property 'length' of abc
function f_strict() { 'use strict'; return this; } console.log(f_strict() === undefined); // trueIn non-strict mode, the value of this is the so-called global object (window in browsers):
function f() { return this; } console.log(f() === window); // true
> function f() { foo = 5 } > f() // no error > foo 5In strict mode, you get an error:
> function f_strict() { 'use strict'; foo2 = 4; } > f_strict() ReferenceError: foo2 is not defined
> var x; > x = 3; > y = 4; ReferenceError: y is not definedYou can declare and initialize several variables with a single var statement:
var x = 1, y = 2, z = 3;But I recommend to use one statement per variable. Thus, I would rewrite the previous statement to:
var x = 1; var y = 2; var z = 3;Due to hoisting (see below), it is usually best to declare variables at the beginning of a function.
function foo() { var x = -3; if (x < 0) { // (*) var tmp = -x; ... } console.log(tmp); // 3 }We can see that the variable tmp is not restricted to the block starting in line (*), it exists until the end of the function.
function foo() { console.log(tmp); // undefined if (false) { var tmp = 3; // (*) } }Internally, this above function is executed like this:
function foo() { var tmp; // declaration is hoisted console.log(tmp); if (false) { tmp = 3; // assignment stays put } }
function createIncrementor(start) { return function () { // (*) return start++; } }The function starting in line (*) leaves the context it was created in, but stays connected to a live version of start:
> var inc = createIncrementor(5); > inc() 5 > inc() 6 > inc() 7A closure is a function plus the connection to the variables of its surrounding scopes. What createIncrementor() returns is thus a closure.
(function () { // open block var tmp = ...; // not a global variable }()); // close blockAbove, you can see a function expression that is called right away. The parentheses prevent that it is interpreted as a function declaration; only function expressions can be immediately invoked. The function body introduces a new scope and prevents tmp from becoming global.
Closures keep their connections to outer variables, which is sometimes not what you want:
var result = []; for (var i=0; i < 5; i++) { result.push(function () { return i }); // (*) } console.log(result[1]()); // 5 (not 1) console.log(result[3]()); // 5 (not 3)The value returned in line (*) is always the current value of i, not the value it had when the function was created. After the loop is finished, i has the value 5, which is why all functions in the array return that value. If you want a snapshot of the current value, you can use an IIFE:
for (var i=0; i < 5; i++) { (function (i2) { result.push(function () { return i2 }); }(i)); // copy current i }
var jane = { name: 'Jane', describe: function () { 'use strict'; return 'Person named '+this.name; } };The above object has the properties name and describe. You can read (“get”) and write (“set”) properties:
> jane.name // get 'Jane' > jane.name = 'John'; // set > jane.newProperty = 'abc'; // created automaticallyFunction-valued properties such as describe are called methods. They use this to refer to the object that was used to call them.
> jane.describe() // call method 'Person named John' > jane.name = 'Jane'; > jane.describe() 'Person named Jane'The in operator checks whether a property exists:
> 'newProperty' in jane true > 'foo' in jane falseIf you read a property that does not exist, you get the value undefined. Hence, the previous two checks could also be performed like this:
> jane.newProperty !== undefined true > jane.foo !== undefined falseThe delete operator removes a property:
> delete jane.newProperty true > 'newProperty' in jane false
> var obj = { 'not an identifier': 123 }; > obj['not an identifier'] 123 > obj['not an identifier'] = 456;Square brackets also allow you to compute the key of a property:
> var x = 'name'; > jane[x] 'Jane' > jane['na'+'me'] 'Jane'
> var func = jane.describe; > func() TypeError: Cannot read property 'name' of undefinedThe solution is to use the method bind() that all functions have. It creates a new function whose this always has the given value.
> var func2 = jane.describe.bind(jane); > func2() 'Person named Jane'
var jane = { name: 'Jane', friends: [ 'Tarzan', 'Cheeta' ], logHiToFriends: function () { 'use strict'; this.friends.forEach(function (friend) { // `this` is undefined here console.log(this.name+' says hi to '+friend); }); } }Calling logHiToFriends produces an error:
> jane.logHiToFriends() TypeError: Cannot read property 'name' of undefinedLet’s look at two ways of fixing this. Fix #1: store this in a different variable.
logHiToFriends: function () { 'use strict'; var that = this; this.friends.forEach(function (friend) { console.log(that.name+' says hi to '+friend); }); }Fix #2: forEach has a second parameter that allows you to provide a value for this.
logHiToFriends: function () { 'use strict'; this.friends.forEach(function (friend) { console.log(this.name+' says hi to '+friend); }, this); }Function expressions are often used as arguments in function calls in JavaScript. Always be careful when you refer to this from one of those function expressions.
In addition to being “real” functions and methods, functions play a third role in JavaScript: They become constructors, factories for objects, if invoked via the new operator. Constructors are thus a rough analog to classes in other languages. By convention, the names of constructors start with capital letters. Example:
// Set up instance data function Point(x, y) { this.x = x; this.y = y; } // Methods Point.prototype.dist = function () { return Math.sqrt(this.x*this.x + this.y*this.y); };We can see that a constructor has two parts: First, the function Point sets up the instance data. Second, the property Point.prototype contains an object with the methods. The former data is specific to each instance, the latter data is shared between all instances.
To use Point, we invoke it via the new operator:
> var p = new Point(3, 5); > p.x 3 > p.dist() 5.830951894845301p is an instance of Point:
> p instanceof Point true > typeof p 'object'
> var arr = [ 'a', 'b', 'c' ];The above array has three elements: the strings 'a', 'b' and 'c'. You can access them via integer indices:
> arr[0] 'a' > arr[0] = 'x'; > arr [ 'x', 'b', 'c' ]
Property length always indicates how many elements an array has.
> arr.length 3But it can also be used to remove trailing elements from the array:
> arr.length = 2; > arr [ 'x', 'b' ]The in operator works for arrays, too.
> 1 in arr // does arr have an element at index 1? true > 5 in arr // does arr have an element at index 5? falseNote that arrays are objects and can thus have object properties:
> arr.foo = 123; > arr.foo 123
> var arr = [ 'a', 'b', 'c' ]; > arr.slice(1, 2) // copy elements [ 'b' ] > arr.slice(1) [ 'b', 'c' ] > arr.push('x') // append an element 4 > arr [ 'a', 'b', 'c', 'x' ] > arr.pop() // remove last element 'x' > arr [ 'a', 'b', 'c' ] > arr.shift() // remove first element 'a' > arr [ 'b', 'c' ] > arr.unshift('x') // prepend an element 3 > arr [ 'x', 'b', 'c' ] > arr.indexOf('b') // find the index of an element 1 > arr.indexOf('y') -1 > arr.join('-') // all elements in a single string 'x-b-c' > arr.join('') 'xbc' > arr.join() 'x,b,c'
forEach iterates over an array and hands the current element and its index to a function:
[ 'a', 'b', 'c' ].forEach( function (elem, index) { // (*) console.log(index + '. ' + elem); });The above code produces the output
0. a 1. b 2. cNote that the function in line (*) is free to ignore arguments. It could, for example, only have the parameter elem.
map creates a new array, by applying a function to each element of an existing array.
> [1,2,3].map(function (x) { return x*x }) [ 1, 4, 9 ]
/^abc$/ /[A-Za-z0-9]+/
> /^a+b+$/.test('aaab') true > /^a+b+$/.test('aaa') false
> /a(b+)a/.exec('_abbba_aba_') [ 'abbba', 'bbb' ]The returned array contains the complete match at index 0, the capture of the first group at index 1, etc. There is a way to invoke this method repeatedly to get all matches.
> '<a> <bbb>'.replace(/<(.*?)>/g, '[$1]') '[a] [bbb]'The first parameter of replace must be a regular expression with a /g flag, otherwise only the first occurrence is replaced. There is also a way to use a function to compute the replacement.
> Math.abs(-2) 2 > Math.pow(3, 2) // 32 9 > Math.max(2, -1, 5) 5 > Math.round(1.9) 2 > Math.cos(Math.PI) // pre-defined constant for π -1