This introduction attempts to explain JavaScript to those with prior programming experience in Java, and will point out some common mistakes along the way. It is just for the JavaScript language and doesn't contain anything browser-related. The code examples use an arrow to indicate code that is typed in, which will be immediately followed by the result if there is one:
→ 2 + 2; 4
Even if you have already used JavaScript, there will hopefully still be some useful information (especially towards the end). It is meant to be read from top to bottom but can also be used as a reference.
Comparisons evaluate to true
and false
, just like in Java:
→ 1 != 2; true → 1 == 2; false
JavaScript also has the strict equality operators ===
and !==
, which don't attempt to convert types before comparison. These should be preferred over ==
and !=
because they have a lower potential for bugs:
→ 0 == ""; true → 0 === ""; false
JavaScript has some very weird comparisons. For example, null >= 0
and null <= 0
but null != 0
. Be careful of these quirks, especially when values may be null
.
A String
object is like a String
in Java (they both use 16 bits per character). Strings can be declared using either single or double quotes. There is no separate character type in JavaScript, so instead characters are just strings of length 1. Strings in JavaScript and Java share many similar methods:
→ "hello".length; 5 → "hello".charAt(0); "h" → "hello, world".toUpperCase(); "HELLO, WORLD" → "a" + "b" + 2; "ab2"
JavaScript strings are immutable (they can't be modified). A mutable operation such as changing a character is performed by constructing a new string with that character changed.
You can also index into a string using str[0]
instead of str.charAt(0)
, but the bracket syntax doesn't work in versions of IE before IE 8.
Documentation for String
instance.length
instance.charAt(index)
index
, where 0 <= index < instance.length
.instance.charCodeAt(index)
index
, where 0 <= index < instance.length
.instance.indexOf(str)
str
in instance
, or -1
if str
could not be found.instance.lastIndexOf(str)
str
in instance
, or -1
if str
could not be found.instance.match(regexp)
regexp
inside instance
. If regexp
includes the g
(global) flag, this returns an array of all matches, otherwise this is the same as regexp.exec(instance)
.instance.replace(regexp or substr, replacement or replacementFunction)
instance
of the regular expression regexp
or the string substr
with the string replacement
or the result of calling the function replacementFunction
. This replaces all matches if regexp
includes the g
(global) flag.
If replacement
is specified, it may contain one of these special values:
$$
: inserts a "$"
$&
: inserts the matched substring$`
: inserts the substring of instance
before the match$'
: inserts the substring of instance
after the match$n
: inserts the nth
parenthesized submatch starting at 1
, provided regexp
was specifiedIf replacementFunction
is specified, it will be called once per match to determine the replacement string with the arguments (match, p1, p2, p3, ..., index, string)
where:
match
: the substring for the current matchp1, p2, p3, ...
: the parenthesized submatches, provided regexp
was specifiedindex
: the index of the match inside instance
string
: the entire string (instance
)instance.search(regexp)
regexp
in instance
, or -1
if there were no matches.instance.slice(start[, end])
start
up to but not including the index end
. If end
is omitted it defaults to the length of instance
. Negative indices indicate an offset from the end of the string, so "'xyz'".slice(1, -1) == "xyz"
.instance.split(separator[, limit])
instance
by the string separator
. If limit
is specified, a maximum of that number of results are returned. The separator
can also be a regular expression.
→ "abc".split("") ["a", "b", "c"] → "game of life".split(" ", 2) ["game", "of"]
instance.substr(index[, length])
index
and containing length
characters. If length
is omitted it defaults to instance.length - index
.instance.toLowerCase()
instance
. Some locales may need the toLocaleLowerCase()
method instead.instance.toUpperCase()
instance
. Some locales may need the toLocaleUpperCase()
method instead.String.fromCharCode(code1[, code2, code3, ...])
nth
character has the Unicode value coden
.A Number
object is like a double
in Java, which is a 64-bit floating-point value. Strings can be converted to numbers using the parseFloat
function. JavaScript has no int
type, although you can get rid of the fractional part of a number with the parseInt
function (which works on both numbers and strings).
Numbers that start with a zero are in octal. The parseInt
function takes a second argument that is the radix to use, so always pass in 10
as the second argument or your zip codes will end up in octal:
→ parseInt("04105"); 2117 → parseInt("04105", 10); 4105
JavaScript also has NaN
(not a number) and Infinity
, which are returned by some computations:
→ parseInt("text", 10); NaN → 1 / 0; Infinity → -1 / 0; -Infinity
Documentation for Number
and Math
instance.toExponential([digits])
instance
in exponential notation with digits
number of digits after the decimal point. When omitted, digits
defaults to as many digits as necessary.instance.toFixed([digits])
instance
with digits
number of digits after the decimal point. When omitted, digits
defaults to 0
.instance.toString()
instance
.Number.MIN_VALUE
5e-324
(the smallest positive number that JavaScript can represent)Number.MAX_VALUE
1.7976931348623157e+308
(the largest positive number that JavaScript can represent)Math.E, Math.LN2, Math.LN10, Math.LOG2E, Math.LOG10E, Math.PI, Math.SQRT1_2, Math.SQRT2
Math.abs(n)
n
.Math.sin(n), Math.cos(n), Math.tan(n)
n
must be in radians (radians = degrees * Math.PI / 180
).Math.asin(n), Math.acos(n), Math.atan(n)
degrees = radians * 180 / Math.PI
).Math.atan2(y, x)
y / x
in radians. This avoids the divide-by-zero errors and quadrant ambiguities present in Math.atan()
.Math.round(n)
n
rounded to the nearest integer.Math.floor(n)
n
.Math.ceil(n)
n
.Math.pow(base, exponent)
base
raised to exponent
.Math.exp(n)
Math.pow(Math.E, n)
.Math.log(n)
n
(inverse of Math.exp
).Math.min(a, b)
a
and b
.Math.max(a, b)
a
and b
.Math.random()
0
inclusive and 1
exclusive. There is no way to set the pseudorandom seed.JavaScript has built-in support for regular expressions. A regular expression can either be created using the literal syntax or using the RegExp
constructor:
→ /\d+/.exec("abc"); null → /\d+/.exec("123"); ["123"] → new RegExp("^(.*)(\\d)$").exec("five5"); ["five5", "five", "5"]
See Mozilla's documentation for more details.
A variable is declared with the var
keyword:
→ var a = null; → a; null
Uninitialized variables are set to a special value called undefined
:
→ var a; → a; undefined
Every JavaScript environment includes an uninitialized variable named undefined
by default. Many people check if a variable is initialized by testing for equality with undefined
(making sure to use ===
instead of ==
because null == undefined
):
→ var x; → x === undefined; true → x = 2; → x === undefined; false
This usually works, except when someone initializes undefined
. The correct way to check if a variable is initialized is to use the typeof
operator:
→ var x; → x === undefined; true → undefined = 2; → x === undefined; false → typeof x == "undefined"; true
An Object
is similar to a HashMap<String, Object>
in Java. Objects can be easily defined using the object literal syntax, and properties can be accessed using brackets or a dot:
→ var object = { a: 3.14159, b: "text" }; → object["a"]; 3.14195 → object.b; "text"
Object properties can be added via assignment, removed via delete
, and queried via in
:
→ var object = {}; → object.a = 2; → "a" in object; true → delete object.a; → "a" in object; false
The properties in an object can be iterated through using a for-in loop:
→ var object = { a: 1, b: 2 }, total = 0; → for (var propertyName in object) { total += object[propertyName]; } → total; 3
Objects are an unordered mapping. Although all major JavaScript engines iterate over object properties in the order they were created, the JavaScript language doesn't specify any iteration order. It has been the case before that some JavaScript engines (for example, early versions of Chrome) iterate in a different order, and it's considered bad practice to rely on this order.
Be careful when using objects as a map with arbitrary keys. Some browsers (Firefox and Chrome) have a special property named __proto__
that will cause undesired behavior in this case when overwritten. Understanding why requires knowledge of prototypes which we haven't gotten to yet. One solution is to prefix all keys with a character not present in identifiers such as space before using the key to find a property:
→ var map = {}, key = '__proto__'; → map[key] = 1; → map[key]; Object.prototype → map[' ' + key] = 1; → map[' ' + key]; 1
Prototypes may also cause extra properties to show up when using the in
operator or for-in loops. To avoid all of these complications you'll want to use something like this, which uses the hasOwnProperty()
method to check if a property is really defined on that object:
→ function hasKey(obj, key) { return obj.hasOwnProperty(' ' + key); } → function getKey(obj, key) { return hasKey(obj, key) ? obj[' ' + key] : null; } → function setKey(obj, key, value) { obj[' ' + key] = value; } → var map = {}, key = '__proto__'; → getKey(map, key); null → setKey(map, key, 1); → getKey(map, key); 1
Arrays can be created using the array literal syntax and indexed into using brackets. Arrays are actually just objects with properties that happen to be numbers and with a special length
property:
→ var array = [3.14159, "text"]; → array[0]; 3.14159 → array["0"]; 3.14159 → array.length; 2
Inserting and removing elements from an array is all done with the splice()
method, although there are special-case methods for adding and removing from the beginning and end of an array (push()
, pop()
, shift()
, and unshift()
). The filter()
and map()
methods are also very useful for concise array manipulations.
Documentation for Array
instance.length
undefined
.instance.splice(index, count[, a, b, c, ...])
count
elements starting at index
and optionally inserts more elements at index
if more arguments are provided. Returns an array containing the removed elements.
→ var x = [1, 2, 3, 4, 5]; → x.splice(2, 1); // remove the element at index 2 [3] → x.splice(1, 0, 1.5); // insert 1.5 at index 1 [] → x.splice(3, 1, 4.5); // replace the element at index 3 with 4.5 [4] → x; [1, 1.5, 2, 4.5, 5]
instance.push(a[, b, c, ...])
instance
. Returns the new length of instance
.instance.pop()
instance
. Returns undefined
if instance
is empty.instance.unshift(a[, b, c, ...])
instance
. Returns the new length of instance
.instance.shift()
instance
. Returns undefined
if instance
is empty.instance.slice(start[, end])
start
up to but not including the index end
. If end
is omitted it defaults to the length of instance
. Negative indices indicate an offset from the end of the array, so [1, 2, 3, 4].slice(1, -1) == [2, 3]
.instance.concat(a[, b, c, ...])
instance
followed by the provided arguments. If an argument is an array then each element of that array is added separately. Note that instance
is not modified.
→ [1, 2, 3].concat([4, 5, 6], 7, [8, 9]) [1, 2, 3, 4, 5, 6, 7, 8, 9]
instance.sort([compareFunction])
instance
and returns instance
. The optional function compareFunction(a, b)
should return a negative number, zero, or a positive number if a
comes before b
, is equal to b
, or comes after b
, respectively. If compareFunction
is not specified, the elements are sorted lexicographically by their string representations (so this will not sort numerically, as "80"
comes before "9"
). Sorting numerically is as easy as returning a - b
from compareFunction
. Note that instance
is modified.instance.reverse()
instance
and returns instance
. Note that instance
is modified.instance.join([separator])
instance
together, each separated by the string separator
. If separator
is omitted it defaults to ","
.
→ [1, 2, 3].join(" : "); "1 : 2 : 3"
instance.indexOf(element[, fromIndex])
element
in instance
starting from fromIndex
and counting up. If fromIndex
is negative it is relative to the end of the array, and if fromIndex
is not specified it defaults to 0
.instance.lastIndexOf(element[, fromIndex])
element
in instance
starting from fromIndex
and counting down. If fromIndex
is negative it is relative to the end of the array, and if fromIndex
is not specified it defaults to instance.length
.instance.filter(callback[, thisObject])
callback
once for each element in instance
and returns an array containing elements for which callback
returned true
. If provided, thisObject
specifies the object to use as this
inside callback
. The callback
function is called with three arguments: the current element, the index of the current element, and the entire original array (instance
). Note that instance
is not modified.
→ [2, -1, 0, 4].filter(function(x) { return x > 0; }); [2, 4]
instance.map(callback[, thisObject])
callback
once for each element in instance
and returns an array of the return values. If provided, thisObject
specifies the object to use as this
inside callback
. The callback
function is called with three arguments: the current element, the index of the current element, and the entire original array (instance
). Note that instance
is not modified.
→ [1, 2, 3].map(function(x) { return x.toString(); }); ["1", "2", "3"]
instance.forEach(callback[, thisObject])
callback
once for each element in instance
. If provided, thisObject
specifies the object to use as this
inside callback
. The callback
function is called with three arguments: the current element, the index of the current element, and the entire original array (instance
).instance.every(callback[, thisObject])
callback
returns true
for each element in instance
(including when instance
is empty). If provided, thisObject
specifies the object to use as this
inside callback
. The callback
function is called with three arguments: the current element, the index of the current element, and the entire original array (instance
).instance.some(callback[, thisObject])
callback
returns true
for at least one element in instance
. If provided, thisObject
specifies the object to use as this
inside callback
. The callback
function is called with three arguments: the current element, the index of the current element, and the entire original array (instance
).Functions are declared using the function
keyword and have no type information for their arguments or return type. A function doesn't have to be named, in which case it's an anonymous function:
→ function f(x) { return x * x; } → var g = function(y) { return Math.sqrt(y); };
Variable scope is different from Java. There is only one scope per function and blocks do not introduce a child scope:
→ function func() { if (true) { var x = 2; } return x; } → f(); 2
Variables declared in the global scope are actually stored as properties on the window
object:
→ var x = 2; → window.x; 2
In a function, assigning to a variable without declaring it using var
will create a global variable. This is almost always unintentional and makes finding typos difficult. Even though leaving off the var
keyword may work, it is considered bad practice and you should always declare all variables with var
before using them. If you do want to create a global variable from within a function, explicitly assign it as a property of the window
object instead.
A function is a Function
object, meaning it can be treated like any other data type. New functions can be constructed dynamically via the Function
constructor:
→ var add = new Function("x", "y", "return x + y;"); → add(1, 2); 3
Functions can also be declared inside other functions, in which case they have access to the variable scope of the outer function. JavaScript functions are closures, which means they retain access to all variables they had access to when they were defined:
→ function createAdder(value) { return function(x) { return x + value; }; } → var adder = createAdder(3); → adder(2); 5
This fact is the cause of a common error:
→ var funcs = []; → for (var i = 0; i < 3; i++) { var value = i * 2; funcs.push(function() { return value; }); } → funcs[0](); 4
One might expect funcs[0]()
to be 0
instead of 4
. This is a consequence of single scope and closures, since the anonymous function captures the variable value
which is set to 4
by the time the loop finishes. The fix is to create a new scope using another anonymous function that is called immediately:
→ var funcs = []; → for (var i = 0; i < 3; i++) { (function(value) { funcs.push(function() { return value; }); })(i * 2); } → funcs[0](); 0
Functions can have a variable number of arguments, which can be accessed using the magic arguments
variable:
→ function sum() { var total = 0; for (var i = 0; i < arguments.length; i++) { total += arguments[i]; } return total; } → sum(1, 2, 3); 6
The object stored in arguments
is not an array, even though it looks like one (for example, arguments.reverse()
will not work). To convert arguments
to an array use Array.prototype.slice.call(arguments)
, which will make more sense after the next section.
The value undefined
is used when arguments are missing or when a function doesn't return anything:
→ function f(a, b) { return [a, b]; } → f(1); [1, undefined] → function g() {} → g(); undefined
Every function has an associated execution context called this
. You can think of this
as "how we got to the function," which is the global window
object for plain functions and the object before the dot for functions called on objects:
→ function func() { return this; } → func(); window → var obj = { f: func }; → obj.f(); obj
The value of this
can be explicitly set with the call
or apply
methods, which are properties of every function object. The first argument to both call
and apply
is the object to use as this
inside the function:
→ function f(a, b) { return [this, a, b]; } → f(1, 2); [window, 1, 2] → f.call(window, 1, 2); [window, 1, 2] → f.apply(window, [1, 2]); [window, 1, 2] → f.call(0, 1, 2); [0, 1, 2]
While JavaScript is object-oriented, it is prototype-based rather than class-based. An object can inherit properties from another object, called its prototype. Instances retain a link to their prototype, and modifying the prototype modifies all instances of that prototype as well. Accessing a property on an object first checks for a value defined directly on that object. If there is no such definition, the object's prototype is checked next, then the prototype's prototype, and all the way up the prototype chain until the prototype for Object
is reached. An object's prototype cannot be set directly and is fixed at construction time. Although most JavaScript engines will allow you to access and modify it directly via the __proto__
property, that property still non-standard and doesn't work in IE.
Java-like classes can still be emulated on top of prototypes. There are many different ways to do this and we will only cover one method here, although it is likely to be one of the more efficient methods in terms of memory and speed. Before we do that, however, we need to know how to set an object's prototype. There are two ways to do this, either the Object.create()
1 method or the new
keyword. The core functionality of Object.create()
is to create an empty object with its prototype set to the first argument:
→ var a = { value: 1 }; → var b = Object.create(a); → b.value; 1 → a.value = 2; → b.value; 2
The other way of setting the prototype of an object is using the new
keyword. This is where it starts to get weird. Any function can serve as an object constructor:
→ function TestClass(value) { this.number = value; } → var t = new TestClass(1); → t.number; 1 → t instanceof TestClass; true
Every function has a prototype
property that is initially set to an empty object. The new
keyword in the above code creates a new object, sets the prototype of that object to TestClass.prototype
, and calls the TestClass
function with this
set to the new instance. If we were implementing new
manually, that would look something like this:
// This implements "var t = new TestClass(1);" → var t = Object.create(TestClass.prototype); → TestClass.call(t, 1);
This allows a direct mapping of class concepts to prototype:
public class Vector { public double x; public double y; public Vector(double x, double y) { this.x = x; this.y = y; } public Vector add(Vector other) { return new Vector(x + other.x, y + other.y); } }
function Vector(x, y) { this.x = x; this.y = y; } Vector.prototype.add = function(other) { return new Vector(this.x + other.x, this.y + other.y); };
Because of how this
works, we don't actually need prototypes to create a singleton. We can just create them directly using an object literal:
→ var singleton = { nextID: 0, getID: function() { return this.nextID++; } }; → singleton.getID(); 0 → singleton.getID(); 1
JavaScript code is allowed to modify the prototype of any object, including built-in objects like Array
and even Object
. This is considered bad practice because it may break some libraries. For example, properties added to Object.prototype
will show up in for
loops:
→ Object.prototype.problem = true; → var obj = { a: 1, b: 2 }; → var names = []; → for (var x in obj) { names.push(x); } → names; ["a", "b", "problem"]
The way to avoid this is to use the hasOwnProperty()
method, which returns true
only when the first argument is the name of a property defined directly on that object:
→ Object.prototype.problem = true; → var obj = { a: 1, b: 2 }; → var names = []; → for (var x in obj) { if (Object.prototype.hasOwnProperty.call(obj, x)) { names.push(x); } } → names; ["a", "b"]
Note that this code does not call obj.hasOwnProperty(x)
but rather Object.prototype.hasOwnProperty.call(obj, x)
. This avoids problems when the hasOwnProperty
property of obj
has been set to something else.
To create subclasses, we need to extend the prototype chain of the subclass to include the superclass. Recall that the prototype for an existing object is inaccessible in JavaScript so we cannot set it directly (excluding non-standard extensions like __proto__
), but we can set the prototype at creation time via either Object.create()
or new
. The key to subclassing is to set Subclass.prototype = Object.create(Superclass.prototype)
:
public class Field { public String value; public static String FONT = "Helvetica"; public Field(String value) { this.value = value; } public String getHTML() { return "<input value=\"" + sanitize(value) + "\">"; } public boolean validate() { return true; } public static Font getFont() { return new Font(FONT); } } public class EmailField extends Field { public String domain; public EmailField(String value, String domain) { super(value); this.domain = domain; } public boolean validate() { return super.validate() && isValidEmail(this.value, this.domain); } }
function Field(value) { this.value = value; } Field.FONT = "Helvetica"; Field.prototype.getHTML = function() { return "<input value=\"" + sanitize(this.value) + "\">"; } Field.prototype.validate = function() { return true; }; Field.getFont = function() { return new Font(Field.FONT); }; function EmailField(value, domain) { Field.call(this, value); this.domain = domain; } EmailField.prototype = Object.create(Field.prototype); EmailField.prototype.validate = function() { return Field.prototype.validate.call(this) && isValidEmail(this.value, this.domain); };
This section contains common JavaScript syntactic shortcuts that you will likely see in the wild. While they are more concise, they will likely be confusing to people reading your code who are not familiar with JavaScript, so use them with caution.
The short-circuit ||
operator is often used to provide defaults for optional values that may be undefined
. This operand evaluates to the right operand if the left operand is false
, null
, undefined
, ""
, or 0
, otherwise it evaluates to the left operand. In the example below, options.host
and options.port
are undefined
when not specified, and so revert to their defaults via the ||
operator:
→ function createURL(path, options) { options = options || {}; var host = options.host || "localhost"; var port = options.port || 80; return { path: path, host: host, port: port }; } → createURL("/"); { path: "/", host: "localhost", port: 80 } → createURL("/dev", { port: 8080 }); { path: "/dev", host: "localhost", port: 8080 }
The short-circuit &&
operator is sometimes used to guard against values that may be null
. This operand evaluates to the left operand if the left operand is false
, null
, undefined
, ""
, or 0
, otherwise it evaluates to the right operand. The code below uses the &&
operator to avoid accessing the name
property of null
(which would cause an error):
→ function getName(obj) { return obj && obj.name; } → getName(null); null → getName({ name: "Steve" }); "Steve"
Casting is sometimes done using the unary +
operator or a double application of the unary !
operator. These cast to a Number
or a Boolean
, respectively:
→ function test(value) { return [Number(value), +value, Boolean(value), !!value]; } → test("52"); [52, 52, true, true] → test(null); [0, 0, false, false]
1 Note that Object.create()
isn't available in versions of IE before IE 9. Luckily we can emulate it using new
, the other way to set an object's prototype. While Object.create()
has more functionality than we used in this article, this replacement will cover setting the prototype:
→ Object.create = Object.create || function(proto) { function proxy() {} proxy.prototype = proto; return new proxy(); };