These are notes on the book
Javascript the Good Parts
which was published in 2008, before es6. There have not been any revised editions of the book published at the time of writing, see https://www.github.com/dwyl/Javascript-the-Good-Parts-notes/issues/33 for the ongoing conversation on this.
This book calls out the best parts of Javascript and tells you what to avoid (the 'bad parts'). It's about making sure you know the really important parts of the language and create good habits instead of having to break bad ones down the line.
The idea of these notes is to collect the excellent information from an already dense book into note form, translating these ideas into 'plain English' where possible and adding explanations throughout where the book might not make things obvious as it assumes prior knowledge.
You can buy the book or follow Douglas Crockford on Github.
Please don't hesitate to submit PRs to improve these notes wherever you feel something can be clarified, improved on or have an example added to it.
Most programming languages contain good parts and bad parts. I discovered that I could be a better programmer by using only the good parts and avoiding the bad parts. After all, how can you build something good out of bad parts?
The best parts of Javascript include:
The worst parts include global variables - there is a common global object namespace where they're all lumped together and they're essential to the language.
Javascript has a class free object makeup, relying instead on objects inheriting properties directly from other objects - this is prototypal inheritance.
Always use // for comments, even multi-line ones to avoid having to escape /*
characters.
NaN
(Not-a-Number) is not equal to any value (including itself) and is essentially an illegal number value, but typeOf(NaN)===number is trueisNaN(number)
to check for NaNsNumber methods are discussed in Chapter 8.
Single quotes are often used to define a String in JavaScript, but if a person's name has an apostrophe (and the developer does not know the difference between an apostrophe and single quote) it is useful to "escape" the apostrophe character:
var name = 'Patrick O\'Brian'; // using a backslash in front of the apostrophe
console.log('name:', name); // name: Patrick O'Brian
further reading: https://webdesignledger.com/common-typography-mistakes-apostrophes-versus-quotation-marks
String methods are discussed in Chapter 8.
break
and continue
to provide more precise control over exactly which statement to break or continue. Format: labelname: statement
and then continue labelname;
case
statements, the optional default statement is executed, otherwise the matching case statement is carried outhasOwnProperty(variable)
to make sure the property belongs to the object you want and is not instead an inherited property from the prototype chain:
for (myvariable in object) {
if (object.hasOwnProperty(myvariable)) {
... //statements to be executed
}
}
catch
clause in a try statement must create a new variable that will catch the exception objectthrow
statement is the try
block it's in, or the try
of the function it's inreturn
statement, return===undefined
break
exits the statement and continue
forces a new iteration of the loop, both with the optional label mentioned aboveexpression ? expression2 : expression3
, if expression is truthy, execute expresion2; if it's falsy, execute expression3(expression1, expression2, etc)
.name
or [expression]
as used in an arrayJavascript simple types:
All other values are objects including arrays and functions.
Objects are class free, can contain other objects and can inherit properties from their prototypes (which can reduce object initialisation time and memory consumption).
var empty_object = {};
var today = {
day: "Wednesday",
month: "April",
year: 2014,
weather: { //objects can contain nested objects like this one
morning: "sunny",
afternoon: "cloudy"
}
}
today.weather.morning
or with square brackets today['month']
var weath = today.weather.evening || "unknown"
Object.create
method is now available in ES5 (but the method is in the book if required for older versions)More details in Chapter 6
typeof
includes all properties in the prototype chain including functionshasOwnProperty(type);
which returns true if that property exists only in that object itself (not the chain)
today.hasOwnProperty('number') //will return true
today.hasOwnProperty('constructor') //will return false
var i;
var properties = [
'day', 'month', 'year'
];
var today = {
day: '5',
month: 'September',
year: '2001'
};
for (i = 0; i < properties.length; i++) {
document.writeIn(properties[i] + ':' + today[properties[i]]);
}
//OUTPUT
// day:5
// month:September
// year:2001
delete today.month
var MYAPP = {}
MYAPP.today = {
day: "Wednesday",
month: "April",
year: 2014,
weather: { //objects can contain nested objects like this one
morning: "sunny",
afternoon: "cloudy"
}
}
//Making sure all other variables (like today) are contained within this one global variable (MYAPP) means none of them have global scope and therefore the risk of naming conflicts, etc in your application is reduced
The best thing about JavaScript is its implementation of functions.
function
itself(parameters)
{statements}
//Format of a function
function name (parameterA, parameterB){
statements;
}
Stops the current function from running and tells the function you have invoked both to start and to use the arguments (values in parentheses) you have passed it in the invocation function (parameters)
Note: The difference between an argument and a parameter is that a parameter is usually what is used in the function literal, when you're setting up the function (almost like the placeholder for the actual values that the function will use when it is active) and an argument is usually the value passed to a function at the time it is invoked
Parameters this
and arguments
are also passed to the function when it is invoked, but their value depends on how the function is invoked
myObject.incrementFunction();
this
to retrieve or update values from the objectthis
they are considered public methodsvar sum = add(3, 4);
this
even in inner functionsthis
within an inner function will therefore refer to its own this
and not the one in global scopeWorkaround: Artificially create a new this
:
myObject.double = function() {
//in the book, the var here is called `that` but name changed for clarity
var globalScopeThis = this; //workaround
var innerFunc = function() {
globalScopeThis.value = add(globalScopeThis.value, globalScopeThis.value);
};
innerFunc(); //invoke innerFunc as function
};
myObject.double();
console.log(myObject.value);
new
, that function contains a link to the function's prototypenew
//create a function Quo that takes a string - Quo will be our prototype function as we will see
var Quo = function (string){
this.status = string;
}
//Now create a get_status method for Quo - this will be a public method
Quo.prototype.get_status = function () {
return this.status;
}
//create a new instance of Quo using the prefix NEW
var myQuo = new Quo("happy");
//because of the use of the new prefix, myQuo is an instance of Quo which means it can access the public method get_status from it's prototype
document.writeIn(myQuo.get_status()); //returns 'happy'
apply
method lets you choose the value to be bound to this
function.apply(valueForThis, arrayOfParamentersForFunction);
var array = [5, 2] //will be the parameters for our function
var sum = add.apply(null, array); //value of 'this' is null and value of sum is 7 as the 'apply' method passes 5 and 2 to the 'add' method
arguments
array which contains all the arguments that were supplied when the function was invokedarguments
array
//inside the function
for (i = 0; i < arguments.length; i++) {
dosomething; //e.g. sum +=arguments[i]
}
arguments
lacks all the array methods except .length because of a bugreturn
statement, it returns immediately without carrying out the remaining statements in the functionvalue
or if unspecified, it returns undefined
new
prefix (used when creating a new object so it must return an object) and the return
value is not an object, then this
(the new object) is returned instead."throw
statement interrupts the execution of the code is used to handle expected exceptions like an incorrect type of argument (e.g. a string where a number is expected)throw
statement should have an exception object with a name
holding the type of exception and a message
with an explanation of it + any other properties you like
//Thinking through what exceptions could happen in an add function, the main function contains the throw statement with the exception object
var add = function (a,b) {
if (typeof a !== 'number' || typeof b !== 'number'){
throw {
name: 'TypeError';
message: 'The add function requires numbers';
}
}
return a + b;
}
try
block where the exception object from the throw
statement in add() will pass control to a single catch clause for all exceptions//When you use the function later on, add a try block with a catch clause to catch the exception object
var try_it = function () {
try{
add("seven"); //will throw an exception as it is not a number
}
catch (e) {
document.writeIn(e.name + ':' + e.message);
}
}
try_it(); //you could rewrite this function so the argument is passed in here where it is invoked
Object.prototype
(or function, array, string, number, regular expression or boolean), you make it available to all the instances of that object so you don't have to use the prototype
property againString.method ('trim', function () {
return this.replace(/ˆ\s+|\s+$/g, ''); //uses regular expression
});
//Makes a method available to all functions, ONLY when it definitely does not already exist
Function.prototype.method (methodName, func) {
if (!this.prototype[methodName]){
this.prototype[methodName] = func;
return this;
}
};
var variable = function functionName (parameters){
//wrap the statements to be executed and the recursive call in a loop statement so it doesn't recurse forever
//statements to be executed in the function;
functionName(arguments);
};
functionName (initialArguments); //initial call to the function
request = prepare_the_request();
send_request_asynchronously(request, function(response){ //function being passed in as a parameter
display(response);
});
It can also be used to produce secure objects (see durable objects below)
this
or that
so it becomes impossible to change them from outside of the object except in ways explicitly permitted by the methods (like passing them a parameter)
var Serial_maker = function() {
//all variables defined in this object are now fixed and hidden from anything outside this function //see page 42 of book for full example }; //calls to methods passing them parameters are made here
undefined
this
instead of undefined
, they return the object which can then be passed to the next method, e.g getElement(myBox).move(350,150)
gets the element and then passes is to the move function for the next action
.
as above)curry
method allows you to partially evaluate an existing function
curry
as in add.curry(10);
) and then later passed the second argumentadd(a,b)
) into a chain of functions that take a single argument each (addA = add(A); addA(B);
where the two functions are now add()
& addA()
)//set up a simple function that we will customise with curry
var add = function (a,b){
return a + b;
}
var addTen = add.curry(10); //passes 10 as the first argument to the add() function
addTen(20); //The use of the curry method in addTen means addTen === add(10, 20);
curry
method natively but this can be added to the Function.protoype
:
Function.method('curry', function() {
var slice = Array.prototype.slice,
args = slice.apply(arguments),
that = this;
return function () {
return that.apply(null, args.concat(slice.apply(arguments)));
}
});
var memoizer = function (memo, fundamental) {
var shell = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fundamental(shell, n);
memo[n] = result;
}
return result;
}
return shell;
}
Javascript is a prototypal language, which means that objects inherit directly from other objects
Main benefit of inheritance is code reuse - you only have to specify differences.
Javascript can mimic classical inheritance but has a much richer set of code reuse patterns
The pseudoclassical code reuse pattern essentially has constructor functions (functions invoked using the new
prefix) work like classes to mimic the classical structure
new
prefix, this
is not bound to the new object - it is instead bound to the global object and you'll be unwittingly altering these instead!There is no need to use it, there are better code reuse patterns in JavaScript
Rather than: var myObject = maker (f, l, m, c, s)
which has too many parameters to remember in the right order, use an object specifier:
var myObject = maker ({ //note curly braces
first: f,
last: l,
state: s,
city: c
}
;)
to contain them. They can now be listed in any order
Also useful to pass object specifiers to JSON (see Appendix E notes)
var myObject = Object.create(originalObjectName)
name
and saying
properties are now completely private and only accessible to the get_name
and says
methodvar mammal = function (spec) {
var that = {}; //that is a new object which is basically a container of 'secrets' shared to the rest of the inheritance chain
that.get_name = function () {
return spec.name;
};
that.says = function () {
return spec.saying || ''; //returns an empty string if no 'saying' argument is passed through the spec object when calling mammal
};
return that; //returns the object that contains the now private properties and methods (under functional scope)
}
var myMammal = mammal({name: 'Herb'});
Creating an object 'cat' can now inherit from the mammal
constructor and only pay attention to the differences between it and mammal
:
var cat = function (spec) {
spec.saying = spec.saying || 'meow'; //if spec.saying doesn't already exists, make it 'meow'
var that = mammal(spec); //here the object 'container of secrets' is set up inheriting from mammal already
//functions and property augmentations happen here
return that; //as above
}
this
or that
is a durable object and cannot be compromised by attackers
that
bundle (i.e. your 'container of secrets')Javascript only has array-like objects which are slower than 'real' arrays.
Retrieval and updating of properties works the same as with an object except with integer property names.
Arrays have their own literal format and their own set of methods (Chapter 8 - Methods).
[a, b, c, etc]
length
property will increase to contain the new element - it will not give an error.length
to a smaller number than the current length of the array, it will delete any properties with a subscript >= the new length
push()
method is sometimes useful to add an element to the end of an array
numbers.push('go') //adds the element 'go' to the end of the numbers array
delete
but this leaves a hole in the arrayarray.splice(keyInArray, howManyElementsToDelete)
which changes the keys for the remaining values in the array so there is no hole left
for
statement can be used to iterate over all the properties of an array (as it is an object)for in
as it does not iterate through the properties in order and sometimes pulls in from further up the prototype chainThe rule is simple: when the property names [keys] are small sequential integers, you should use an array. Otherwise, use an object.
typeof array === object
var is_array = function (value) {
return Object.prototype.toString.apply(value) === '[object Array]';
//apply(value) binds `value` to `this` & returns true if `this` is an array
}
Array.prototype
which can be augmented using the format:
//capital A in Array means this refers to the prototype
Array.method('reduce', function (parameters){
//define variables and function
//return a value
});
Array.prototype
myArray.total = function () { //statements to execute; }
adds a 'total' function to the array myArray
Object.create()
will create an object - lacking the length
property - not an array.[]
will create an empty array as they are not initialized in JavaScriptundefined
undefined
values, you can write a function that will prep your array to have a certain number of defined values, essentially initializing it with certain values in place
Array.dim
function is outlined on page 63 which will allow var myArray = Array.dim(10,0)
to make an array with 10 zeroes starting from the first position in the array(0)A regular expression is the specification of the syntax of a simple language
Used with regexp.exec
, regexp.test
, string.match
, string.replace
, string.search
and string.split
to interact with string (more in Chapter 8 - Methods)
Quite convoluted and difficult to read as they do not allow comments or whitespace so a JavaScript regular expression must be on a single line
/ˆ(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([ˆ?#]*))?(?:\?([ˆ#]*))?(?:#(.*))?$/
Breaking it down one portion (factor) at a time:
Note that the string starts and ends with a slash /
ˆ
indicates the beginning of a string
(?:([A-Za-z]+):)?
(?:...)
indicates a noncapturing group, where the '...' is replaced by the group that you wish to match, but not save to anywhere?
indicates the group is optional, so it could or could not exist in the string - it could even exist more than once()
around the ([A-Za-z]+) indicates a capturing group which is therefore captured and placed in the result
array
result[1]
(?:Bob says: (\w+))
[...]
indicates a character classA-Za-z
is a character class containing all 26 letters of the alphabet in both upper and lower case+
means character class will be matched one or more times:
is matched literally (so the letters will be followed by a colon in this case)(\/{0,3})
\/
The backslash \
escapes the forward slash /
(which traditionally symbolises the end of the regular expression literal) and together they indicate that the forward slash /
should be matched{0,3}
means the slash /
will be matched between 0 and 3 times([0-9.\-A-Za-z]+)
+
at the end denoting possible multiple occurrences) digits, letters (upper or lower case), full stops (.) or hyphens (-)
\-
as hyphens usually denote a range but in this case is a hyphen within the expression(?::(\d+))?
\d
represents a digit character so this will be a sequence of one or more digit characters (as per the +
):
(\d+)
will be the fourth capturing group in this expression, it is also optional (?
) and inside a non-capturing group (?:...)
(?:\/([ˆ?#]*))?
?
), beginning with a literal slash /
(escaped by the backslash)ˆ
at the beginning of character class [ˆ?#]
means it includes all characters except ? and #
*
indicates the character class will appear zero or more times(?:\?([ˆ#]*))?
?
(escaped by the backslash) with zero or more characters that are not #(?:#(.*))?
#
.
matches any character except a line ending character$
represents the end of a string
Note: ˆ
and $
are important because they anchor the regexp and checks whether the string matched against it contains only what is in the regexp
ˆ
and $
weren't present, it would check that the string contained the regexp but wouldn't necessarily be only made up of thisˆ
checks the string starts with the regexp$
checks the string ends with the regexpAnother example
/ˆ-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;
Most of this we have seen before but here are the new bits:
i
at the end means ignore case when matching letters-?
means the minus sign is optional(?:\.\d*)
matches a decimal point followed by zero or more digits (123.6834.4442284 does not match)3 flags exist in regular expressions: i
means insensitive - ignore the character case, g
means global - to match multiple items and m
means multiline - where ˆ and $ can match line-ending characters
Two ways to build a regular expression:
/
/i
RegExp
objects made by regular expression literals share a single instanceRegExp
constructor
RegExp
object, the second is the flag//example creating a regular expression object that matches a JavaScript string
var my_regexp = new RegExp("'(?:\\\\.|[ˆ\\\\\\'])*'", 'g');
|
provides a match if any of the sequences provided match.
In "into".match(/in|int/);
, the in will be a match so it doesn't even look at the int.
A regexp sequence is made up of one or more regexp factors. If there are no quantifiers after the factor (like ?
, *
or +
), the factor will be matched one time.
A regexp factor can be a character, a parenthesized group, a character class, or an escape sequence.
It's essentially a portion of the full RegExp
, like what we broke down the regexp above into.
\
to be taken literally, or they will take on an alternative meaning: \ / [ ] ( ) { } ? + * | . ˆ$\
prefix does not make letters or digits literal.
matches any character except line-endingˆ
matches the beginning of the text when lastIndex
property is zero, or matches line-ending character when the m
flag is presentˆ
inside a character class means NOT, so [ˆ0-9] means does not match a digit$
matches the beginning of the text or a line-ending character when the m
flag is presentAs well as escaping special characters in regexp factors, the backslash has additional uses:
\f
is the formfeed character, \n
is new line, \r
is carriage return, \t
is tab and \u
specifies Unicode as a 16-bit hex. But \b
is not a backspace character\d
=== [0-9] and \D
is the opposite, NOT (ˆ) a digit, [ˆ0-9]\s
matches is a partial set of Unicode whitespace characters and \S
is the opposite\w
=== [0-9A-Za-z] and \W
=== [ˆ0-9A-Za-z] but useless for any real world language (because of accents on letters, etc)\1
refers to the text captured in group 1 so it is matched again later on in the regexp
\2
refers to group 2, \3
to group 3 and so on*\b
is a bad part. It was supposed to be a word-boundary anchor but is useless for multilingual applications
Four kinds of groups:
(...)
where each group is captured into the result
array - the first capturing group in the regexp goes into result[1]
, the second into result[2]
and so on(?:...)
where the text is matched, but not captured and saved anywhere, making is slightly faster than a capturing group (has no bearing on numbering of capturing groups)(?=...)
acts like a noncapturing group except after the match is made, it goes back to where text started(?!...)
is like a positive lookahead but only matches if there is no match with what is in it[]
, for example vowels: [aeiou]
ˆ
as the first character after the opening [
to mean NOT the characters in the character setThere are specific characters that must be escaped in a character class: - / [ \ ] ˆ
A quantifier at the end of a factor indicates how many times the factor should be matched
/o{3}
matches ooo{3,5}
indicates it will match 3, 4 or 5 times?
or {0,1}
*
or {0,}
+
or {1,}
Prefer to use 'zero or more' or 'one or more' matching over the 'zero or one' matching - i.e. prefer greedy matching over lazy matching
Produces new array copying the original array with the items
appended to it (does not modify the original array like array.push(item)
does). If the item
is an array, its elements are appended.
Creates a string of all the array's elements, separated by the separator
. Use an empty string separator
('') to join without separation.
Removes last element of array. Returns undefined
for empty arrays.
Modifies the array, appending items
onto the end. Returns the new length
of the array.
Modifies the array by reversing the order of the elements.
Removes the first element of the array (does not leave a hole in the array - same effect as using the .splice(a,b)
method) and returns that first element.
Different to splice
.
'slice' creates a new array, copying from the start
element and stopping at the element before the end
value given. If no end
is given, default is array.length
.
Negative values for start
and end
will have array.length
added to them and if start
>end
, it will return an empty array.
JavaScript has a sort()
method which was created only to compare strings and therefore sorts numbers incorrectly (it will sort them as 1, 15, 2, 23, 54 for example). Therefore, we have to write a comparison function which returns 0 if the two elements you are comparing are equal, a positive number if the first element should come first and a negative number if the second element should come first. Then pass this comparison function to sort()
as a parameter to allow it to sort array elements intelligently.
Page 80-82 in the book takes you through various iterations of the comparison functions - for numbers, simple strings, objects and objects with multiple keys (for example if you want to sort objects by first and last names). These should be taken from the book when required.
Removes elements from the array making sure there are no holes left in the array. It is most popularly used for deleting elements from an array.
It removes the deleteCount
number of elements from the array starting from the start
position. If there are item
parameters passed to it, it will replace the deleted elements in the array with the items
.
It returns an array containing the deleted elements.
Works like push
but adds items to the front of the array instead of the end. Returns the new length
of the array.
The apply
method invokes a function, passing in the object that will be bound to this
and optional array of arguments.
Converts number to a string in exponential form (e.g. 3.14e+0). fractionDigits
(from 0 to 20) gives the number of decimal places.
Converts number to a string in decimal form (e.g. 3.1415927). fractionDigits
(from 0 to 20) gives the number of decimal places.
Converts number to a string in decimal form (e.g. 3.1415927). The difference from toFixed
is that precision
(from 0 to 21) gives the number of total digits.
Converts number to a string. radix
is an optional parameter between 2 and 36 and gives the base. The default radix is 10.
Does not look at the property chain. Returns true if the object contains the property name
.
Most powerful (and slowest) regexp method.
Checks the string
against the regexp (starting at position 0) and returns an array containing the matches.
The regexp is set up with various capturing groups and these determine the elements that go in the array:
string
that matched the regexpnull
If the regexp contains a g
flag (e.g. var regexp = /[ˆ<>]+|<(\/?)([A-Za-z]+)([ˆ<>]*)>/g;
), there is a lot more to look out for:
regexp.lastIndex
(initially zero)lastIndex
becomes the position of the first character of the matchlastIndex
is reset to zeroexec
in a loop, ensure you reset lastIndex
when exiting the loop and remember ˆ
only matches when lastIndex
is equal to zeroExample on page 87 of the book is worth reading to improve understanding.
Simplest (and fastest) regexp method.
If regexp matches the string
it returns true. Otherwise it returns false.
Do not use the g
flag with this method.
Returns character at position pos
in the string starting from 0. If pos
is less than zero or bigger than the string itself it return an empty string.
Same as charAt
except it returns the integer that represents the code point value of the character at position pos
.
Returns NaN
if string.length < pos
< 0.
Creates new string concatenating various strings. +
tends to be used instead of this method (e.g. var cat = 'c'+'a'+'t';
)
Searches for searchString
within string starting at position position
(an optional parameter). If position
is not provided, search starts at the beginning of the string.
Returns the integer position of the first matched character or -1 if no match is found.
Same as indexOf
but searches from the end of the string instead of the beginning.
Compares string to that
parameter and returns:
that
that
NB. 'a' < 'A', comparison is not just in length.
Works the same way as regexp.exec(string)
if there is no g
flag in the regexp
.
If there is a g
flag in teh regexp
, it produces an array of the matches but excludes the capturing groups
Searches for the searchValue
in string and replaces it with the replaceValue
.
If searchValue
is a:
replaceValue
replaceValue
; otherwise, only the first occurrence will be replacedIf replaceValue
is a:
$
value has a special meaning when used in the replaceValue
that conveys what to replace - see table on page 90 for possible variations on $
Similar to .indexOf(string)
but takes a regexp
instead of a string
, returning the position of the first match (or -1 if there is no match).
The g
flag is ignored.
Creates a new string by copying the characters from the start
position to the character before the end
position in string.
The end
parameter is optional and defaults to string.length. If either parameter is negative, string.length is added to it.
Creates an array of strings by splitting apart string at the points where the separator
appears (e.g. if the separator is '.', ab.cd' becomes ['ab', 'cd']).
limit
is optional and determines how many pieces are to be split off from the original string.separator
can be a regexp
but
var e = text.split(/\s*(,)\s*/);
the commas (,) will each be included as a separate element in the resulting arrayseparator
is a regexp
No reason to use, use slice
instead.
Produces a new string converted to lower case, using the rules for the particular locale (geography).
Produces a new string converted to upper case, using the rules for the particular locale (geography).
Produces a new string converted to lower case.
Produces a new string converted to upper case.
Produces a new string from a series of numbers.
var a = String.fromCharCode(67, 97, 116); //a === 'Cat'
NB. You're calling the prototype here, not replacing 'String' with your own variable.
JavaScripts's loose typing and excessive error tolerance provide little compile-time assurance of our programs' quality, so to compensate, we should code with strict discipline.
We should avoid the bad parts of JavaScript, but also the useful parts that can be occasionally dangerous
the likelihood a program will work [as intended] is significantly enhanced by our ability to read it
Must be written in a clear, consistent style, including:
if
and while
to avoid confusion on what the statement is actually doing
{
on the same (first) line as the statement to avoid JavaScript's semicolon insertion issues - i.e if (a) { ...
//comment
and not block commenting (unless you're commenting out code)I use a single global variable to contain an application or library. Every object has its own namespace, so it is easy to use objects to organize my code. Use of closure provides further information hiding, increasing the strength of my modules.
Each feature you add to something has a lot of different costs (documentation costs, specification, design, testing and development costs) and these are often not properly accounted for.
Features that offer value to a minority of users impose a cost on all users
We cope with the complexity of feature-driven design by finding and sticking with the good parts. For example, microwaves do a ton of different things, but most people just use one setting, the timer and the clock. So why not design with just the good parts?
Need to know what all the pitfalls are with these parts.
These are variables that are visible throughout the code in any scope. They can be changed at any time by any part of the program which makes them unreliable in larger complex programs. This can also lead to naming conflicts which can cause your code to fail or you to accidentally overwrite a global variable.
Defined in three ways:
var
statement outside of any function; var foo = value
;window
in browsers; window.foo = value;
var
, which makes it an implied global; foo = value
Although JavaScript has block syntax (i.e. is written in blocks) like a lot of other programming languages, it has functional scope and not block scope.
Variables should all be declared at the top of the function and not littered throughout the block.
Attempts to correct faulty programs by automatically inserting semicolons. Do not depend on this as it can hide underlying issues.
Also ensure opening curly braces ({) are on the first line of a statement, otherwise semicolons will be erroneously inserted and cause problems:
//Ensure curly braces open on the first line of a statement
return {
status: true //for example
};
//instead of
return
{
status:true
};
Most JavaScript reserved words are not used in the language but cannot be used to name variables or parameters.
If used as the key in object literals, they must be quoted. For example object - {'case' : value};
or object['final'] = value;
as case and final are both reserved words.
JavaScript characters are 16 bits which only cover the original Unicode Basic Multilingual Place.
Watch out for:
typeof null
which returns 'object' instead of 'null'typeof array
will return 'object'All object
s are truthy and null
is falsy, so you can use the following to tell them apart:
if (my_value && typeof my_value === 'object') {
//then my value is definitely an object or an array because not only is its 'typeof' an object but it's also truthy (first statement)
}
typeof NaN === 'number'
even though it stands for not-a-numberNaN
then at least one of them will have generated NaN
NaN !=== NaN
isNaN(value)
can be used to distinguish numbers from NaNFor numbers, best use your own isNumber formula:
var isNumber = function isNumber(value) {
return typeof value === 'number' && isFinite(value); //isFinite() rejects NaN and Infinity, but is only good for numbers, not strings
}
JavaScript doesn't have real arrays, it has array-like objects.
To test if value is an array:
if (my_value && typeof my_value === 'object' && typeof my_value.length === 'number' &&
!(my_value.propertyIsEnumerable('length'))) {
//my_value is definitely an array!
}
The arguments
array isn't an array, just an object with a length property.
0
, NaN
, ''
, false
, null
and undefined
are all falsy values, but they are not interchangeable. When testing for a missing member of an object for example, you need to use undefined
and not null
.
undefined
and NaN
are actually global variables instead of constants but don't change their values.
JavaScript objects inherit members from the prototype chain so they are never truly empty.
To test for membership without prototype chain involvement, use the hasOwnProperty
method or limit your results (for example, to specific types like number so you know you're not dragging in object members from up the prototype for example if that's what's causing the problem).
Avoid these altogether
==
and !=
: Don't function properly when result is false, use ===
or !==
insteadwith
statement: Intended to provide a shortcut to properties of an object but results vary every time it is runeval
: Adds unnecessary complication and compromises the security of the application
setTimeout
and setInterval
should also be avoided as this makes them act like eval
continue
statement: Forces a loop into its next iteration but the code is usually much improved when re-written without continue
switch
fall through: In a switch
statement, each case
falls through to the next case
unless you explicitly disrupt the flow, but using these intentional fall throughs makes the unintentional ones that are causing errors basically impossible to find
{}
to block in statements so as to avoid misinterpretation and aid error finding&
, |
, ˆ
, ˜
, >>
, >>>
or <<
&&
for example++
and --
: This one seems debatable to me; Douglas Crockford finds it makes his coding style much more cryptic and difficult to read (the book uses +=1
and -=1
instead)The function statement vs the function expression: To use JavaScript well, important to understand that functions are values.
function foo() {}
(a function statement) means pretty much the same as var foo = function foo(){};
(a function expression)function foo(){}
) are hoisted to the top of the scope in which they are defined - this encourages sloppy programming and should be avoidedif
statementsTyped wrappers:
Don't use new Boolean
or new String
or new Number
, it's completely unnecessary. Also avoid new Object
and new Array
and use {}
and []
instead.
new
operator:
Functions that are intended to be used with new
(conventionally starting with a capital letter) should be avoided (don't define them) as they can cause all kinds of issues and complex bugs which are difficult to catch.
void:
In JavaScript, this actually takes a value and returns undefined
, which is hugely confusing and not helpful. Don't use it.
JSLint is a code quality tool for JavaScript which checks your syntax.
Having read through this appendix (you can read more about JSLint here), I tend more towards JSHint, a fork of JSLint. It allows programmers to customise for themselves which the good parts and bad parts are and define their own subset, although naturally there are a number of pre-defined options. This is a really fantastic article on using JSHint; it's simple and aimed at having you using JSHint in a few minutes as well as providing various sources for pre-defined subsets.
Interesting article on prototypes: https://sporto.github.io/blog/2013/02/22/a-plain-english-guide-to-javascript-prototypes/