< YALU STUDIO >

JavaScript Notes

Word count: 2,673 / Reading time: 17 min
2019/11/28 Share

Types, Values, and Variables

NaN

NaN does not compare equal to any other value, including itself.

You can’t write x === NaN.

Instead, you should write x != x or Number.isNaN(x)

Text

JavaScript uses the UTF-16 encoding of the Unicode character set. Unicode characters whose codepoints do not fit in 16 bits are encoded following the rules of UTF-16 as a sequence of two 16-bit values.

1
2
let e = "𝑒"
e.length // => 2

Template Strings

ES6 has a built-in tag function String.raw() which returns the text within backticks raw, without any processing of backslash escapes:

1
String.raw`\n` // => \n

null and undefined

null undefined
Language keyword yes no (predefined global constant)
Type object undefined
Meaning A speacial object value that indicates “no object” The value of variables that have not been initialized and the value you get when you query the value of an object property or array element that does not exist
1
2
null == undefined // => true
null === undefined // => false

Type Conversions

1
2
3
4
8 + " yalu" // => "8 yalu"
"2" * "7" // => 14
1 - "x" // => NaN
n + " yalu" // => "NaN yalu"
Value to String to Number to Boolean
undefined “undefined” NaN false
null “null” 0 false
true “true” 1
false “false” 0
“” 0 false
“2.7” 2.7 true
“one” NaN true
0 “0” false
-0 “0” false
NaN “NaN” false
Infinity “Infinity” true
-Infinity “-Infinity” true
1 “1” true
{} (any object) true
[] “” 0 false
[2] (one numeric element) “2” 2 true
[‘a’] (any other array) use join() NaN true
function(){} (any function) NaN true

You can convert numbers in other bses (between 2 and 36).

1
2
3
4
let n = 17
let binary = "0b" + n.toString(2); // => "0b10001"
let octal = "0o" + n.toString(8) // => "0o21"
let hex = "0x" + n.toString(16) // => "0x11"

Variable Declaration and Assignment

let declares variables that change.

const declares constant variable.

It is an untyped language, so a JavaScript variable can hold a vlue of any type.

var has the same syntax as let. However, it does not have block scope. Instead, it is scoped to the body of the containing function no matter how deeply nested it is inside the function. When it is outside of a function body, it declares a global variable and is implemented as properties of the global object. For example, var x = 2 is like global.x = 2; or window.x = 2;. And it cannot be deleted with the delete operator.

Destructuring Assignment

The value on the right-hand side of the equals sign is an array or object (a “structured” value) and the left-hand side specifies one or more variable names using a syntax that mimics array and object literal syntax.

1
2
3
4
5
6
7
8
9
10
11
12
let [x, y] = [1, 2] // x => 1, y => 2
[x, y] = [y, x] // x => 2, y => 1
[x, y] = [1] // x => 1, y => undefined
[x, y] = [1, 2, 3] // x => 1, y => 2
[ , x, , y] = [1, 2, 3, 4] // x => 2, y => 4
[x, ...y] = [1, 2, 3, 4] // x => 1, y => [2, 3, 4]

let transparent = {r: 0.0, g: 0.0, b: 0.0, a: 1.0};
let {r, g, b} = transparent; // r = 0.0, g = 0.0, b = 0.0

let {sin, cos, tan} = Math; // sin = Math.sin, cos = Math.cos, tan = Math.tan
{cos: cosine, tan: tangent} = Math; // cosine = Math.cos, tangent = Math.tan

If the left-hand side of the assignment had included a variable whose name was not a property of the object, that variable would be assigned undefined.

Objects

Property

Attempting to query a property of an object that does not exist throws an error. It can be avoided by using the && operator.

1
let surname = book && book.author && book.author.surname;

Delete Properties:

1
2
delete book.author;
delete book["title"];

It can conly delete own properties, not inherited ones. To delete an inherited property, you must delete it from the prototype object in which it is defined.

in can distinguish between properties that do not exist and properties that exist but have been set to undefined.

1
2
3
4
5
6
7
let o = { x: undefined };
o.x !== undefined // => false
o.y !== undefined // => false
"x" in o // => true
"y" in o // => false
delelte o.x;
"x" in o // => false

Serializing Objects

Object serialization is the process of converting an object’s state to a string from which it can later be restored.

JSON - JavaScript Object Notation

1
2
3
let o = {x: 1, y: {z: [false, null, ""]}};
let s = JSON.stringify(o);
let p = JSON.parse(s);

Object Methods

toString()

The default toString() method simply returns [object Object] which does not display useful information. You can define your own toString() like this:

1
2
3
4
5
let o = {
x: 1,
y: 2,
toString: function() { return `(${this.x}, ${this.y})`; }
}

valueOf()

It converts an object to some primitive type other than a string, usually a number. And obejcts can then be compared with < and >.

1
2
3
4
5
6
7
8
9
10
11
12
13
let p = {
x: 0,
y: 0,
valueOf: function(){ return Math.hypot(this.x, this.y); }
}
let p1 = Object.create(p)
p1.x = 3
p1.y = 4
let p2 = Object.create(p)
p2.x = 2
p2.y = 5

p1 < p2 // => true

Arrays

  • untyped: an array element may be of any type
  • index from 0 up to (2^32-2)
  • dynamic: grow or shrink as needed
  • sparse: elements need not have contiguous indexes

Because of the optional trailing comma, [,,] has a length of 2, not 3.

1
2
let undefs = [ , , ]
undefs.length // => 2

To remove duplicates in an array, you can convert it to a set and converted it back to an array.

1
2
let letters = [...'yalu studio'];
[...new Set(letters)]

Create an array:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let a = new Array()

// Create an array with a length of 10
a = new Array(10)

// Explicitly specify two or more array elements or a sigle non-numeric element
a = new Array(1, 2, 3, 4, 5)

// The Array() cannot be used with a single numberic element. To address this, use the Array.of() function.
// ** PREFERED **
Array.of()
Array.of(10)
Array.of(1,2,3)

// Another way to true-copy an array-like object.
let copy = Array.from(original);

When you try to query a nonexistent property of any object, you don’t get an error, instead you get undefined.

Array’s length is guaranteed to be the largest index + 1, whether it is sparse or not. By setting the length property, the rest of the array will be deleted.

1
2
3
4
let a = [1, 2, 3, 4, 5];
a.length = 3; // => [1, 2, 3]
a.length = 0; // => []
a.length = 5; // => equals to new Array(5)

Adding elements into the array

1
2
3
4
5
6
// Simply assign values to new indexes
a = []
a[0] = "zero"
// Use push() add one or more to the end of the array
a.push("zero")
a.push("one", "two")

forEach()

1
2
3
let data = [1, 2, 3, 4, 5]
data.forEach((v, i, a) => a[i] = v + 1)
// => data == [2, 3, 4, 5, 6]

map()

1
2
let a = [1, 2, 3];
a.map(x => x * x); // a == [1, 4, 9]

filter()

Keeps the values that let the function return true.

1
2
3
4
5
6
7
8
let a = [5, 4, 3, 2, 1];
a.filter(x => x < 3); // a == [2, 1]

// To turn a sparse array into a dense one
let dense = sparse.filter(() => true)

// To close gaps and remove undefined and null
dense = sparse.filter(x => x !== undefined && x !== null)

find() and findIndex()

Return the first element/ index found in the array, otherwise return -1

every() and some()

1
2
3
4
5
6
7
// return true if and only if the predicate function returns true for all elements in the array
a = [1, 2, 3, 4, 5]
a.every(x => x < 10) // => false

// return true if there exists at least one element in the array for which the predicate returns true
a.some(isNaN) // => false
a.some(x => x == 2) // => true

reduce() and reduceRight()

reduce() takes 2 arguments, the first one is the accumulated value, initially 0. If ignored, it uses the second arguments as the first arguments. reduceRight() is the same, but start from the highest index to the lowest.

1
2
let a = [1, 2, 3, 4, 5];
a.reduce((sum, val) => sum + val)

Add to / Remove from the arrays

push() add to the end of the array
unshift() add to the beginning of the array
pop() remove the last element of the array
shift() remove the first element of the array

splice()

The first argument is the start index, and the second index is the number of the elements are to deleted.

1
2
3
let a = [1, 2, 3, 4, 5, 6, 7, 8]
a.splice(1, 2) // => [2, 3]
// a == [1, 4, 5, 6, 7, 8]

The following additional arguements will be inserted at the start position.

1
2
3
let a = [1, 2, 3, 4, 5]
a.splice(2, 0, 'a', 'b')
a.splice(2, 2, [1, 2])

fill()

1
2
3
4
5
6
let a = new Array(5)
a.fill(0) // => [0, 0, 0, 0, 0]
// provided with start index
a.fill(9, 1) // => [0, 9, 9, 9, 9]
// provided with start and end index
a.fill(8, 2, -1) // => [0, 9, 8, 8, 9]

copyWithin()

Arguments

  • The first index to copy to
  • The first index to be copied (default 0)
  • The end index to be copied (default till the end)

findAll()

You can implements it yourself

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// find all indexes of value x in array a
function findAll(a, x) {
let results = [],
len = a.length,
pos = 0;
while (pos < len) {
pos = a.indexOf(x, pos);
if (pos === -1) {
break;
}
results.push(pos);
pos += 1;
}
return results;
}

includes()

Test whether it is in the array, can be used to test NaN.

1
2
3
let a = [1, true, 3, NaN];
a.includes(NaN) // => true
a.indexOf(NaN) // => -1

sort()

By default, sorted in alphabetical order. Alternatively, provide a function that the first argument appears before the second when the function returns number less than 0.

1
2
3
let a = [33, 4, 1111, 222];
a.sort() // => [1111, 222, 33, 4]
a.sort((a, b) => b - a); // => [1111, 222, 33, 4]

Functions

Arrow functions

1
2
3
// Return an object
const f = x => { return {value: x}; };
const g = x => {{ value: x }};

Type check

Example:

1
2
3
4
5
6
7
8
9
function sum(a) {
let total = 0;
for (let element of a) {
if (typeof element !== 'number')
throw new TypeError("sum(): elements must be numbers");
total += element;
}
return total;
}

Anonymous function

The parentheses are required. Otherwise, it parses the function keyword as a function declaration statement.

1
2
3
4
5
6
7
(function() {
// codes
}());

(() => {
// codes
}) ();

Closures

Fundamental rule of lexical scoping: JavaScript functions are executed using the scope they were defined in.

this is a keyword. this cannot be used within functions defined with the function keyword. Use arrorw functions => instead, or call bind() on the closure before returning it, or assign the outer this value to a variable that the closure will inherit:

1
const self = this;

Classes

Defining a class

Using a factory method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function range(from, to) {
let r = Object.create(range.methods);
r.from = from;
r.to = to;
return r;
}

range.methods = {

includes(x) {
return this.from <= x && x <= this.to;
},

*[Symbol.iterator]() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},

toString() {
return "(" + this.from + "..." + this.to + ")";
}
}

let r = range(1, 3);
r.includes(2)
r.toString()
[...r]

Using a constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Range(from, to) {
this.from = from;
this.to = to;
}

Range.prototype = {

includes: function(x) {
return this.from <= x && x <= this.to;
},

[Symbol.iterator]: function*() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
},

toString: function() {
return "(" + this.from + "..." + this.to + ")";
}
}

let r = new Range(1, 3);
r.includes(2)
r.toString()
[...r]
  • Names begin with a capital letter
  • Use new keyword

Using the class keyword

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Range {
constructor(from, to) {
this.from = from;
this.to = to;
}

includes(x) {
return this.from <= x && x <= this.to;
}

*[Symbol.iterator]() {
for(let x = Math.ceil(this.from); x <= this.to; x++) yield x;
}

toString() {
return "(" + this.from + "..." + this.to + ")";
}
}

let r = new Range(1, 3);
r.includes(2)
r.toString()
[...r]
  • Use object literal method shorthand, omit the function keyword
  • No commas are used to seperate functions
  • constructor function can be ommitted if it doesn’t need to do initializing

Inherit from another class

1
2
3
4
5
6
7
8
9
class Span extends Range {
constructor(start, length) {
if (length > 0) {
super(start, start + length);
} else {
super(start + length, start);
}
}
}
  • Not “hoisted” (moved to the top of the file)
  • Declared in strict mode

Private fields

Private fields start with a #.

1
2
3
4
class Buffer {
#size = 0;
get size() { return this.#size; }
}

Iterators and Generators

An Iterable object is any object with a special iterator method that returns an iterator object.

An iterator is any object with an next() method that returns an iteration result object.

An iteration result object is an object with properties named value and done

1
2
3
4
5
let iterable = [99];
let iterator = iterable[Symbol.iterator]();
for(let result = iterator.next(); !result.done; result = iterator.next()) {
console.log(result.value)
}

Lazy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function words(s) {
var r = / +|$/g;
r.lastIndex = s.match(/[^ ]/).index;
return {
[Symbol.iterator]() {return this;},
next() {
let start = r.lastIndex;
if (start < s.length) {
let match = r.exec(s);
if (match) {
return {value: s.substring(start, match.index)};
}
}
return {done: true};
}
};
}

Generator Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function* fibonacciSequence() {
let x = 0, y = 1;
for(;;) {
yield y;
[x, y] = [y, x+y];
}
}

function* take(n, iterable) {
let it = iterable[Symbol.iterator]();
while(n-- > 0) {
let next = it.next();
if (next.done) return;
else yield next.value;
}
}

[...take(5, fibonacciSequence())] // => [1, 1, 2, 3, 5]

yield* and Recursive Generators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* sequence(...iterables) {
for(let iterable of iterables) {
for(let item of iterable) {
yield item;
}
}
}

// It equals to the following

function* sequence(...iterables) {
for(let iterable of iterables){
yield* iterable;
}
}

Note that yield* must be used in a generator function, thus cannot be used in an arrow function.

Asynchronous JavaScript

Timer

1
2
3
4
5
6
7
8
9
// Set timeout
setTimeout(checkForUpdates, 60000);

// Set intervals
let updateIntervalId = setInterval(checkForUpdates, 60000);

function stopCheckingForUpdates() {
clearInterval(updateIntervalId);
}

Promises

A Promise is an object that represents the result of an asynchronous computation.

1
2
3
4
getJSON('/api/user/profile')
.then(displayUserProfile)
.catch(handleProfileError)
.finally(cleanup);

Error handling

p.then(null, c) equals to p.catch(c)

Parallel

1
2
3
4
5
6
7
8
const urls = [ /* zero or more urls here */ ]

promises = urls.map(url => fetch(url).then(r => r.text()))

// Get a promise to run all promises in parallel
Promise.all(promises)
.then(bodies = > { /* do sth */ })
.catch(e => console.error(e))

async and await

You can only use the await keyword within functions that have been declared with the async keyword.

1
2
3
4
5
async function gestHighScore() {
let response = await fetch('/api/user/profile');
let profile = await response.json();
return profile.highScore;
}
CATALOG
  1. 1. Types, Values, and Variables
    1. 1.1. NaN
    2. 1.2. Text
    3. 1.3. Template Strings
    4. 1.4. null and undefined
    5. 1.5. Type Conversions
    6. 1.6. Variable Declaration and Assignment
    7. 1.7. Destructuring Assignment
  2. 2. Objects
    1. 2.1. Property
    2. 2.2. Serializing Objects
    3. 2.3. Object Methods
      1. 2.3.1. toString()
      2. 2.3.2. valueOf()
  3. 3. Arrays
    1. 3.0.1. forEach()
    2. 3.0.2. map()
    3. 3.0.3. filter()
    4. 3.0.4. find() and findIndex()
    5. 3.0.5. every() and some()
    6. 3.0.6. reduce() and reduceRight()
    7. 3.0.7. Add to / Remove from the arrays
    8. 3.0.8. splice()
    9. 3.0.9. fill()
    10. 3.0.10. copyWithin()
    11. 3.0.11. findAll()
    12. 3.0.12. includes()
    13. 3.0.13. sort()
  • 4. Functions
    1. 4.1. Arrow functions
    2. 4.2. Type check
    3. 4.3. Anonymous function
    4. 4.4. Closures
  • 5. Classes
    1. 5.1. Defining a class
      1. 5.1.1. Using a factory method
      2. 5.1.2. Using a constructor
      3. 5.1.3. Using the class keyword
    2. 5.2. Inherit from another class
    3. 5.3. Private fields
  • 6. Iterators and Generators
    1. 6.1. Lazy
    2. 6.2. Generator Examples
      1. 6.2.1. yield* and Recursive Generators
  • 7. Asynchronous JavaScript
    1. 7.1. Timer
    2. 7.2. Promises
      1. 7.2.1. Error handling
    3. 7.3. Parallel
    4. 7.4. async and await