JavaScript
Basic structures
Hoisting
- JS moves
var
declarations all the way up to the top of the scope - solution: use
let
instead ofvar
if (mojo) {
var dojo = 1
}
// becomes
var dojo
if (mojo) {
dojo = 1
}
Assignment
let person = {
name: 'Kunio Otani'
}
// assign copies the values from one ore more source to a target object
// returns the target object
Object.assign(person, {
age: 42,
nationality: 'JP'
})
// expression, returns the right side of the operator
person.age = 42
person.nationality = 'JP'
THIS reference
foo(); // "this" in foo refers to the global object
test.foo(); // "this" in foo refers to test
new foo(); // "this" in foo will refer to a newly created object
foo.apply(bar, [1, 2, 3]); // "this" in foo is set to bar
foo.call(bar, 1, 2, 3); // "this" in foo is set to bar
foo = () => { ... } // "this" is set to the surrounding scope
Arguments
- every function scope can access a special variable
arguments
which holds a list of all function arguments - arrow functions, however, have only access to the arguments of the nearest non-arrow parent function
function foo(a, b, c) {
console.log(arguments[0]) // will print a
}
Six falsy values
- any other values is true
false
,0
,''
,null
,undefined
,NaN
Six primitive values (+ Object)
- Boolean, Null, Undefined, Number, String, Symbol
For loops
- for-in - works for any object, not only arrays. The order is undefined
for(let key in activeUsers) {
console.log(activeUsers[key])
}
- for-of - works only for objects that have
[Symbol.iterator]
for(let user of activeUsers) {
console.log(user)
}
Destructuring assignment
- array destructuring - allows to extract multiple array elements
let [ name1, name2 ] = names
- ignoring second element
let [ name,, name3 ] = names
- setting default values
let [ a = 1, b = 2, c = 3 ] = names
- object destructuring - allows to extract attributes
// assign properties - bad way
let user = buildUser('Sam', 'Williams')
let first = user.first
let last = user.last
// assign properties - good way
let { first, last, fullName } = buildUser('Sam', 'Williams')
let { fullName } = buildUser('Sam', 'Williams') // we need only fullName
Template strings
let fullName = `${first} ${last}` // ` backtick must be used!!!
Rest and Spread operators
- denoted with three ellipses or periods
- rest is used to represent an infinite number of arguments
- spread is used to allow an iterable object to be expanded
- rest destructuring
function fn(num1, num2, ...args) { } /// args always goes last
- destructured rest
function fn(...[n1, n2, n3]) {}
- spread operator
function myFunction(n1, n2, n3) { ... }
const values = [ 1, 2, 3 ]
myFunction(...values)
- object destructuring
const { firstNamne, lastName } = obj
- destructuring into a new variable
const { firstName: first, lastName } = obj
console.log(first)
Arrow functions
- anonymous, not bound to an identifier
- are callable but not constructable (you can't use
new
operator)
// old way
TagComponent.prototype.render = function(){
getRequest(this.urlPath, function(data) {
// this points to getRequest() scope
displayTags(this.targetElement, ... tags)
})
}
// a regular function
const fn1 = function(a, b) { return a + b }
// arrow function
const fn2 = (a, b) => { return a + b }
// arrow function without return keyword
const fn3 = (num1, num2) => num1 + num2
Array operations
- mutating arrays
const mutatingAdd = [1, 2, 3]
mutatingAdd.push(4) // [1, 2, 3, 4]
mutatingAdd.unshift(0) // [0, 1, 2, 3, 4]
// Immutable operations over arrays
const arr1 = [1, 2]
const arr2 = [3, 4]
const arr3 = arr1.concat(arr2) // [1, 2, 3, 4]
const arr3 = [...arr1, ...arr2]// [1, 2, 3, 4]
const arr4_altern = [0, ...arr1] //[0, 1, 2]
- remove strings from an array
const arr = [0, 1, 2]
const tailArr = arr.splice(-1) // [2]
console.log(arr) // [0, 1]
- slice and update
const numbers = [0, 1, 2, 3, 4]
const lessThanThree = numbers.slice(0, 3) // [0, 1, 2]
const moreThanTwo = numbers.splice(2, numbers.length) // [2, 3, 4]
- other functions
concat, entries, fill, filter, find, flat, flatMap, forEach, join,
includes, keys, map, push, pop, reduce, reverse, shift, slice,
sort, splice, unshift, values
- sorting arrays
// sorting function:
// if < 0, then a < b if == 0, then a == b if > 0, then b > a
nums.sort((a,b) => {
return a-b // ascending
})
nums.sort((a,b) => {
return b-a // descending
})
- generating a sequence
const indices = Array.from(Array(10).keys())
console.log(indices) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Maps
- when using objects as maps, their keys are always converted to strings
- Maps can't be serialized into JSON, yet they can be iterated via for-of loop
let totalReplies = new Map()
totalReplies.set(user1, 5)
totalReplies.set(user2, 42)
let has = totalReplies.has(user1)
totalReplies.delete(user1)
WeakMap
- only objects can be passed as keys, aren't iterable, better with memory
Sets
let tags = new Set()
tags.add('JavaScript')
tags.add({version : '2015'})
let [first] = tags // first item
Classes
- old way (ES5)
function SponsorWidget(name, description, url){
this.name = name
this.description = description
this.url = url
}
Sponsor.prototype.render = function(){
}
- new way (ES6+),
class
is only a sugar syntax
class SponsorWidget {
myVar = 12
myOtherVar
constructor(name description, url){
this.name = name
this.description = description
this.url = url
}
render(){
let link = this._buildLink(this.url)
}
_buildLink(url) { // underscore is a convention for private method
}
}
class SponsorWidget extends Widget {
constructor(name, description, url) {
super()
}
render() {
super.render() // parent version
let parsedName = this.parse(this.name)
let css = this._buildCss()
}
}
Promises
- three states - pending, fulfilled, rejected
- handlers -
then()
,catch()
,finally()
- when
Promise.then()
is called, it returns a new promise in the pending state - when
Promise.catch()
is called, it internally callsPromise.then(undefined, rejectHandler)
const myPromise = new Promise((resolve, reject) => {
reject(new Error())
})
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => { resolve('Done!')}) // also works
})
- error handling
myPromise.then(() => {}, error => console.log(error))
Promise.resolve('Resolve').then(console.log) // prints Resolve
Promise.reject('Reject').catch(console.log) // prints Reject
- combined promises
await Promise.all([
resolveAfter1Second(),
resolveAfter2Seconds(),
])
Async/await
async
function returns a promise - all async functions can havethen()
andcatch()
handlers- if we don't return a promise, JS will do it automagically!
async function example1() {
return 'Hello' // JS will wrap this into a promise
}
async function example2() {
return Promise.resolve('World')
}
await
can be used only within anasync
function- error rejection
try {
const value1 = await Promise.reject('Error')
} catch(err) {
}
Advanced structures
Curried functions
- multiple arrow functions, can be used to wrap event handlers with additional parameters
const three = a => b => c => a + b + c
three(1)(2)(3)
// equivalent:
const three = (a) => {
return (b) => {
return (c) => {
return a + b + c
}
}
}
Iterators
- we have to define a function that takes a collection in as the parameter and returns an object which must have property
next
- when
next
is called, the iterator steps to the next value in the collection and returns an object with the value and the done status of the iteration
function createIterator(array){
let currIdx = 0
return {
next() {
return currIdx < array.lenth ? {
value: array[currIdx++], done: false,
} : { done: true }
},
}
}
Generators
- provide an iterative way to build a collection of data
- can be used for asynchronous processing also
function *nameList(){
yield 'Sam' // { done : false, value: 'Sam' }
yield 'Tyler' // { done : false, value: 'Tyler' }
}
for(let name of nameList()) {...} // usage
let names = [...nameList()]
function *gen() {
let i = 0
while(true) {
yield i++
}
}
Tagged template literals
- can be parsed with tag functions and can return a manipulated string
function tagFunction(strings, param) {
return strings[0] + param
}
const tagged = tagFunction`We have ${num} param`
Enhanced object properties
- ES6 added 3 ways to simplify the creation of object literals
- object properties
function getPersonES6(name, age, height) {
return {
name,
age,
height
}
}
- function declaration
function getPersonES5(name, age, height) {
return {
getAge: function() { return age }
}
}
function getPersonES6(name, age, height) {
return {
getAge() { return age }
}
}
- computed properties
const personES5 = {
lastName: 'Smith'
}
personES5[varName] = 'John'
const personES6 = {
lastName: 'Smith',
[varName]: 'John',
}
Readonly properties
var a = {}
Object.defineProperty(a, 'mojo', {
value: 15,
writable: false
})
Getters and setters
function Foobar () {
var _foo // true private property
Object.defineProperty(obj, 'foo', {
get: function () { return _foo },
set: function (value) { _foo = value }
})
}
Proxies
function Foo() {
return new Proxy(this, {
get: function (object, property) {
if (Reflect.has(object, property)) {
return Reflect.get(object, property)
} else {
return function methodMissing() {
console.log(`You called ${property} but it doesn't exist!`)
}
}
}
})
}
Modules
- old way (ES5)
// anonymous namespaces
(function () {
// a self contained namespace
window.foo = function() {
// exposed closure
}
})() // execute immediately
- new way (ES6+)
- we can use
require
orimport
- we can use
// flas-message.js
export default function(message){
alert(message)
}
// app.js
import flashMessage from './flash-message'
flashMessage('Hello')
Tips and tricks
Creating own trim function
String.prototype.trim = function(){return this.replace(/^s+|s+$/g, '')}
Transforming arguments object into an array
var argArray = Array.prototype.slice.call(arguments)
Closures inside loops
- wrong
var funcs = []
for (var i = 0; i < 3; i++) {
funcs[i] = function() {
console.log('i value is' + i) // i value is 3, always!
}
}
- good
for (var i = 0; i < 3; i++) {
funcs[i] = (function(value) {
console.log('i value is ' + i) // 0, 1, 2
})(i)
}
- even better
for (let i = 0; i < 3; i++) {
funcs[i] = () => {
console.log('i value is ' + i) // 0, 1, 2
}
}
Remove duplicities from an array
const arr = [...new Set([1, 2, 3, 3])] // [1, 2, 3]
Get the last item in an array
let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
console.log(array.slice(-1)) // Result: [9]
Assigning operator in a constructor
class Polygon {
constructor(options) { // many attributes
Object.assign(this, options)
}
}
Freezing properties of an object
const obj = { foo1: 'bar1', foo2: { value: 'bar2' } };
Object.freeze( obj );
obj.foo = 'foo'; // doesn't change the property, doesn't throw Error
obj.foo2.value = 'bar3'; // does change the value - it's nested!
Deep cloning and comparing
JSON.stringify(myObj1) === JSON.stringify(myObj2)
Weird parts of JavaScript
false.toString() // false
function Foo() { }
Foo.bar = 1
2.toString() // SyntaxError, dot here means a floating point literal
(2).toString() // OK
2 .toString() // OK
2..toString() // OK
var foo = {} // new object, derives from Object.prototype
// property access
var foo = { name : 'kitten' }
foo.name // OK
foo['name'] // OK
var get = 'name'
foo[get] // OK
foo.1234 // ERROR
foo['1234'] // OK
// delete is the only way how to remove a property
// undefined or null only removes the VALUE
var obj = { bar: 1 }
obj.bar = undefined // removes value
delete obj.bar // removes key
// you can't delete global variables declared by var!
var mojo = 12
// use assignment to a global property instead,
// if you need to create a global variable
global.mojo = 12
// arrays
new Array(3) // [undefined, undefined, undefined],
new Array('3') // ['3']
// equality operator
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
{ } === {} // false
new String('foo') === 'foo' // false
10 == '10' // true
10 == '+10' // true
10 == '010' // true
isNan(null) == false // true, null converts to 0
new Number(10) === 10 // false, object !== number
// typeof operator
'foo' // string
new String('foo') // object
true // boolean
[1, 2, 3] // object
new Function() // function
// casting
'' + 10 === '10' // true
!!'foo' // true
!!true // true
// boolean evaluation
new Boolean() // false
new Boolean(0) // false (for any falsy parameter)
// but careful, (new Boolean(anything) === true/false) -> always FALSE
new Boolean(true) // true (for any truthy parameter)
// why is this comma recommended? Because it prevents any previous code from
// executing your code as the arguments to that function
;(async () => { ... }