Semicolons terminate statements but not blocks.
Only time a semicolon follows a block is a function expression
var fun1 = function( x ) { return x + 1 }; has semicolon
var fun2 = ( x ) => x + 1; has semicolon
log( fun1(1), fun2(1) ) 2 2
if(true){ log("no semicolon")} no semicolon
Compount assignment operator += work on strings, yay!.
var str = ''
str += 'one'
str += ' two'
str += ' three'
log(str) one two three
Primitive values vs objects
- The primitive values are booleans, numbers, strings, null, and undefined
- All other values are objects
- A major difference is how they are compared.
- Properties of primitive values cannot be changed
log( true === true ) true
log( 0 === 0 ) true
log( {} === {} ) false!
var arr = 'abc'
arr.length = 100 Cannot assign to read only property 'length' of abc ( 'use strict' verion )
arr.here = 'there' Cannot assign to read only property 'here' of abc ( 'use strict' version )
All nonprimitive values are objects
The most common kinds are
- Plain objects
- Arrays
- Regex
Categorizing values using typeof and instanceof
- typeof is mainly used for primative values
- instanceof is used for objects
- Life is too short for this nonsense, use lodash or underscore
log( typeof true ) boolean
log( typeof 'abc' ) string
log( typeof {} ) object
log( typeof [] ) object (lame)
log( typeof null ) object (lame)
log( {} instanceof Object ) true
log( [] instanceof Object ) true ( oh thanks )
log( [] instanceof Array ) true
log(_.isBoolean(true)) true
log(_.isString('abc')) true
log(_.isObject({})) true
log(_.isObject([])) true
log(_.isNull(null)) true
log( 'abc'.slice( 0 ) ) abc
log( 'abc'.slice( 1 ) ) bc
log( ' abc '.trim() ) 'abc'
if ( myvar === 0 ) {
} else if ( myvar === 1 ) {
} else if ( myvar === 2 ) {
} else {
}
switch (myVar) {
case 'one':
do_one()
break
case 'two':
do_two()
break
default:
do_default()
}
do {
do_something()
} while ( true )
function pair( x, y ) {
if ( arguments.length !== 2 ) {
throw new Error( 'Need exactly 2 arguments' );
}
}
pair()
Exception handling, most common way
My note: try/catch is sketchy for async programming, but uncaught throw is
probably fine
function pair( x, y ) {
if ( arguments.length !== 2 ) {
throw new Error( 'Need exactly 2 arguments' );
}
}
try {
pair(1)
} catch(ex) {
log(ex.name) Error
}
var obj = {
first: 'john',
last: 'doe',
data: [1,2,3,4,5],
full: function(){
return [this.first, this.last].join(' ')
},
all: function(){
var dataString = function(){
return this.data.join(' ')
}
return [this.first, this.last, dataString.bind(this)()]
},
}
log(obj.full()) john doe
var fun = obj.full.bind(obj)
log(fun()) john doe
log( obj.all() ) [ 'john', 'doe', '1 2 3 4 5' ]
Multiple places to pass context to some iterators
var obj = {name:'kevin', data:[1,2,3,4,5]}
log( obj.data.map( function( x ) { return this.name + x }, obj ) )
log( obj.data.map( function( x ) { return this.name + x }.bind( obj ) ) )
Functions called with new become constructors for objects
function MyObject(name){
this.name = name
}
var obj = new MyObject('bob')
log(obj.name) bob
Regex
- RegExp.prototype.test() returns a boolean
- RegExp.prototype.exec() works with capture groups and can be looped, capture group at index 1
- String.prototype.replace()
var r = /^a/
log( r.test( 'abc' ) ) true
log( /a(b+)c/g.exec( 'abc abbc abbbc' ) ) [ 'abc', 'b', index: 0, input: 'abc abbc abbbc' ]
log('abc abc abc'.replace(/b/g, 'B')) aBc aBc aBc
Wrapper objects for primitives
- boolean, number, and string have corresponding constructors: Boolean(), Number(), String().
- As constructors they are garbage, best avoided.
- As functions they convert to that primitive type.
log( typeof 'abc' ) string
log( typeof new String( 'abc' ) ) object (?)
log( 'abc' === new String( 'abc' ) ) false
log( 'abc' === 'abc' ) true
log( 123 + '4') 1234
log( 123 + Number('4')) 127
There is no way of comparing objects in javascript
The useful Math functions
- Math.floor()
- Math.ceil()
- Math.round()
log( Math.floor( 1.9 ) ) 1
log( Math.ceil( 1.1 ) ) 2
log( Math.round( 1.4 ) ) 1
log( Math.round( 1.5 ) ) 2
parseInt
- parseInt() is useful for parsing strings
- parseInt() is bad for converting numbers to integers
log( parseInt( '123junk')) 123
log( parseInt( 1000000000000000000000.5, 10 ) ) 1, not even close
log( _.parseInt( 1000000000000000000000.5, 10 ) ) 1, not even close
log( 'abc'.charAt( 2 ) ) c
log( 'abc' [ 2 ] ) c
Converting to string
- String(value) preferred
- '' + value
- value.toString() doesn't work with undefined and null!
log( String( 123 ) ) '123'
log( '' + 123 ) '123'
var value = 123
log( value.toString()) '123
var value = null
log( String( value ) ) null
log( value.toString()) TypeError: Cannot read property 'toString' of null
String.prototype.slice(start, end?)
log( 'abc'.slice(-1)) c
log( 'abc'.slice(-2)) bc
log( 'abc'.slice(1)) bc
log( 'abc'.slice(0,2)) ab
String.prototype.split(separator?, limit?)
- Preferred over substring
- separator is regex or string
- capture groups are also returned
log('fred, barney, & pebbles'.split(/[, ]+/)) [ 'fred', 'barney', '&', 'pebbles' ]
log('fred, barney, & pebbles'.split(/([, ]+)/)) [ 'fred', ', ', 'barney', ', ', '&', ' ', 'pebbles' ]
log( ' abc '.trim() ) 'abc'
String.prototype.concat(str1, str2, ...)
log( ''.concat( 'a', 'b', 'c' ) ) 'abc'
String.prototype.indexOf(str, position?)
- Reverse: lastIndexOf(str, position?)
log( 'abcabc'.indexOf( 'c' ) ) 2
log( 'abcabc'.indexOf( 'c', 2+1 ) ) 5
log( 'abcabc'.lastIndexOf( 'c' ) ) 5
String.prototype.search(regexp)
- Returns index of first match
- Returns -1 if no match
- Captures seem to have no effect
log( 'abc'.search(/b/)) 1
log( 'abc'.search(/(b)/)) 1
String.prototype.match(regexp)
- returns match object if no /g
- returns all matches if /g
- captures also affect result but only if no /g ?
log( '-abb--aaab-'.match( /a+b?/ ) ) [ 'ab', index: 1, input: '-abb--aaab-' ]
log( '-abb--aaab-'.match( /a+b?/g ) ) [ 'ab', 'aaab' ]
log( '-abb--aaab-'.match( /(a+)b?/ ) ) [ 'ab', 'a', index: 1, input: '-abb--aaab-' ]
log( '-abb--aaab-'.match( /(a+)b?/g ) ) [ 'ab', 'aaab' ]
String.prototype.replace(search, replacement)
- search can be string or regex
- replacement can be string or function
log( 'abc'.replace( /./g, ( x ) => x.toUpperCase() ) ) ABC
Use the Error constructor
if ( somethingBad ) {
throw "Something bad happened"
}
if ( somethingBad ) {
throw new Error( "Something bad happened" )
}
Finally is always executed
try {
throw new Error('whoops!')
} catch ( ex ) {
log('caught ' + ex) caught Error: whoops!
} finally {
log('always') always
}
Finally doesn't need catch
try {
throw new Error( 'whoops!' ) Error: whoops!
} finally {
log( 'always' ) always
}
func.apply( thisValue, *array )
- Is this needed when the ...rest operator is available?
- Is passing thisValue a common use case?
- Is this anything more than a glorified splat?
log( Math.max( 1, 2, 3 ) ) 3
log( Math.max.apply( null, [ 1, 2, 3 ] ) ) 3
log( Math.max( ...[ 1, 2, 3 ] ) ) 3
func.bind( thisValue, arg1, ..., argN )
- Performs partial function application (suspect rare use case)
- Unlike apply and call, this returns a new function
- Is binding callback to thisValue the most common use case?
var obj = {
format: function( x ) {
return [ '-', x, '-' ].join( '' )
},
data: [ 1, 2, 3, 4, 5 ]
}
obj.data.forEach( function( x ) {
print( this.format(x) ) -1- -2- -3- -4- -5-
}.bind( obj ) )
obj.data.forEach( function( x ) {
print( this.format(x) ) -1- -2- -3- -4- -5-
}, obj )
arguments
- array like - has .length(), but not .forEach(), .slice(), etc
- strict mode forbids assignment
- In nonstrict mode arguments keeps up to date with assignment to parameters
- In strict mode arguments doesn't keep up to date with assignment to parameters
if ( arguments.length < 1 ) {
throw new Error( 'You need to provide at least 1 argument' );
}
function fun() {
var args = [].slice.call( arguments )
var args = Array.prototype.slice.call( arguments )
args.forEach( ( x ) => log( x ) )
}
fun('a', 'b', 'c')
a
b
c
pass by reference
- Parameters don't pass by reference (good, fewer side effects)
- Wrap in something to pass by reference
- I don't think this is so simple
function fun(arg){
arg.replace(/./g, (x) => x.toUpperCase() )
}
var str = 'abc'
fun(str)
log( str ) abc
function fun(arg){
log('arg=', arg)
arg[0] = arg[0].replace(/./g, (x) => x.toUpperCase() )
}
var str = 'abc'
fun( [ str ] )
log( str ) abc
var str = ['abc']
fun( str )
log( str[0] ) ABC
Object.keys and hasOwnProperty
var obj = { a: 1, b: 2, c: 3 }
Object.keys( obj ).forEach( function( x ) {
log( x, obj.hasOwnProperty( x ) )
} )
a true
b true
c true
for( var x in obj) {
log( x, obj.hasOwnProperty( x ) )
}
a true
b true
c true
var obj = new Object();
var obj = {};
"But you frequently just create an empty object and then manually add
properties, because descriptors are verbose"
var proto = { a: 1 }
var obj = Object.create( proto )
obj.b = 2
log( obj.a ) 1
log( obj.b ) 2
var obj = Object.create( String.prototype )
log( Object.getPrototypeOf( obj ) ) [String: '']
log( String.prototype.isPrototypeOf( obj ) ) true
var obj = Object.create( String )
log( Object.getPrototypeOf( obj ) ) [Function: String]
log( String.isPrototypeOf( obj ) ) true
var proto = {}
var obj = Object.create( proto )
log( proto.isPrototypeOf( obj ) ) true
Avoid invoking hasOwnProperty() directly on an object, as it may be overridden
Object.prototype.hasOwnProperty.call(obj, 'foo')
{}.hasOwnProperty.call(obj, 'foo')
var obj = {
x: 1,
get getter() {
return this.x
},
set setter( x ) {
this.x = x
}
}
obj.setter = 100
log(obj.getter) 100
The only operations affected by enumerability are
- for in loop
- Object.keys()
- JSON.stringify()
- Best Practices
- For your own code, you can usually ignore enumerability and should avoid the for-in loop
- You normally shouldn't add properties to built-in prototypes and objects. But if you
do, you should make them nonenumerable to avoid breaking existing code.
Use array length to remove and append elements
- I'd probably use push() to append elements
var a = [1,2,3]
a.length = 2
log(a) [ 1, 2 ]
a[a.length] = 3
log(a) [ 1, 2, 3 ]
in operator works
- Awkward looking operator isn't it?
- use something like _.includes instead
- Don't use in for loops, what are you thinking?
var a = [1,2,3]
log( 1 in a ) true
log( _.include( a, 1 ) ) true
a.forEach((x,y) => log(x))
Literal vs Constructor
- Literal compiled at load time.
- Constructor compiled at runtime.
- If you can prefer literals because of load time compilation errors