ALL of the falsy values:
""
0, -0, NaN
null, undefined
false
The empty array is truthy. However, when loosely compared, it is coerced into an empty string, which is falsy. So,
if ([]) { console.log('hey'); } // 'hey'
but
[] == false // true
Array.toString() will simply list contents comma-delimited:
[1,2,3].toString() == "1,2,3" // true
NaN will never be equal to another number.
NaN == 0 // false
NaN == NaN // false
For inequality comparisons (greater than, less than), if either side is not a string, then both sides are coerced into numbers (or 'NaN' if string cannot be cast to a Number).
'let' and 'const' are block scoped in ES6. Blocks are any set of curly braces. Including those for 'if' and 'while' statements:
if (true) { let a = 1; }
if (true) { var b = 2; }
console.log(a); // ReferenceError
console.log(b); // 2
Immediately Invoked Function Expression (IIFE) is the name for functions expressions (not declarations) wrapped in a set of parentheses, and then immediately executed:
(function IIFE() {
var a = 'Hello!';
console.log(a); // Hello!
})();
IIFE(); // ReferenceError
This type of expression prevents polluting the global scope.
The error parameter in the 'catch' of a try-catch block is block scoped and will not pollute the surrounding scope:
try {
undefined();
} catch (err) {
console.log( err ); // valid
}
console.log( err ); // ReferenceError
Variable declarations are hoisted, variable assignments are not:
console.log(a); // undefined (not ReferenceError)
var a = 2;
console.log(a); // 2
foo(); // TypeError (not ReferenceError)
var foo = function() { console.log( 5 ); }
foo(); // 5
Multiple 'var' declarations are ignored:
var a = 2;
var a;
console.log(a); // 2
Function definitions, however, override previous declarations (and variables):
foo(); // 3
function foo() { console.log( 1 ); }
var foo = function() { console.log( 2 ); };
function foo() { console.log( 3 ); }
Closure in a for loop:
// won't work
for (var i = 0; i < 6; i++) {
setTimeout(function() { console.log( i ); }, i * 1000);
} // 6, 6, 6, 6, 6
// will work (IIFE)
for (var i = 0; i < 6; i++) {
(function(j) {
setTimeout(function() { console.log( j ); }, j * 1000);
})(i);
}
// will also work (in ES6)
for (let i = 0; i < 6; i++) {
setTimeout(function() { console.log( i ); }, i * 1000);
}
Singleton pattern:
function Singleton() {
if (Singleton._instance) {
return Singleton._instance;
}
Singleton._instance = this;
}
Singleton.getInstance = function() {
return Singleton._instance || new Singleton();
}
The new arrow functions in JavaScript are not just for letting developers type less. It actually changes how 'this' works in the function. It changes 'this' to use lexical scope rather than the normal rules:
var MyFunc = function() {
this.myNum = 123;
var callMe = function() {
console.log( this.myNum );
}
return { callMe: callMe };
}
var ArrowFunc = function() {
this.myNum = 123;
var callMe = () => {
console.log( this.myNum );
}
return { callMe: callMe };
}
var myFunc = new MyFunc();
myFunc.callMe(); // undefined
var arrowFunc = new ArrowFunc();
arrowFunc.callMe(); // 123
Variables declared on the global scope are placed on the global object as a property.
var a = 2;
console.log( a === this.a ); // true
Default 'this' binding for a function call comes from the call site of the function. This would be any function called without any context.
function foo() { console.log( this.a ); }
var a = 2;
foo(); // 2 - this won't work in NodeJS
In strict mode, if a function is called from global scope, then 'this' will be bound to 'undefined'. In other words, the global scope is not eligible for default binding in strict mode.
'use strict';
function foo() { console.log( this.a ); }
var a = 2;
foo(); // TypeError
Implicit 'this' binding for a function call includes when a function call has context:
function foo() { console.log( this.a ); }
var obj = {
a: 2,
b: 3,
foo: foo,
bar: function() { console.log( this.b ); }
};
obj.foo(); // 2
obj.bar(); // 3
Function calls can also lose their implicit binding:
var obj = {
a: 2,
foo: function() { console.log( this.a ); }
}
var bar = obj.foo;
var a = "whoops"
bar(); // whoops
Explicit 'this' binding for a function includes calling the function using the 'call()' or 'apply()' functions:
function foo() { console.log( this.a ); }
var obj = { a: 2 };
foo.call( obj ); // 2
Hard 'this' binding is explicitly setting the context for which a function can be called. Hard binding is just a variation of explicit binding:
function foo() { console.log( this.a ); }
var obj = { a: 2 };
var bar = foo.bind( obj );
bar(); // 2
'new' 'this' binding is when a new object is constructed and 'this' is bound to the newly created object:
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
Explicit binding takes precedence over implicit binding:
var obj = {
a: 2,
foo: function() {
console.log( this.a );
}
}
obj.foo(); // 2
obj.foo.call({ a: 3 }); // 3
'new' 'this' binding takes precedence over explicit 'this' binding:
var foo = function(a) { this.a = a; };
var obj = {};
var bar = foo.bind( obj );
bar( 2 );
var baz = new bar( 3 );
console.log( bar.a ); // 2
console.log( baz.a ); // 3
In summary, the precedence and rules of 'this' binding can be stated:
- Is the function called with 'new'? If so, 'this' is the newly constructed object.
- Is the function called with 'call()', 'apply()', or 'bind()' ? If so, this is the explicitly specified object.
- Is the function called as a 'child' to an object? If so, 'this' is bound to the parent/owner object.
- Otherwise, 'this' is bound to 'undefined' in strict mode or the global object if not.
Of course, there are some exceptions to these rules...
If 'null' or 'undefined' is passed into 'call', 'apply', or 'bind', then the parameter is ignored and default binding is applied:
function foo() { console.log( this.a ); }
var a = 2;
foo.call( null ); // 2
Passing in 'null' is typical for currying which can be done via 'bind':
function foo(a, b) { console.log( 'a: ', a, ', b: ', b ); }
var bar = foo.bind( null, 2 );
bar( 3 ); // a: 2, b: 3
However, passing in 'null' isn't always the safest. To avoid unwanted error messages, it might be best to pass in an object that is intentionally empty:
function foo(a, b) { console.log( 'a: ', a, ', b: ', b ); }
var empty = Object.create( null ); // alternatively '{}' works
var bar = foo.bind( empty, 2 );
bar( 3 ); // a: 2, b: 3
JavaScript has seven primary types:
string
number
boolean
null
undefined
object
symbol
- added in ES6!
All types except object
are primitive types, however some also have Object pairs.
JavaScript has these built-in objects:
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
Primitive types are not objects themselves:
var strPrimitive = "I am a string";
typeof strPrimitive; // "string"
strPrimitive instanceof String; // false
var strObject = new String( "I am a string" );
typeof strObject; // "object"
strObject instanceof String; // true
and cannot be treated as objects:
var a = 5;
a.foo = 'bar';
console.log( a.foo ); // undefined
but are automatically coerced when performing operations:
var strPrimitive = "I am a string";
console.log( strPrimitive.length() ); // 13
ES6 adds computed property names, where you can specify an expression as a key for an object (surrounded with brackets):
var prefix = "foo";
var obj = {
[prefix + 'bar']: 'hello',
[prefix + 'baz']: 'world'
}
console.log( obj['foobar'], obj['foobaz'] ); // hello world
All properties on an object are stored as strings:
var obj = {};
obj[true] = 'foo';
obj[1] = 'bar';
obj[{}] = 'baz';
obj['true']; // foo
obj['1']; // bar
obj['[object Object]']; // baz
Named properties on an array will not change the length of the array, but numeric properties of any value will:
var arr = ['foo', 'bar', 'baz'];
console.log( arr.length ); // 3
arr.something = 5;
console.log( arr.something ); // 5
console.log( arr.length ); // 3
arr['20'] = true;
console.log( arr[20] ); // true
console.log( arr.length ); // 21
JavaScript object properties have four descriptors: value
, writable
, enumerable
, and configurable
. These can be explicitly set, and read:
var obj = {};
Object.defineProperty( obj, "a", {
value: 2,
writable: true,
configurable: true,
enumerable: true
} );
Object.getOwnPropertyDescriptor( obj, "a" );
// {
// value: 2,
// writable: true,
// configurable: true,
// enumerable: true
// }
To prevent any new properties from being added to an object, call Object.preventExtensions( obj )
.
To prevent any new properties and to set all current properties to non-configurable, call Object.seal( obj )
.
To prevent any new properties and to set all current properties as non-configurable and non-writable, call Object.freeze( obj )
.
Objects can have getters and setters, declared during initialization or by defineProperty
:
var obj = {
get a() {
return this._a_ + 2;
},
set a(a) {
this._a_ = a + 2;
}
};
Object.defineProperty( obj, 'b', {
get: function() { return this._b_ + 3; },
set: function(b) { this._b_ = b + 3; }
});
obj.a = 2;
obj.b = 3;
console.log( obj.a ); // 6
console.log( obj.b ); // 9
To check existence of a property on an object, use in
or hasOwnProperty
. Using in
will consult the prototype chain as well, but hasOwnProperty
will only check the referenced object. Example:
var obj = { a: 2 };
('a' in obj); // true
('b' in obj); // false
obj.hasOwnProperty( 'a' ); // true
obj.hasOwnProperty( 'b' ); // false
Enumerability also affects the keys
function. keys
will only return the properties of an object that are enumerable. To get all of the properties of an object, call getOwnPropertyNames
. However, this doesn't seem that useful since properties are enumerable by default.
ES6 adds a way to iterate through arrays without the need for an index or accidentally iterating over property names by using the of
operator in a for loop:
var arr = [ 1, 2, 3 ];
arr.prop = 'uh oh!';
for (var prop in arr) {
console.log( arr[prop] ); // 1, 2, 3, uh oh!
}
for (var val of arr) {
console.log( val ); // 1, 2, 3
}
Objects do not come with iterators by default but can be manually defined. Note that the Symbol.iterator
property isn't enumerable by default like most properties (magic!):
var obj = {
a: 2,
b: 3,
[Symbol.iterator]: function() {
var o = this;
var idx = 0;
var keys = Object.keys( o );
return {
next: function() {
return {
value: o[ keys[ idx++ ] ],
done: (idx > keys.length)
}
}
}
}
}
var it = obj[Symbol.iterator]();
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: false }
it.next(); // { value: undefined, done: true }
for (var v of obj) {
console.log ( v ); // 2 3
}
Property assignment on an object with a prototype is not as simple as it originally appears. There are rules about property assignment if the object has a prototype with a property of the same name. Consider obj.foo = 'bar'
when foo is not already on obj
directly, but is at a higher level on the prototype chain:
- If
foo
is found on the prototype chain and not marked as read-only (writable: true
), thenfoo
will be added toobj
and will shadow the prototypefoo
. - If
foo
is found on the prototype chain and marked as read-only (writable: false
), then the assignment will silently fail in default mode or throw an error instrict mode
. - If
foo
is found on the prototype chain and it's a setter function, then the setter will always be called. Nofoo
will be added toobj
nor will the setter be redefined.
If you wanted to redefine foo
on obj
in scenario #2 and #3 then you would have to call Object.defineProperty(..)
.
There is no inheritance in JavaScript, only linking. The prototype object that exists on objects is simply a link between other objects - not a copy of behavior.
If you want to create a new object with similar behavior to another object, use Object.create(..)
instead of creating a 'class function' and adding functions to its prototype.
You can get the prototype of an object with Object.getPrototypeOf(..)
. You can also check if an object is the prototype of another with isPrototypeOf()
:
var Foo = function() {};
var bar = new Foo();
console.log( Foo.prototype.isPrototypeOf( bar ) ); // true
Object.create(..)
also takes a second parameter which specifies property names to add to the new object:
var other = { a: 2 };
var obj = Object.create( other, {
b: { value: 3 },
c: { value: 4 }
});
obj.hasOwnProperty( 'a' ); // false
obj.hasOwnProperty( 'b' ); // true
obj.hasOwnProperty( 'c' ); // true
obj.a; // 2
obj.b; // 3
obj.c; // 4
The author argues that behavior delegation is a more appropriate paradigm for JavaScript than inheritance. The author seems to be correct since trying to force inheritance does require a little hackery.
OLOO (objects-linked-to-other-objects) is the alternative to OOP and looks a little something like this:
var Task = {
setId: function(id) { this.id = id; },
outputId: function() { console.log( this.id ); }
};
var LabeledTask = Object.create( Task );
LabeledTask.prepareTask = function(id, label) {
this.setId( id );
this.label = label;
};
LabeledTask.outputTaskDetails = function() {
this.outputId();
console.log( this.label );
};
Another example of using OLOO:
var Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
var Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
}
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
To safely check if a variable has been declared or not, use the typeof
operator. This is useful for creating a JS library:
var a = 5;
typeof a !== "undefined"; // true
typeof b !== "undefined"; // false
a ? true : false; // true
b ? true : false; // ReferenceError: b is not defined
undefined
can be reassigned globally in non-strict mode and locally in strict mode:
undefined = 2;
console.log( undefined ); // 2
'use strict';
function foo() {
var undefined = 2;
console.log( undefined ); // 2
}();
undefined = 2; // TypeError
However, doing this is a terrible idea. Don't do it. Ever.
Use the void
operator to void any expression of its value:
void 0; // undefined
void true; // undefined
void 'hello!'; // undefined
function () { return void someFunc(); }(); // undefined
This knowledge is also not incredibly useful.
NaN
is the only value in javascript that is not reflexive: NaN !== NaN
. Also, the type of NaN is number
. So, the way to check for NaN
is using the global utility function isNaN(..)
. However, this function doesn't just check for NaN
, it will return true for anything that isn't a number (string, boolean, etc).
Luckily, ES6 fixes this problem with Number.isNaN(..)
. It will only return true for the NaN
value.
Overflowing a numeric value in JavaScript will result in Infinity
, aka Number.POSITIVE_INFINITY
:
var a = Number.MAX_VALUE;
a + a; // Infinity
Other rules:
Infinity / Infinity
is equal toNaN
- A finite number divided by
Infinity
is0
- Any positive number divided by
0
isInfinity
- Any negative number divided by
0
is-Infinity
JavaScript has the concept of negative zero (-0
) and it's weird and confusing. Just read the book chapter if you want to study it.
Rules for passing/assigning by value/reference:
- Primitives (
null
,undefined
,string
,number
,boolean
, andsymbol
) are always by value. - Compound values (
object
,array
, andfunction
) are always by reference.
Example:
var a = 2;
var b = a;
b++;
a; // 2
b; // 3
var c = [1, 2, 3];
var d = c;
d.push( 4 );
c; // [1, 2, 3, 4]
d; // [1, 2, 3, 4]
To do a shallow copy of an array (in order to pass by value), use the slice(..)
function with no parameters:
var numHolder {
_nums_: [1, 2, 3],
get nums() {
return this._nums_.slice();
}
}
numHolder.nums.push(4);
numHolder.nums; // [1, 2, 3]
Putting a toString
function on any object will allow that method to be automatically called when coerced to a string.
var obj = {
name: 'Nick',
occupation: 'Developer',
toString: function() {
return 'Hi my name is ' + this.name + ' and I am a ' + this.occupation;
}
}
console.log( String( obj ) ); // Hi my name is Nick and I am a Developer
Putting a toJSON
function on any object will allow that method to be automatically called when JSON.stringify
is used on the object:
var obj = {
secret: 'no one can know!',
data: 42,
toJSON: function() {
return { data: this.data }; // exclude secret!
}
}
console.log( JSON.stringify( obj ) ); // {"data":42}
This is also useful for removing circular references since toJSON
will throw an error if any circular references exist.
When coercing to a number, values behave in the following way:
boolean
-true
becomes1
andfalse
becomes0
undefined
- becomesNaN
null
- becomes0
string
- an attempt at parsing the string into a number is made. SoNumber( "42" ) === 42
but any invalid string will produce NaN:Number( "42c" ); // NaN
object
(array
andfunction
) - An attempt will be made to convert the object into a primitive, first usingvalueOf
and then usingtoString
. Then an attempt will be made to coerce that primitive into a number using the other rules.
Values can be coerced to a number
by using the Number(..)
function.
When coercing to a boolean, there is a finite list of items that return false
. All other values return true
. The falsy list:
undefined
null
false
0
NaN
""
Values can be explicitly coerced into a Boolean by using the Boolean(..)
function. However, a more idiomatic approach might be using double negatation: !!
.
An idiom for coercing a -1
sentinel value into false
is using the bitwise NOT operator ~
:
var a = 'Hello World!';
if (a.indexOf( "lo" ) >= 0) {
console.log( 'Found it!' ); // Found it!
}
if (~a.indexOf( "lo" )) {
console.log( 'Found it!' ); // Found it!
}
The function parseInt
(and parseFloat
) should only be called with a string. Once called, it will parse the value until it reaches a non-numeric character and return the result:
parseInt( '42px' ); // 42
parseInt
and (parseFloat
) can also take a second parameter which specifies which numeric base to use.
If parseInt
(or parseFloat
) is called with a non-number
then that value will be coerced into a string
and then parsed. This is a common source of confusion (and comedy):
parseInt( 1/0, 19 ); // 18
parseInt( 0.000008 ); // 0 ("0" from "0.000008")
parseInt( 0.0000008 ); // 8 ("8" from "8e-7")
parseInt( false, 16 ); // 250 ("fa" from "false")
parseInt( parseInt, 16 ); // 15 ("f" from "function..")
parseInt( "0x10" ); // 16
parseInt( "103", 2 ); // 2
The difference between ==
(loose equality) and ===
(strict equality) is that loose equality allows coercion and strict equality does not. A little known fact is that both operators perform the same for object
types, returning true only if both values have the same reference.
For equality checks, null
and undefined
are equal with loose equality.
var a = null;
var b;
a == b; // true
b == a; // true
This is useful if you don't want to distinguish between null and undefined for a logical branch.
There are seven gotchas to implicit coercion comparison:
"0" == false; // true
false == 0; // true
false == ""; // true
false == []; // true
"" == 0; // true
"" == []; // true
0 == []; // true
Safely using implicit coercion (in comparison):
- If either side of the comparison can have
true
orfalse
, don't ever use==
. - If either side of the comparison can have
[ ]
,""
, or0
values, seriously consider not using==
.
JavaScript has labeled blocks which can be used with break
and continue
, however they are highly discouraged:
outer: for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
if (i == j) {
continue outer;
}
console.log(i, j);
}
}
// 1 0
// 2 0
// 2 1
block: {
console.log( 'Hello, ' ); // Hello,
break block;
console.log( 'never runs..');
}
console.log( 'World!' ); // World!
Object destructuring is going to be a thing in ES6:
function getData() {
return {
a: 42,
b: 'foo'
};
}
// shorthand for `var { a: a, b: b } = getData();`
var { a, b } = getData();
console.log( a, b ); // 42 foo
Object destructuring will also work in function arguments:
function foo({ a, b, c }) {
console.log( a, b, c) // foo bar baz
}
foo( {
a: 'foo',
b: 'bar',
c: 'baz'
} );
HTML DOM elements with an id
attribute create global javascript variables with the same name.
...
<div id="whoa"></div>
...
console.log( whoa ); // <div></div>
Rules around extending native objects:
- Don't extend natives
- If you do extend natives, at least guard against future additions:
if (!Array.prototype.push) {
// Limited version of 'push'
Array.prototype.push = function(item) {
this[this.length] = item;
}
}
Generators are pretty cool:
function *generator() {
for (var i = 0; true; i++) {
yield i;
}
}
var it = generator();
it.next().value; // 0
it.next().value; // 1
it.next().value; // 2
Generators can be used with promises to create async code that looks sychronous:
var http = require( 'http' );
function getJoke() {
var promise = new Promise(function ( resolve, reject ) {
http.get({
host: 'api.icndb.com',
path: '/jokes/random'
}, function( res ) {
res.on( 'data', function( chunk ) {
resolve( JSON.parse( chunk.toString() ) );
} );
} );
} );
return promise;
}
/* It looks so synchronous! Just get the joke, then print it */
function* main() {
try {
var res = yield getJoke();
console.log( res.value.joke );
} catch (e) {
console.log( e );
}
}
var it = main();
var promise = it.next().value;
promise.then( function resolved( data ) {
it.next( data );
} );
Generators can also delegate to other generators:
function* foo() {
var b = yield "B";
console.log( b ); // 2
return "C";
}
function* bar() {
var a = yield "A";
console.log( a ); // 1
var b = yield* foo(); // notice * syntax
console.log( b ); // C
return "D";
}
var it = bar();
it.next().value; // A
it.next( 1 ).value; // B
it.next( 2 ).value; // D
Tail call optimization is when a function call is the last expression in a function. This can be optimized because the engine can use the current stack (because it's empty) instead of creating a new stack for the function. This eliminates the 'stack overflow' problems.
function factorial(n) {
function fact(n, res) {
if (n < 2) return res;
return fact( n - 1, n * res );
}
return fact( n, 1 );
}
factorial( 5 ); // 120
ES6 introduces
default function parameters
function foo( x = 5, y = 10 ) {
console.log( x + y );
}
foo( 7 ); // 17
object destructuring (pattern matching) with default values
var [ a, b ] = [ 1, 2 ];
var { x, y, z: Z = 5 } = { x: 3, y: 4 };
console.log( a, b, x, y, Z ); // 1 2 3 4 5
smart strings (backtics)
var name = "Nick";
console.log( `${name} works ${6 + 2} hours` );
Note: Smart strings finally allow multiline strings in JavaScript. However, the newlines will appear in the final string, unless you use a tagged literal:
function multiline(strings, ...values) {
return strings.reduce( function( s, v, idx ) {
if (idx > 0) {
s += values[ idx - 1 ];
}
return s + v.replace( /\n/g, ' ' );
}, '');
}
multiline`Look at how cool
this thing is! it is amazingly cool
and I can even include interpreted
${ 'values' } in this string`;
// "Look at how cool this thing is! it is amazingly cool and I can even include interpreted values in this string"