Skip to content

References notes for a short series of lectures on the latest ~JavaScript language features.

License

Notifications You must be signed in to change notification settings

alistair-hmh/es-next-fundamentals

Repository files navigation

ES Next Logo

ES Next Fundamentals

Reference notes for a short series of lectures on the latest ~JavaScript language features.

  • Strings: Templates, Methods
  • Functions: Arrow Functions, Default Parameter Values, Spread & Rest Operators
  • Object-Oriented: Prototypes, Classes, The Module Pattern, Lexical Context
  • Flow Control: Promises, Async & Await, Generators & Iterators
  • Immutability: Why Immutability?, Primitives, Non Primitives, Object Assignment
  • Objects: assign, values, keys, entries, freeze, seal
  • Arrays: filter, every, includes, some, from, of, forEach, copyWithin, fill, find, findIndex
  • Meta Programming: Reflect, Proxy
  • React: How ES Next syntax makes your React code cleaner and easier to manage.

Table of Contents

[[TOC]]

Part 1 - The "Basics"

Identifiers

  • Const - A constant never changes it's primitive value.
  • Let - Let can be updated
  • Block scoped - invisible outside of the scope in which they were assigned.
  • Can't be re-assigned (referenced to another name-space)

const

A constant never changes it's primitive value.

// Const can re-reference Var
const a = 2
a = 2
// Uncaught TypeError: Assignment to constant variable.
// Const can re-reference Var
var a = 1
const a = 2
console.log(a)
// Var cannot re-reference Const
const a = 1
var a = 2
// ...or...
a = 2
// SyntaxError: "Identifier 'a' has already been declared"
// (Execution of your script will halt!)

Block Scoping

Const and Var are both blocked scoped.

{
	const a = 2
}
console.log(a)
// Uncaught ReferenceError: a is not defined

You can assign to a name-space that is also used in the parent block.

const a = 1
{
	const a = 2
	console.log(a)
}

let

Let can be updated.

let a = 1
a = 2
console.log(a)
// 2

Let cannot be re-assigned.

let a = 1
let a = 2
// SyntaxError: Identifier 'a' has already been declared

For Loops

for (let i = 0; i < 10; i++) {
	console.log(i)
}

console.log(i)
// ReferenceError: i is not defined

If Statements

if (true) {
	const a = 1
}
const a = 2
console.log(a)
// 2

React

  • Never use var in a react application.
  • Always use const and let.

Strings

String Templates

Template Literals

Template literals are string literals allowing embedded expressions. You can use multi-line strings and string interpolation features with them. They were called "template strings" in prior editions of the ES2015 specification.

const name = 'Al'
const output = `My name is: ${name}`
console.log(output)
// My name is: Al

Tagged Templates

const cool = function(strings, foo, bar, baz) {
	console.log(strings, foo, bar, baz)
	return 'Psych!'
}

const foo = 111
const bar = 222
const baz = 333

const output = cool`One ${foo} two ${bar} three ${baz}.`

console.log(output)
// Psych!
const cool = function(str, foo, bar, baz) {
	console.log(strings, foo, bar, baz)
	return str[0] + foo + str[1] + bar + str[2] + baz + str[3]
}

const foo = 111
const bar = 222
const baz = 333

const output = cool`One ${foo} two ${bar} three ${baz}!`

console.log(output)
// One 111 two 222 three 333!

Multi-line Strings

console.log(`
This
is
valid!
`)
//
// This
// is
// valid!

// ... aka ...

// \nThis\nis\nvalid!

βš› In React - String Templates Literals

import React from "react";
import ReactDOM from "react-dom";

const MyComp = (props) => (
  <div>
    <h1>Hello, {props.name}!</h1>
    <h2>{`Hello, ${props.name}!`}</h2>
  </div>
)

function App() {
  return (
    <div>
      <MyComp name="World"/>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

CodeSandbox

String Methods

.repeat()

const val = 'Foo'
console.log(val.repeat(10))
// FooFooFooFooFooFooFooFooFooFoo

.padStart()

const val = 'Foo'
const pattern = '123456789'
console.log(val.padStart(4, pattern))
// 1Foo
console.log(val.padStart(8, pattern))
// 1234Foo
console.log(pattern)

.padEnd()

const val = 'Foo'
const pattern = '123456789'
console.log(val.padEnd(4, pattern))
// 1Foo
console.log(val.padEnd(8, pattern))
// 1234Foo
console.log(pattern)

Object Literal Shorthand

The old way

const a = 'foo'
const b = 'bar'
const c = 'baz'

const obj = {a: a, b: b, c: c}
console.log(obj)
// {a: "foo", b: "bar", c: "baz"}
const a = 'foo'
const b = 'bar'
const c = 'baz'

const obj = {a, b, c}
console.log(obj)
// {a: "foo", b: "bar", c: "baz"}

βš› In React - Object Literal Shorthand

  getData = () => {
    const method = "GET";
    const url = "https://jsonplaceholder.typicode.com/posts/1";
    const obj = { url: url, method: method };

    // Object Literal Shorthand
    //     const method = "GET";
    //     const url = "https://jsonplaceholder.typicode.com/posts/1";
    //     const obj = { url, method };

    fetchData(obj).then(data => this.setState({ data }));
  };

CodeSandBox

Destructuring

Destructuring Objects

The old way

function rotate(props) {
	var x = props.x
	var y = props.x
	var z = props.x
	var originX = props.origin.x
	var originY = props.origin.y
	var originZ = props.origin.z

	// ... apply transformation matrix
}

rotate({
	x: 0,
	y: 1,
	z: 0,
	origin: {
		x: 0,
		y: 0
	}
})

The new way

const rotate = props => {
	let {
		x,
		y,
		z,
		origin: {x: originX, y: originY}
	} = props
	// ... apply transformation matrix
}

rotate({
	x: 0,
	y: 1,
	z: 0,
	origin: {
		x: 0,
		y: 0
	}
})

Destructuring Arrays

const rotate = coords => {
	let [x, y, z] = coords
	console.log(x, y, z)
}

const coords = [0, 1, 2]
rotate(coords)

Destructuring Function Parameters

const rotate = ({x, y, z}) => {
	console.log(x, y, z)
}

const coords = {x: 0, y: 1, z: 0}
rotate(coords)

Arrays

const rotate = ([x, y, z]) => {
	console.log(x, y, z)
}

const coords = [0, 1, 0]
rotate(coords)

βš› In React - Object Destructuring

const MyOldComponent = props => (
  <div>
    <p>
      {props.a}, {props.b}, {props.c}
    </p>
  </div>
);
const MyDestructuredComponent = ({ a, b, c }) => (
  <div>
    <p>
      {a}, {b}, {c}
    </p>
  </div>
);

CodeSandbox

Arrow Functions

const log = msg => {
	console.log(msg)
}
log('Hi!')

Automatic Return

const log = msg => console.log(msg)
log('Hi!')

Optional Parens

const log = msg => console.log(msg)
log('Hi!')

Currying Without Arrow Functions

function curry(a) {
	return function(b) {
		return function(c) {
			return function(d) {
				return a + b + c + d
			}
		}
	}
}

console.log(curry(1)(2)(3)(4))
// Logs 10

Currying With Arrow Functions

// Currying with Arrow Functions
const curry = a => b => c => d => {
	return a + b + c + d
}

console.log(curry(1)(2)(3)(4))
// Logs 10

Promises w/wo Arrow Functions

Promises without arrow functions:

function getData(url) {
	return new Promise(function(resolve) {
		return fetch(url).then(function(response) {
			resolve(response.json())
		})
	})
}

getData('https://jsonplaceholder.typicode.com/posts/1').then(data =>
	console.log(data)
)

Promises with arrow functions:

const getData = url =>
	new Promise(resolve =>
		fetch(url).then(response => resolve(response.json()))
	)

getData('https://jsonplaceholder.typicode.com/posts/1').then(data =>
	console.log(data)
)
(async () {
	const getData = async url => JSON.parse((await fetch(url)).data)
	const data = await getData('https://jsonplaceholder.typicode.com/posts/1')
	console.log(data)
})()

βš› In React - Arrow Functions

const MyComponent = () => <div/>;
  doSomething = () => {
    alert("Something!");
  };

CodeSandbox

Default Parameter Values

const adder = (a, b) => {
	return a + b
}

const result = adder(2, 2)
console.log(result)
// 4
const adder = (a, b) => {
	return a + b
}

const result = adder()
console.log(result)
// NaN
const adder = (a, b) => {
	a = a || 2
	b = b || 2
	return a + b
}

const result = adder()
console.log(result)
// 4
const adder = (a = 2, b = 2) => {
	return a + b
}

const result = adder()
console.log(result)
// 4

Rest & Spread

Rest

// You need parentheses with arrow functions and the rest operator
const adder = (start, ...vals) => {
	let result = start

	vals.forEach(val => {
		result += val
	})
	return result
}

const start = 1
const result = adder(start, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512)
console.log(result)
// 1024

Spread

// You need parentheses with arrow functions and the rest operator
const adder = (start, ...vals) => {
	let result = start

	vals.forEach(val => {
		result += val
	})
	return result
}

const start = 1
const values = [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
const result = adder(start, ...values)
console.log(result)
// 1024
const vals = [2, 3, 4]
var a = [1, ...vals, 5]
console.log(a)
//Β [1, 2, 3, 4, 5]
const myTag = (strs, ...vals) =>
	vals
		.map((val, i) => {
			return strs[i] + val
		})
		.concat(strs[strs.length - 1])
		.join('')

const foo = 111
const bar = 222
const baz = 333

const output = myTag`One ${foo} two ${bar} three ${baz}!`
console.log(output)
// One 111 two 222 three 333!

Demo: Spread Template Tags

class Greeting extends React.Component {
	render() {
		console.log(this.props)
		return <h1 {...props}>Hello</h1>
	}
}

Part 2 - Object-Oriented Programming

Prototypes

function Dog() {}
console.log(Dog.prototype)

Prototype Constructor

function Dog(name) {
	this.name = name
}
const buddy = new Dog('Buddy')
console.log(buddy)

Properties & Mehotds

function Dog(name) {
	this.name = name
}

Dog.prototype.bark = function() {
	console.log(`Woof, my name is ${this.name}!`)
}

const buddy = new Dog('Buddy')
buddy.bark()
// Woof, my name is Buddy!

Prototype Chaining

function Dog(name) {
	this.name = name
}
Dog.prototype.bark = function() {
	console.log(`Woof, my name is ${this.name}!`)
}

function FlyingDog(name) {
	Dog.call(this, name)
}
FlyingDog.prototype.fly = function() {
	console.log("I'm flying!")
}
FlyingDog.prototype.constructor = Dog
FlyingDog.prototype = new Dog()


const buddy = new Dog('Buddy')
const bella = new FlyingDog('Bella')
buddy.bark()
// Woof, my name is Buddy!
bella.bark()
// Woof, my name is Bella!
bella.fly()
// I'm flying!

Classes

Instantiation (new)

class Dog {}
const buddy = new Dog()
console.log(buddy)

Class Methods

class Dog {
	bark() {
		console.log('woof')
	}
}
const buddy = new Dog()
buddy.bark()

constructor()

class Dog {
	constructor(name) {
		this.name = name
	}

	bark() {
		console.log(`Woof, my name is ${this.name}!`)
	}
}
const buddy = new Dog('Buddy')
const bella = new Dog('Bella')
buddy.bark()
bella.bark()

Inheritance & Polymorphism

  • Extends()
  • Super()
class Dog {
	constructor(name) {
		this.name = name
	}

	bark() {
		console.log(`Woof, my name is ${this.name}!`)
	}
}

class FlyingDog extends Dog {
	constructor(name) {
		// Inheritance
		super(name)
	}

	// Polymorphism
	fly() {
		console.log("I'm flying!")
	}
}

const buddy = new Dog('Buddy')
const bella = new FlyingDog('Bella')
buddy.bark()
bella.bark()
bella.fly()
// buddy.fly()
// TypeError: buddy.fly is not a function

βš› In React - Inheritance & Polymorphism

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

Lexical Scope (this)

The context of this inside foo.log is foo.

const foo = {
	bar: 123,
	log: function() {
		console.log(this.bar)
	}
}

foo.log()
// 123

The context of this inside inner is Window.

const foo = {
	bar: 123,
	log: function() {
		const inner = function() {
			console.log(`bar = ${this.bar}`)
			console.log(this)
		}

		inner()
	}
}

foo.log()
// bar = undefined

The context of this inside inner is foo.

const foo = {
	bar: 123,
	log: function() {
		const inner = function() {
			console.log(this.bar)
		}

		inner.call(this)
	}
}

foo.log()
// 123

βš› In React - Lexical Scope (this)

Lexical scope is referenced in react components. React components are classes.

class Welcome extends React.Component {
	state = {
		name: 'Alistair'
	}
	render() {
		return <h1>Hello, {this.state.name}</h1>;
	}
}

Closures

β€œA closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created.”

https://developer.mozilla.org/en-US/docs/Web/JavaScript

;(function iAmEnclosed() {
	const a = 1
})()

// ReferenceError: a is not defined
console.log(a)

The Module pattern

const createDog = function dogClosure(name) {
	const secret = 'I hate squirrels!'

	const dog = {
		name,

		bark: function() {
			console.log(`Woof, my name is ${this.name}!`)
		},

		tellSecret: function() {
			console.log(secret)
		}
	}

	return dog
}

const buddy = createDog('Buddy')
buddy.bark()
// Woof, my name is Buddy!

console.log(buddy.secret)
// undefined

buddy.tellSecret()
// I hate squirrels!

Part 3 - The Meta Bits

Immutability

What is "Immutability"?

  • Immutability is about being "Non-Destructive"
  • "Immutable" software means the original referenced value 1) does not change and 2 can not change.
  • Every time you update an object, you should receive a new copy of the original.
    • (The different between "Save" and "Save As")

See YouTube Video: ReactCasts #9 - Immutability in JavaScript

  • Why is immutability important? (in no particular order)
    • Re-usability
    • Providing Stable State
    • Reduce/eliminate of unintended side-effects (functional programming)
    • Better data control
    • Replaying State at specific points (Time Travel)
      • Tracking bugs, etc.
    • Undo/Redo implementations
    • Performance boosts with DOM Tree Diff'ing
    • Parallelization (Multi Core)

Idempotentcy

An idempotent operation produces the result in the same state even if you call it more than once, provided you pass in the same parameters.

Source: Stack Overflow - What is an Idempotent Operation

Unintended Side Effects

function createPerson () {
	let name
	let age

	const person = {
		getName: () => {
			return name
		},
		getAge: () => {
			name = 'Bob'
			return age
		},
		setName: function (newName) {
			name = newName
		},
		setAge: function (newAge) {
			age = newAge
		}
	}

	return person
}

const person = createPerson()
person.setName('Alistair')
person.setAge(37)
console.log(person)
// {getAge: Ζ’, updateName: Ζ’, updateAge: Ζ’, name: "Alistair", age: 37}

console.log(`Name: ${person.getName()}, Age: ${person.getAge()}`)
// Name: Alistair, Age: 37

console.log(`Name: ${person.getName()}, Age: ${person.getAge()}`)
// Name: Bob, Age: 37

Languages with Immutability

  • JavaScript is not immutable by nature.

  • React is born in the immutable paradigm.

  • Immutable

    • Haskell (Purely functional)
    • Erlang
    • Scala
    • R
    • Lisp
      • Logo
      • Scheme
    • ML
      • Ocaml

Reference vs. Primitive Variables

Primitives

  • Some types of JavaScript value are "Primitive".
  • When you assign primitive variables, the value gets copied in memory.
const a = 'foo'
console.log(a)  // foo

const b = a
console.log(b)  // foo

b = 'bar'
console.log(a)  // foo
console.log(b)  // bar

Primitive types include:

  • Number
  • String
  • Boolean

References

Some types of JavaScript value are "Referential".

  • When you assign referential variables, the reference to the memory location is passed into the variable.
const a = {foo: 'bar'}
console.log(a)  // {foo: "bar"}

const b = a
console.log(b)  // {foo: "bar"}

b = {oh: 'what?!'}
// Both values are now the same!
// They have been pointed to the same reference in memory.
console.log(a)  // {oh: 'what?!'}
console.log(b)  // {oh: 'what?!'}

The object {foo: "bar"} is now orphaned, will be picked up my garbage collection and should get deleted from memory on the next garbage cycle.

Referential types include:

  • Object
  • Array
  • Function

Object.assign()

Object a remains unchanged.

const a = {foo: 'bar'}
const b = Object.assign({}, a)
console.log(b)  // {foo: "bar"}

b.ping = 'pong'
console.log(a)  // {foo: "bar"}
console.log(b)  // {foo: "bar", ping: "pong"}

Adding to an Array Immutably

.concat()

  • Object a remains unchanged.
const a = ['foo', 'bar', 'baz']
console.log(a)  // ['foo', 'bar', 'baz']

const b = a.concat('qux')
console.log(b)  // ['foo', 'bar', 'baz', 'qux']

Spread

  • We can also use Spread
  • Object a remains unchanged.
const a = ['foo', 'bar', 'baz']
console.log(a)  // ['foo', 'bar', 'baz']

const b = [...a, 'qux']
console.log(b)  // ['foo', 'bar', 'baz', 'qux']

Updating an Array Immutably

Array.filter()

  • Object a remains unchanged.
const a = ['foo', 'bar', 'baz']
console.log(a)  // ['foo', 'bar', 'baz']

const b = a.filter(n => n !== 'bar' ? n : null)
console.log(b)  // ['foo', 'baz']

Immutability Challenge #1 Immutability Solution

Asynchronous Control Flow

Callbacks

A simple callback:

function addButNoHurry (a, b, callback) {
	setTimeout(() => {
		callback(a + b)
	}, 1000)
}

const a = 2
const b = 2

addButNoHurry(a, b, function (result) {
	console.log(result)
}))

You could clean this up:

function addButNoHurry (a, b, callback) {
	setTimeout(() => {
		callback(a + b)
	}, 1000)
}

function callback (result) {
	console.log(result)
}

var a = 2
var b = 2

addButNoHurry(a, b, callback)
// But what if I need my result here...
// πŸ‘‡ ?
// var result = addButNoHurry(a, b, callback)
// console.log(result)
var result

function addButNoHurry (a, b, callback) {
	setTimeout(() => {
		result = a + b
	}, 1000)
}

var a = 2
var b = 2

addButNoHurry(a, b, callback)
// πŸ‘‡
console.log(result)

// - Talk about why nothing happens
// - Talk about undesired-side-effects

Callback Soup

With callbacks:

function getUserProfile (success, error) {
	$.ajax.get('/user/profile', success, error)
}

function getUserTestResults (success, error) {
	$.ajax.get('/user/results', success, error)
}

function getUserClasses (success, error) {
	$.ajax.get('/user/classes', success, error)
}

function getUserDashBoardData () {
	var profile
	var results
	var classes

	getUserProfile(function (response) {
		profile = JSON.stringify(response.data.profile)
		getUserTestResults(function (response) {
			results = JSON.stringify(response.data.results)
			getUserClasses(function (response) {
				classes = JSON.stringify(response.data.classes)

				UserDashboard.show(profile, results, classes)				
			}, function (err) {
				ErrorModal.show('Could not load test results for user')
			})
		}, function (err) {
			ErrorModal.show('Could not load test results for user')
		})
	}, function (err) {
		ErrorModal.show('Could not load user profile')
	})
}

With promises:

const getUserProfile = () => fetch('/user/profile')
const getUserTestResults = () => fetch('/user/results')
const getUserClasses = () => fetch('/user/classes')

const getUserDashBoardData = () => {
	Promises.all([
		getUserProfile,
		getUserTestResults,
		getUserClasses
	])
	.then(responses => responses.map(response => response.json()))
	.then(data => UserDashboard.show(...data))
	.catch(err => ErrorModal.show(err.message))
}

Promises

const myPromise = () => new Promise((resolve, reject) => {
	setTimeout(() => {
		resolve('Hi!')
	}, 1000)
})

myPromise().then(result => {
	console.log(result)
})
// PromiseΒ {<pending>}
// Hi!

.catch(err)

const myPromise = () => new Promise((resolve, reject) => {
	setTimeout(() => {
		//resolve('Hi!')
		reject('Oops!')
	}, 1000)
})

myPromise().then(result => {
	console.log(result)
}).catch(err => {
	console.error(err)
})
// PromiseΒ {<pending>}
// Error: Oops!

.finally()

const tryToFlipHeads = () => new Promise((resolve, reject) => {
	const rnd = Math.round(Math.random(1))
	const result = rnd ? 'heads' : 'tails';
	setTimeout(() => {
		if (result === 'heads') {
			resolve('HEADS! :)')	
		} else {
			reject('TAILS :(')
		}
	}, 1000)
})

tryToFlipHeads().then(result => {
	console.log(result)
}).catch(err => {
	console.error(err)
}).finally(status => {
	console.log('DONE!')
})
// PromiseΒ {<pending>}
// Heads! :)
// ... or ...
// Tails :(
// DONE!

.all(promises)

const fetch = require('node-fetch')

const fetchAll = endpoints => new Promise((resolve, reject) => {
	const promises = []

	endpoints.forEach(endpoint => {
		promises.push(fetch(endpoint))
	})	

	return Promise.all(promises).then(resolve).catch(reject)
})

const responseToJson = fetched => new Promise((resolve, reject) => {
	const promises = []

	fetched.forEach(data => {
		promises.push(data.json())
	})	

	return Promise.all(promises).then(resolve).catch(reject)
})

const endpoints = [
	'https://jsonplaceholder.typicode.com/posts/1',
	'https://jsonplaceholder.typicode.com/posts/2',
	'https://jsonplaceholder.typicode.com/posts/3',
	'https://jsonplaceholder.typicode.com/posts/4',
	'https://jsonplaceholder.typicode.com/posts/5'
]

fetchAll(endpoints)
.then(responseToJson)
.then(items => {
	items.forEach((item, i) => {
		console.log(`${i}: ${item.title}`)
	})
})
// 0: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
// 1: qui est esse
// 2: ea molestias quasi exercitationem repellat qui ipsa sit aut
// 3: eum et est occaecati
// 4: nesciunt quas odio

.race()

const fetch = require('node-fetch')

const fetchOne = endpoints => new Promise((resolve, reject) => {
	const promises = []

	endpoints.forEach(endpoint => {
		promises.push(fetch(endpoint))
	})	

	return Promise.race(promises).then(resolve).catch(reject)
})

const endpoints = [
	'https://jsonplaceholder.typicode.com/posts/1',
	'https://jsonplaceholder.typicode.com/posts/2',
	'https://jsonplaceholder.typicode.com/posts/3',
	'https://jsonplaceholder.typicode.com/posts/4',
	'https://jsonplaceholder.typicode.com/posts/5'
]

fetchOne(endpoints)
.then(result => result.json())
.then(json => console.log(json.title))
// A different title is output each time

Challenge #2 - Creating a Promise

Async & Await

const myPromise = () => new Promise(resolve => {
	setTimeout(() => {
		resolve('Hi!')
	}, 1000)
})

const msg = await myPromise()
console.log(msg)
const fetchAll = endpoints => new Promise((resolve, reject) => {
	const promises = []

	endpoints.forEach(endpoint => {
		promises.push(fetch(endpoint))
	})	

	return Promise.all(promises).then(resolve).catch(reject)
})

const responseToJson = fetched => new Promise((resolve, reject) => {
	const promises = []

	fetched.forEach(data => {
		promises.push(data.json())
	})	

	return Promise.all(promises).then(resolve).catch(reject)
})

const get = async endpoints => {
	const fetched = await fetchAll(endpoints)
	// console.log(fetched)

	const jsonItems = await responseToJson(fetched)
	// console.log(jsonItems)

	jsonItems.forEach((item, i) => {
		console.log(`${i}: ${item.title}`)
	})
}

const endpoints = [
	'https://jsonplaceholder.typicode.com/posts/1',
	'https://jsonplaceholder.typicode.com/posts/2',
	'https://jsonplaceholder.typicode.com/posts/3',
	'https://jsonplaceholder.typicode.com/posts/4',
	'https://jsonplaceholder.typicode.com/posts/5'
]

get(endpoints)

🌐 Challenge #3

Generators

function *() {}

yeild

// A Node.js helper library for generators
const co = require('co')

const delay = name => new Promise(resolve => {
	const delay = Math.random() * 1000
	setTimeout(() => resolve({name, delay}), delay)
})

co(function *() {
	const foo = yield delay('foo')
	console.log(foo)
	const bar = yield delay('bar')
	console.log(bar)
	const baz = yield delay('baz')
	console.log(baz)
})

Generator Functions, Iterators & Next

  • Generator Functions
  • Iterators
  • next
const run = generator => {
	const iterator = generator()
	const iteration = iterator.next()

	const iterate = iteration => {
		if (iteration.done) {
			return iteration.value
		}

		const promise = iteration.value
		return promise.then(result => iterate(iterator.next(result)))
	}

	return iterate(iteration)
}

const delay = name => new Promise(resolve => {
	const delay = Math.random() * 1000
	setTimeout(() => resolve({name, delay}), delay)
})

run(function *() {
	const foo = yield delay('foo')
	console.log(foo)
	const bar = yield delay('bar')
	console.log(bar)
	const baz = yield delay('baz')
	console.log(baz)
})

Part 4 - Array Methods

For this section, we are going to walk through examples of the array method documentation from Developer.Mozilla.org/JavaScript.

.forEach()

Moz Docs Array.ForEach()

... executes a provided function once for each array element.

var array1 = ['a', 'b', 'c'];

array1.forEach(function(element) {
  console.log(element);
});

There is no way to stop or break a forEach() loop other than by throwing an > exception. If you need such behavior, the forEach() method is the wrong tool.

Early termination may be accomplished with:

A simple loop, for...of loop, every(), some(), find(), findIndex()

Challenge

For...Of Loops

Moz Docs For...Of

... creates a loop iterating over iterable objects for the value of each distinct property of the object. - Can itterative over Strings, Arrays, Array-like's, NodeList objects, TypedArrays, Maps, Sets and user-defined iterables.

let iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}

.filter()

Moz Docs Array.filter()

... creates a new array with all elements that pass the test implemented by the provided function.

var words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(result);  // ["exuberant", "destruction", "present"]

.every()

Moz Docs Array.every()

The every method executes the provided callback function once for each element present in the array until it finds one where callback returns a falsy value.

function isBelowThreshold(currentValue) {
  return currentValue < 40;
}

var array1 = [1, 30, 39, 29, 10, 13];

console.log(array1.every(isBelowThreshold));  // true

.some()

Moz Docs Array.some()

...tests whether at least one element in the array passes the test implemented by the provided function.

...executes the callback function once for each element present in the array until it finds one where callback returns a truthy value.

var array = [1, 2, 3, 4, 5];

var even = function(element) {
  // checks whether an element is even
  return element % 2 === 0;
};

console.log(array.some(even));
// expected output: true

.includes()

Moz Docs Array.includes()

...determines whether an array includes a certain element, returning true or false as appropriate. It uses the sameValueZero algorithm to determine whether the given element is found.

var array1 = [1, 2, 3];

console.log(array1.includes(2));
// expected output: true

var pets = ['cat', 'dog', 'bat'];

console.log(pets.includes('cat'));
// expected output: true

console.log(pets.includes('at'));
// expected output: false

.find()

Moz Docs Array.find()

returns the value of the first element in the array that satisfies the provided testing function. Otherwise undefined is returned.

var array1 = [5, 12, 8, 130, 44];

var found = array1.find(function(element) {
  return element > 10;
});

console.log(found);  // 12

.findIndex()

Moz Docs Array.findIndex()

... returns the index of the first element in the array that satisfies the provided testing function. Otherwise -1 is returned.

var array1 = [5, 12, 8, 130, 44];

function findFirstLargeNumber(element) {
  return element > 13;
}

console.log(array1.findIndex(findFirstLargeNumber));  // 3

.of()

Moz Docs Array.from()

... creates a new Array instance with a variable number of arguments, regardless of number or type of the arguments.

const ary1 = Array.of(7)
const ary2 = Array(7) 

console.log(ary1)  // [7]
console.log(ary2)  // [ , , , , , , ]

.from()

Moz Docs Array.from()

... creates a new, shallow-copied Array instance from an array-like or iterable object.

const str = 'foo';
const result = Array.from(str);
console.log(Array.from(str))  // ["f", "o", "o"]

.from() with .map()

const ary = [4, 8, 16];

const map = val => val * 2

const result = Array.from(ary, map)

console.log(result)  // ["f", "o", "o"]

.fill()

Moz Docs Array.fill()

... fills all the elements of an array from a start index to an end index with a static value. The end index is not included.

var array1 = [1, 2, 3, 4];

// fill with 0 from position 2 until position 4
console.log(array1.fill(0, 2, 4))  // [1, 2, 0, 0]

// fill with 5 from position 1
console.log(array1.fill(5, 1));  // [1, 5, 5, 5]

console.log(array1.fill(6));  // [6, 6, 6, 6]

.copyWithin()

Moz Docs Array.copyWithin()

... shallow copies part of an array to another location in the same array and returns it, without modifying its size.

arr.copyWithin(target)
arr.copyWithin(target, start)
arr.copyWithin(target, start, end)

var array1 = [1, 2, 3, 4, 5];

// place at position 0 the element between position 3 and 4
console.log(array1.copyWithin(0, 3, 4));  // [4, 2, 3, 4, 5]

// place at position 1 the elements after position 3
console.log(array1.copyWithin(1, 3));
// expected output: Array [4, 4, 5, 4, 5]

Part 5 - Object Methods

Is

Number.isNaN()

const val = undefined + undefined
console.log(val)  // NaN
console.log(Number.isNaN(val))  // true

Array.isArray()

const ary = []
console.log(Array.isArray(ary))  // true
const obj = {}
console.log(Array.isArray(obj))  // false

Object.is()

Moz Docs Object.is()

... determines whether two values are the same value.

console.log(123 === 123)  // true
console.log(Object.is(123, 123))  // true

This is not the same as being equal according to the == operator. The == operator applies various coercions to both sides (if they are not the same Type) before testing for equality (resulting in such behavior as "" == false being true), but Object.is doesn't coerce either value.

This is also not the same as being equal according to the === operator. The === operator (and the == operator as well) treats the number values -0 and +0 as equal and treats Number.NaN as not equal to NaN.

Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is

console.log(0 === -0)
console.log(Object.is(0, -0))

.assign()

Moz Docs Object.assign()

... used to copy the values of all enumerable own properties from one or more source objects to a target object. It will return the target object.

const obj1 = {1: 'Hello,'}
const obj2 = {2: 'world!'}
const obj3 = Object.assign({}, obj1, obj2)
console.log(obj3)  // {1: "Hello,", 2: "world!"}

.keys()

var foo = {
	a: 1,
	b: 2,
	c: 3
}

console.log(Object.keys(foo))  // ["a", "b", "c"]

.values()

Moz Docs Object.values()

... returns an array of a given object's own enumerable property values, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).

var obj = {
	2: 'A',
	1: 'B',
	0: 'C',
}
console.log(Object.values(obj))

.keys()

Moz Docs Object.keys()

... returns an array of a given object's property names, in the same order as we get with a normal loop.

var obj = {
	a: 1,
	b: 2,
	c: 3
}

console.log(Object.keys(obj))  // ["a", "b", "c"] 

.entries()

Moz Docs Object.keys()

... returns an array of a given object's own enumerable property [key, value] pairs, in the same order as that provided by a for...in loop (the difference being that a for-in loop enumerates properties in the prototype chain as well).

var obj = {
	a: 1,
	b: 2,
	c: 3
}

console.log(Object.entries(obj))
//	[
//    ["a", 1],
//    ["b", 2],
//    ["c", 3] 
//  ]

Challenge 1 Challenge 1 - Solution

.freeze()

Moz Docs Object.freeze()

The Object.freeze() method freezes an object: that is, prevents new properties from being added to it; prevents existing properties from being removed; and prevents existing properties, or their enumerability, configurability, or writability, from being changed, it also prevents the prototype from being changed. The method returns the passed object.

const object1 = {
  property1: 42
};

const object2 = Object.freeze(object1);

object2.property1 = 33;

.isFrozen()

Moz Docs Object.isFrozen()

The Object.isFrozen() determines if an object is frozen.

const object1 = {
  property1: 42
};

console.log(Object.isFrozen(object1));
// expected output: false

Object.freeze(object1);

console.log(Object.isFrozen(object1));

.seal()

Moz Docs Object.seal()

...seals an object, preventing new properties from being added to it and marking all existing properties as non-configurable. Values of present properties can still be changed as long as they are writable.

const object1 = {
  property1: 42
};

Object.seal(object1);
object1.property1 = 33;
console.log(object1.property1);
// expected output: 33

delete object1.property1; // cannot delete when sealed
console.log(object1.property1);
// expected output: 33

.isSealed()

Moz Docs Object.isSealed()

The Object.isSealed() method determines if an object is sealed.

const object1 = {
  property1: 42
};

console.log(Object.isSealed(object1));
// expected output: false

Object.seal(object1);

console.log(Object.isSealed(object1));
// expected output: true

Meta-Programming

Meta-Programming is a programming technique in which computer programs have the ability to treat programs as their data. It means that a program can be designed to read, generate, analyse or transform other programs, and even modify itself while running. In some cases, this allows programmers to minimize the number of lines of code to express a solution, thus reducing the development time. It also allows programs greater flexibility to efficiently handle new situations without recompilation.

https://en.wikipedia.org/wiki/Metaprogramming

Starting with ECMAScript 2015, JavaScript gains support for the Proxy and Reflect objects allowing you to intercept and define custom behavior for fundamental language operations (e.g. property lookup, assignment, enumeration, function invocation, etc). With the help of these two objects you are able to program at the meta level of JavaScript.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Meta_programming

Symbols

Moz Docs Symbols

The most common use of symbols by programmers is for performing language reflection (particularly for callbacks), and most common indirectly is their use to create object linkages.

Symbol Programming

  • Unique
  • Immutable
  • They are tokens to be used as unique ids
  • Symbol-keyed properties will be completely ignored when using JSON.stringify():
var a = String('A')
var b = String('A')
console.log(a === b)  // true

var a = Symbol('A')
var b = Symbol('A')
console.log(a === b)  // false
var foo = {
	[Symbol('bar')]: 123
}

console.log(foo.bar)  // undefined

console.log(Object.getOwnPropertySymbols(foo)[0])  // [Symbol(bar)]
console.log(foo[Object.getOwnPropertySymbols(foo)[0]])  // 123

console.log(foo[Reflect.ownKeys(foo)[0]])  // 123
var  myPrivateMethod  = Symbol();
this[myPrivateMethod] = function() {...};

Reflect()

In computer science, reflection is the ability of a computer program to examine, introspect, and modify its own structure and behavior at runtime.

Object Reflection Programming

.has(obj, 'propertyName')

const foo = {a: 1, b: 2}

const result = Reflect.has(foo, 'a')
console.log(result)  // true

.ownKeys()

const bar = {a: 1, b: 2}

const keys = Reflect.ownKeys(bar)
console.log(keys)  // ['a', 'b']
const baz = {a: 1, b: 2}

Reflect.ownKeys(baz).map(key => {
	baz[key] = "πŸ‘Š"
})
console.log(baz)  // {a: "πŸ‘Š", b: "πŸ‘Š"}

Object Extensibility and Sealing

const qux = {a: 1, b: 2}
console.log(qux)  // {b: 2}
console.log(Reflect.isExtensible(qux)) // false

Reflect.set(qux, 'c', 3)
console.log(qux)  // {b: 2}

Reflect.preventExtensions(qux)
console.log(Reflect.isExtensible(qux))  // false
Reflect.set(qux, 'd', 4)
// Nothing was added!
console.log(qux)  // {a: 1, b: 2, c: 3}

// Delete still works!
console.log(Reflect.deleteProperty(qux, 'a'))  // true
console.log(qux)

// But seal stops delete too!
Object.seal(qux)
console.log(Reflect.deleteProperty(qux, 'b')) // false
console.log(qux)

Proxy()

Traps

[Traps are the] methods that provide property access. This is analogous to the concept of traps in operating systems.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

  • get()
  • set()
  • has()
  • ownKeys()
  • apply()
  • call()
  • construct()
  • ...etc.

new Proxy(target, handler)

const target = {foo: 'bar'};

const handler = {
	set: (target, prop, val) => {
		target[prop] = val.replace('πŸ‘»', 'πŸŽƒ')
	}
}

const proxy = new Proxy(target, handler);

proxy.foo = 'BOO! πŸ‘»';

console.log(target.foo);

About

References notes for a short series of lectures on the latest ~JavaScript language features.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published