JavaScript
Basic structures#
Hoisting#
- JS moves
vardeclarations all the way up to the top of the scope - solution: use
letinstead ofvar
if (mojo) { var dojo = 1}
// becomesvar dojoif (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 objectObject.assign(person, { age: 42, nationality: 'JP'})
// expression, returns the right side of the operatorperson.age = 42person.nationality = 'JP'
THIS reference#
foo(); // "this" in foo refers to the global objecttest.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 barfoo.call(bar, 1, 2, 3); // "this" in foo is set to barfoo = () => { ... } // "this" is set to the surrounding scopeArguments#
- every function scope can access a special variable
argumentswhich 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 waylet user = buildUser('Sam', 'Williams')let first = user.firstlet last = user.last// assign properties - good waylet { first, last, fullName } = buildUser('Sam', 'Williams')let { fullName } = buildUser('Sam', 'Williams') // we need only fullNameTemplate 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 } = objconsole.log(first)Arrow functions#
- anonymous, not bound to an identifier
- are callable but not constructable (you can't use
newoperator)
// old wayTagComponent.prototype.render = function(){ getRequest(this.urlPath, function(data) { // this points to getRequest() scope displayTags(this.targetElement, ... tags) })}
// a regular functionconst fn1 = function(a, b) { return a + b }
// arrow functionconst fn2 = (a, b) => { return a + b }
// arrow function without return keywordconst fn3 = (num1, num2) => num1 + num2Array 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 > anums.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 itemClasses#
- old way (ES5)
function SponsorWidget(name, description, url){ this.name = name this.description = description this.url = url}
Sponsor.prototype.render = function(){ }- new way (ES6+),
classis 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 ResolvePromise.reject('Reject').catch(console.log) // prints Reject- combined promises
await Promise.all([ resolveAfter1Second(), resolveAfter2Seconds(),])Async/await#
asyncfunction 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')}awaitcan be used only within anasyncfunction- 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 + cthree(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
nextis 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()) {...} // usagelet 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
requireorimport
- we can use
// flas-message.jsexport default function(message){ alert(message)}
// app.jsimport 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 Errorobj.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() // OK2..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 variableglobal.mojo = 12
// arrays new Array(3) // [undefined, undefined, undefined], new Array('3') // ['3']
// equality operator '' == '0' // false 0 == '' // true 0 == '0' // truefalse == '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 0new 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 evaluationnew Boolean() // falsenew Boolean(0) // false (for any falsy parameter)// but careful, (new Boolean(anything) === true/false) -> always FALSEnew 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 () => { ... }