Douglas Crockford: “JavaScript is the only language (that I'm aware of) where people feel they don't need to learn it before using it.”
- Main influences:
- Scheme (Functions)
- Self (Prototypes)
- Java (Syntax)
- Perl (Regex)
- Initial version “Mocha”
- written during 10 days in May 1995
- by Brendan Eich @ Netscape
Mocha
LiveScript
JavaScript
| (JScript)
| | ES1 ES2 ES3 ES5 ES6
| | | | | | |
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---
93 94 95 96 97 98 99 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
| | | | | | | | | | | | |
| | Internet | JSON | | Ajax jQuery | Node.js Electron.js |
| | Explorer XMLHTTP | | | |
| | | Mozilla Google Microsoft
| Netscape | Firefox Chrome Edge
| Navigator |
| Apple
Mosaic Safari
- Yearly releases since ECMAScript 2016 (ES7)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="pretty.css">
<script defer src="bundle.js"></script>
</head>
<body>
<div style="display: flex">
<textarea id="output" style="flex: 0px" rows="50"></textarea>
</div>
</body>
<script>
// ...
let response = await fetch("https://raw.githubusercontent.com/frectures/js/master/README.md");
let text = await response.text();
document.getElementById("output").value = text;
// ...
</script>
</html>- Browser (frontend)
- HTML (HyperText Markup Language)
- CSS (Cascading Style Sheets)
- JS (JavaScript)
- DOM (Document Object Model)
- Node.js (backend)
- JavaScript runtime environment built on Chrome's V8 JavaScript engine
- High-performance, asynchronous server code
- Electron.js = Chromium + Node.js (cross-platform desktop applications)
- Atom
- Discord
- Microsoft Teams
- Skype
- Visual Studio Code
- Dynamically typed
- Primitive types
booleanfalsetrue
number(double precision floating point)0.1 + 0.2 === 0.300000000000000041.0 + 2.0 === 3.0- Contains all integers up to 253 =
9_007_199_254_740_992
bigint(arbitrary precision integer)123nBigInt(456)
string(UTF-16)"Ain't no sunshine"'The cow says: "Moo"'`Frantic fans: "It's great!"`"🤨".length === 2- UTF-16:
55358, 56616(Java, C#, JavaScript) - UTF-8:
240, 159, 164, 168(Go, Rust, Swift)
- UTF-16:
undefined(mostly bugs)- uninitialized variables
- function calls without return value
- missing function arguments
- missing object properties
- array index out of bounds
null(intentional absence)
- Reference types
ObjectFunctionArray- ...
Exercise:
- Visit https://frectures.github.io/jspad/
- Copy/paste the program below
- Execute the whole program via F5
- Which logged types do you find surprising?
log(typeof false);
log(typeof 3.14);
log(typeof 123n);
log(typeof "hello");
log(typeof "hello"[0]);
log(typeof undefined);
log(typeof null);
log(typeof {});
log(typeof Math.log);
log(typeof []);Martin Fowler: “Choosing JavaScript was deeply ironic for me, as many readers may know, I'm not a fan of it. It has too many awkward edge cases and clunky idioms. But the compelling reason for choosing it over Java is that JavaScript isn't wholly centered on classes. There are top-level functions, and use of first-class functions is common. This makes it much easier to show refactoring out of the context of classes.”
varvariables have function scope:
function f() {
log(x); // undefined
{
log(x); // undefined
var x = 42;
log(x); // 42
}
log(x); // 42
}letvariables have block scope:
function f() {
log(x); // ReferenceError: x is not defined
{
log(x); // ReferenceError: x is not defined
let x = 42;
log(x); // 42
}
log(x); // ReferenceError: x is not defined
}constrequires initialization and forbids mutation of the variable:
function f() {
const uninitialized; // SyntaxError: Missing initializer in const declaration
const x = 42;
x = 97; // TypeError: Assignment to constant variable
const account = new Account();
account.deposit(42); // okay: mutates referenced object, not account variable
account = new Account(); // TypeError: Assignment to constant variable
}- Type-safe equality via
===and!==(compares type and value)
- Type-unsafe equality via
==and!=(confusing type coercions)
- There is no universal
equalsmethod for object equality
function releaseYearOfEcmaScript(version) {
if (version < 1) throw new Error(`non-positive ECMAScript version ${version}`);
// /////
switch (version) {
//////
case 1: return 1997;
case 2: return 1998;
case 3: return 1999;
case 4: throw new Error("abandoned ECMAScript version 4");
case 5: return 2009;
default: return (version >= 2015) ? version : 2009 + version;
} ////// /// ///
}Exercise:
releaseYearOfEcmaScripthandles some illegal inputs (such as 0 and 4)- Can you think of more illegal inputs to
releaseYearOfEcmaScript?- Handle those illegal inputs as you see fit
- Technically, conditions are not restricted to
booleanvalues:
function f(value) {
if (value) {
return "truthy";
} else {
return "falsy";
}
}
f(42) // truthy
f( 0) // falsy
f("0") // truthy
f("") // falsy- The following equivalent function explicitly lists all falsy values:
function g(value) {
const falsyValues = [ false, NaN, 0, -0, 0n, "", undefined, null ];
return falsyValues.includes(value) ? "falsy" : "truthy";
}- For the sake of maintenance, prefer
booleanvalues in conditions
Exercise:
- Replace the array in function
gwith aswitch- Is
gstill equivalent tof? Try all falsy values!
const s = "racecar";
for (let i = 0; i < s.length; ++i) {
log(s[i]);
}
// find the smallest integer without odd successor
let x = 1;
while (x + 1 > x) {
x *= 2;
log(x);
}
// log all 6 permutations of 3 numbers,
// inspired by C++ std::next_permutation
const numbers = [ 1, 2, 3 ];
do {
log(numbers);
[ 1, 2, 3 ]
[ 1, 3, 2 ]
[ 2, 1, 3 ]
[ 2, 3, 1 ]
[ 3, 1, 2 ]
[ 3, 2, 1 ]
} while (nextPermutation(numbers));
[ 1, 2, 3 ]Exercise:
- Start a loop at
x = 27and transformxonce per iteration:
- If
xis even, dividexby 2- If
xis odd, multiplyxby 3 and increment- log
xafter every change- Stop the loop when
xreaches 1- How many numbers were logged in total?
- Every parameter is initialized with its corresponding argument:
function join(array, separator, prefix, suffix) {
return prefix + array[0] + separator + array[1] + suffix;
}
const treats = [ "peanuts", "chocolate", "pretzels" ];
join(treats, " and ", "Yummy: ", "!");- Missing arguments are
undefined:
// 2 indistinguishable calls:
join(treats, " and ");
join(treats, " and ", undefined, undefined);
// 2 indistinguishable calls:
join(treats);
join(treats, undefined, undefined, undefined);- Manually replace
undefinedwith sensible defaults:
function join(array, separator, prefix, suffix) {
if (separator === undefined) {
separator = ", "; // replace undefined
}
prefix = prefix ?? ""; // replace null or undefined
suffix = suffix || ""; // replace all falsy values
return prefix + array[0] + separator + array[1] + suffix;
}- Automatically replace
undefinedsince ES2015:
////// //// ////
function join(array, separator = ", ", prefix = "", suffix = "") {
return prefix + array[0] + separator + array[1] + suffix;
}- JavaScript does not have named arguments
- But object literals
{ key: value, }work just fine:
function join(array, options) {
return options.prefix + array[0] + options.separator + array[1] + options.suffix;
}
join(treats,
{
prefix: "Yummy: ",
separator: " and ",
suffix: "!",
}); ///////////////- The
joinsignature does not reveal the available options - Accessing every option with
options.is quite cumbersome - Mitigate both problems with destructuring:
/////////////////////////////
function join(array, { prefix, separator, suffix }) {
return prefix + array[0] + separator + array[1] + suffix;
}
join(treats,
{
prefix: "Yummy: ",
separator: " and ",
suffix: "!",
});- Default values must be handled manually:
function join(array, { prefix, separator, suffix }) {
prefix = prefix ?? "";
separator = separator ?? ", ";
suffix = suffix ?? "";
return prefix + array[0] + separator + array[1] + suffix;
}
join(treats,
{
separator: " and ",
});Exercise:
- So far,
joinalways joins 2 array elements
- Fix
joinso it joinsarray.lengthelements- Does it work if
array.lengthis 1 or 0?- 🏆 Are you worried that
str = str + somethingin a loop may have quadratic complexity?
- Create a string of length 1 million via repeated concatenation of 1 character
- Measure the elapsed milliseconds with 2
Date.now()calls (before and after)- Repeat the experiment with length 10 million
- Is it ~10 times slower? linear complexity
- Is it ~100 times slower? quadratic complexity
- Higher-order functions accept (or return) other functions
- For example,
array.map(f)appliesfto allarrayelements:
const primes = [ 2, 3, 5, 7 ];
function square(x) {
return x * x;
}
primes.map(square); // [ 4, 9, 25, 49 ]
primes.map(function (x) { // [ 4, 9, 25, 49 ]
return x * x;
});
primes.map((x) => x * x); // [ 4, 9, 25, 49 ]
primes.map( x => x * x); // [ 4, 9, 25, 49 ]- A custom
mapimplementation could look like this:
///
function map(array, f) {
const result = [];
for (let i = 0; i < array.length; ++i) {
result.push(f(array[i], i, array));
} /////////////////////
return result;
}
map([ 2, 3, 5, 7 ], x => x * x); // [ 4, 9, 25, 49 ]- Note how
mappasses 3 arguments tof- element, index, array
- But
x => x * xdefines only 1 parameter- JavaScript ignores extraneous arguments
- Other popular higher-order functions are
filterandreduce:
const primes = [ 11, 2, 3, 13, 5, 7, 17, 19 ];
function smallerThanTen(x) {
return x < 10;
}
primes.filter(smallerThanTen); // [ 2, 3, 5, 7 ]
primes.filter(x => x < 10); // [ 2, 3, 5, 7 ]
function plus(x, y) {
return x + y;
}
primes.reduce(plus, 0); // 0 + 11 + 2 + 3 + 13 + 5 + 7 + 17 + 19 = 77
primes.reduce((sumSoFar, currentNumber) => sumSoFar + currentNumber, 0); // 77Exercise:
- Implement the custom
filterfunction below- Implement the custom
reducefunction below
function filter(array, predicate) {
const result = [];
// ...
return result;
}
filter([ 11, 2, 3, 13, 5, 7, 17, 19 ], x => x < 10); // [ 2, 3, 5, 7 ]
function reduce(array, update, result) {
// ...
}
reduce([ 11, 2, 3, 13, 5, 7, 17, 19 ], (x, y) => x + y, 0); // 77- In practice, you will encounter two “kinds” of objects:
- Object literals / JSON (JavaScript Object Notation)
- Class objects
- Technically, there is only 1 kind of object, but the whole truth is quite complicated
- A JavaScript object is essentially a
java.util.Map<String, Object>
// Object literal
const inventor = { forename: "Brandon", surename: "Eich" };
// read properties
inventor.forename // 'Brandon'
inventor["surename"] // 'Eich'
// write properties
inventor.forename = "Brendan"; // { forename: 'Brendan', surename: 'Eich' }
inventor["year"] = 1961; // { forename: 'Brendan', surename: 'Eich', year: 1961 }
// delete properties
delete inventor.forename; // { surename: 'Eich', year: 1961 }// JSON
const str = JSON.stringify(inventor); // '{"surename":"Eich","year":1961}'
const twin = JSON.parse(str); // { surename: 'Eich', year: 1961 }- Quotation marks around keys are:
- optional in literals
- mandatory in JSON
const primes = [ 2, 3, 5, 7 ];
typeof primes // 'object'
Object.getOwnPropertyNames(primes) // [ '0', '1', '2', '3', 'length' ]
primes.length // 4
primes[0] // 2
primes[4] // undefined
primes[4] = 11; // [ 2, 3, 5, 7, 11 ]
primes.push(13); // [ 2, 3, 5, 7, 11, 13 ]
primes.push(17, 19); // [ 2, 3, 5, 7, 11, 13, 17, 19 ]
primes[24] = 97; // [ 2, 3, 5, 7, 11, 13, 17, 19, <16 empty items>, 97 ]
primes.length = 5; // [ 2, 3, 5, 7, 11 ]
primes.pop(); // [ 2, 3, 5, 7 ]- JavaScript arrays are JavaScript objects with a special
lengthproperty- The keys are stringified numbers
- The (mutable!)
lengthproperty is the largest index +1 - There are no “array index out of bounds” errors:
- Reading from such an index gives
undefined - Writing to such an index grows the array
- Reading from such an index gives
const primes = [ 2, 3, 5, 7 ];
for (let i = 0; i < primes.length; ++i) {
log(primes[i]);
}
//
for (const value of primes) {
log(value); // 2, 3, 5, 7
}
//
for (const index in primes) {
log(index); // '0', '1', '2', '3'
}
primes.forEach(function (value, index, array) {
array[index] = -value;
});array.sort()sorts the elements by theirtoStringvalue:
const primes = [ 11, 2, 3, 13, 5, 7, 17, 19 ];
primes.sort();
[ 11, 13, 17, 19, 2, 3, 5, 7 ]array.sort(compare)sorts the elements according tocompare:
primes.sort((a, b) => a - b);
[ 2, 3, 5, 7, 11, 13, 17, 19 ]
primes.sort((a, b) => b - a);
[ 19, 17, 13, 11, 7, 5, 3, 2 ]compare(a, b) |
Order |
|---|---|
| negative | a belongs before b |
| positive | a belongs after b |
| zero | a is equivalent to b |
const inventors = ["Guido", "Bjarne", "James", "Brendan", "Anders"];
inventors.sort((a, b) => a.length - b.length);
["Guido",
"James",
"Bjarne",
"Anders",
"Brendan"]- Since ES2019, equivalent elements retain their order
const url = "https://api.chucknorris.io/jokes/random";
fetch(url) // result? blocking/asynchronous?- Network traffic is slow
- JavaScript has no language-level threads
fetchshould not block JavaScript- Asynchronous
Promises to the rescue:
| ECMAScript 2015 | ECMAScript 2017 |
|---|---|
function f() {
return fetch(url) // Promise<Response>
.then(response => response.text()) // Promise<string>
.then(str => JSON.parse(str)) // Promise<object>
.then(joke => log(joke)); // Promise<undefined>
} |
async function f() {
const response = await fetch(url); // Response
const str = await response.text(); // string
const joke = JSON.parse(str); // object
log(joke); // undefined
} |
fetch(url)resolves when the response header arrives- The response body may still be downloading
response.text()resolves when the body is:- downloaded
- unzipped
- converted to string
response.json()resolves when the body is:- downloaded
- unzipped
- converted to object
- 🚀 no intermediate string:
| ECMAScript 2015 | ECMAScript 2017 |
|---|---|
function f() {
return fetch(url) // Promise<Response>
.then(response => response.json()) // Promise<object>
.then(joke => log(joke)); // Promise<undefined>
} |
async function f() {
const response = await fetch(url); // Response
const joke = await response.json(); // object
log(joke); // undefined
} |
- 📝
async functions implicitly returnPromiseobjects
Exercise:
- Fetch all joke categories from
https://api.chucknorris.io/jokes/categories- Ignore these controversial categories:
- explicit
- political
- religion
- Fetch 1 joke per category from
https://api.chucknorris.io/jokes/random?category=insertCategoryHere- Log the joke
values, sorted alphabetically- 🏆 Sort the jokes by
idinstead- 🏆 Fetch the jokes in parallel with Promise.all






