function hypot (x, y) {
return Math.sqrt(x ** 2 + y ** 2);
}
let hypot = function (x, y) {
return Math.sqrt(x ** 2 + y ** 2);
};
let hypot = (x, y) => Math.sqrt(x ** 2 + y ** 2);
let hypot = (x, y) => {
return Math.sqrt(x ** 2 + y ** 2);
}
hypot(3, 4); // 5
<button id=button>Click me</button>
let handler = evt => console.log("Thank you 😊");
button.addEventListener("click", handler());
undefined)
as the event listener. This is a very common mistake.
let hypot = (x, y) => Math.sqrt(x ** 2 + y ** 2);
console.log(hypot(5)); // What does this log?
let sum = function (...numbers) {
let total = 0;
for (let n of numbers) {
total += n;
}
return total;
}
let avg = function(...numbers) {
return sum(...numbers) / numbers.length;
}
let hypot = (x, y = x) => Math.sqrt(x ** 2 + y ** 2);
console.log(hypot(5)); // What does this log?
let event = document.createEvent('KeyboardEvent');
event.initKeyEvent("keypress", true, true, null, null,
true, false, true, false, 9, 0);
let event = new KeyboardEvent("keypress", {
ctrlKey: true,
shiftKey: true,
keyCode: 9
});
<button id=button>Click me</button>
button.addEventListener("click", function (event) {
event.target.textContent = "Thank you 😊";
});
let hypot = (x, y) => Math.sqrt(x ** 2 + y ** 2);
let f = x => x + 1;
let g = f;
f = 42;
g(1);
let greet = function() {
console.log(`Hi, I’m ${this.name}`);
};
let instructors = [
{name: "Lea", greet: greet},
{name: "David", greet: greet}
];
for (let instructor of instructors) {
instructor.greet();
}
" Hello! ".trim() // "Hello!"
this
function interpolate(min, max) {
return function (p) {
return min + (max - min) * p;
}
}
let range = interpolate(1, 10);
console.log(range(.1), range(.5), range(.9));
Functions that:
function curry(f, ...fixedArgs) {
return function(...args) {
return f(...fixedArgs, ...args);
}
}
Or, more succinctly (but less clearly):
let curry = (f, ...fixedArgs)
=> (...args)
=> f(...fixedArgs, ...args);
function interpolate(min, max, p) {
return min + (max - min) * p;
}
let range = curry(interpolate, 1, 10);
console.log(range(.1), range(.5), range(.9));
Common patterns for transforming a collection of items en masse
Array object. Each takes a
callback: a function that will be applied
to each item.
let numbers = [1, 2, 3, 4];
let squares = [];
for (let n of numbers) {
squares.push(n ** 2);
}
let numbers = [1, 2, 3, 4];
let squares = numbers.map(n => n ** 2);
let numbers = [1, 2, 3, 4, 5];
let odd = [];
for (let n of numbers) {
if (n % 2) {
odd.push(n);
}
}
let numbers = [1, 2, 3, 4, 5];
let odd = numbers.filter(n => n % 2);
let numbers = [1, 2, 3, 4, 5];
let sum = 0;
for (let n of numbers) {
sum += n;
}
let numbers = [1, 2, 3, 4, 5];
let sum = numbers.reduce(
(acc, current) => acc + current,
0 // initial value
);
Reduce code arguably longer or more complicated, but still more informative
Sum the squares of odd numbers from 1 to 5
let numbers = [1, 2, 3, 4, 5];
let result = numbers
.filter(n => n % 2)
.map(n => n ** 2)
.reduce((acc, cur) => acc + cur, 0);
sort
filter
map
reduce
find
Object.groupBy
flatMap
every
some
forEach
reduceRight
Map.groupBy
let i = 5;
function logI() {
console.log(i);
}
logI();
i = 6;
logI();
function logI() {
console.log(i);
}
let i = 5;
logI();
i = 6;
logI();
let i = 5;
function logI() {
console.log(i);
}
logI();
{
let i = 6;
logI();
}
Functions use lexical scope: they capture
variables when the function is defined,
not when the function is executed.
This is implemented using closures.
| expression | runtime value | |||||
|---|---|---|---|---|---|---|
| number | 5 |
5 |
||||
| object | { prof: "David" } |
{ prof: "David" } |
||||
| function | () => x |
|
let i = 5;
function logI() {
console.log(i);
}
logI(); // 5
i = 6;
logI(); // 6
| Environment |
let i →
56
|
|---|---|
| Code |
|
let i = 5;
function logI() {
console.log(i);
}
logI(); // 5
{
let i = 6;
logI(); // 5
}
| Environment |
let i →
5
|
|---|---|
| Code |
|
constlet means the closed-over
binding may change later.
const unless you really need
mutation.
<button id="button">Click me</button>
let i;
for (i = 1; i <= 3; i++) {
button.addEventListener("click", evt => {
console.log(i);
});
}
<button id="button">Click me</button>
let i;
for (i = 1; i <= 3; i++) {
let j=i;
button.addEventListener("click", evt => {
console.log(j);
});
}
<button id="button">Click me</button>
for (let i = 1; i <= 3; i++) {
button.addEventListener("click", evt => {
console.log(i);
});
}
<button id="button">Click me</button>
for (var i = 1; i <= 3; i++) {
button.addEventListener("click", evt => {
console.log(i);
});
}
var variables are function scoped
function counter(start = 0) {
let i = start;
return () => ++i;
}
let a = counter();
console.log(a(), a()); // 1, 2
function counter(start = 0) {
let i = start;
return () => ++i;
}
let a = counter();
console.log(a(), a()); // 1, 2
| Environment |
let i →
012
|
|---|---|
| Code |
|
What is happening here?
counter defines local variable
i
i variable
counter returns
class Person {
constructor (name, birthday) {
this.name = name;
this.born = new Date(birthday);
}
getAge () {
const ms = 365 * 24 * 60 * 60 * 1000;
return (new Date() - this.born) / ms;
}
}
let david = new Person(
"David Karger",
"1967-05-01T01:00"
);
let lea = new Person(
"Lea Verou",
"1986-06-13T13:00"
);
console.log(lea.getAge(),
david.getAge());
new
class PowerArray extends Array {
constructor(...args) {
super(...args);
}
isEmpty() {
return this.length === 0;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
console.log(arr.isEmpty()); // false
let arr2 = PowerArray.from([1, 2, 3]);
console.log(arr2.isEmpty()); // false
super is bound to the class you inherit
from if you want to access its properties or
methods.
class Clicker {
#clicks = 0;
click () {
this.#clicks++
}
getClicks () {
return #clicks;
}
}
<click-counter></click-counter>
class ClickCounter extends HTMLElement {
#clicks = 0;
constructor() {
super();
this.#render();
this.addEventListener('click', this.#clicked)
}
#render () {
this.innerHTML = `Clicked ${this.#clicks}x`;
}
#clicked () {
this.#clicks++;
this.#render();
}
}
customElements.define('click-counter', ClickCounter);
this
Based on the design of Self, this does not
just work in classes.
We often create JavaScript objects directly, with no class definition at all.
let greet = function() {
console.log(`Hi, I’m ${this.name}`);
};
let instructors = [
{name: "Lea", greet: greet},
{name: "David", greet: greet}
];
for (let instructor of instructors) {
instructor.greet();
}
We can add a function as a property, and that function can
call this.
this come from?
let greet = function() {
console.log(`Hi, I’m ${this.name}`);
};
let instructors = [
{name: "Lea", greet: greet},
{name: "David", greet: greet}
];
for (let instructor of instructors) {
instructor.greet();
}
This feels a bit strange: this was not declared
anywhere. 🤔
this to functions when they are called
let greet = function(this) {
console.log(`Hi, I’m ${this.name}`);
};
let instructors = [
{name: "Lea", greet: greet},
{name: "David", greet: greet}
];
for (let instructor of instructors) {
instructor.greet(instructor);
}
The runtime implicitly gives every function a
this variable and sets it from the context
where the function is called, usually the object before the
dot.
this
is a special variable declared in every scope.
But what is its value?
<script type="module">
console.log(this);
</script>
<script type="module">
let logContext = function() {
console.log(this);
}
logContext();
</script>
globalThis object
globalThis
=== window
<script type="module">
console.log(globalThis); // Window
console.log(globalThis.HTMLElement === HTMLElement); // true
console.log(globalThis.globalThis); // Window
console.log(this); // undefined
</script>
<script type="module">
globalThis.logContext = function() {
console.log(this);
}
</script>
<script type="module">
logContext(); // undefined
</script>
<script type="module">
globalThis.logContext = function() {
console.log(this);
}
</script>
<script type="module">
logContext(); // undefined
globalThis.logContext(); // Window
</script>
button.addEventListener("click", function(event) {
console.log(this);
});
this and nested functions
<button class="btn">Old</button>
document.querySelector(".btn").addEventListener("click", function () {
setTimeout(function () {
this.textContent = "New";
}, 100);
});
this
let person = {
name: "David",
hello: () => console.log(this)
};
person.hello(); // undefined
this
<button class="btn">Old</button>
document.querySelector(".btn").addEventListener("click", function () {
setTimeout(() => {
this.textContent = "New";
}, 100);
});
this yourselffunc.call(context, ...args)
func.apply(context, args)
function logContext() {
console.log(this);
}
logContext(); // logs undefined
logContext.call(document); // logs document
this permanently:
func.bind(context, ...args)
Returns a new function whose context is always bound to the first argument
function logContext() {
console.log(this);
}
let logContext2 = logContext.bind({foo: 1});
logContext2(); // logs {foo: 1}
logContext2.call(document); // logs {foo: 1}
this Takeawaysthis is flexible, always declared, and
resolved dynamically, but that flexibility can be a footgun
this works best in classes and simple
objects
this is
bound to in some context, it may be best to remove it
arr[0] = "hi";
arr.slice(0, 1)
arr.length?
console.log(document.body.innerHTML);
// 👆🏼 serializes <body>’s contents as an HTML string
document.body.innerHTML = "<em>Hi</em>";
// 👆🏼 parses the string provided
// and replaces <body>’s subtree with it
let lea = {
name: "Lea",
birthday: new Date("1986-06-13T13:00"),
get age () {
const ms = 365 * 24 * 60 * 60 * 1000;
return (new Date() - this.birthday) / ms;
}
}
console.log(lea.age); // 39.82507970709665
let lea = {
birthday: new Date("1986-06-13T13:00"),
get age () {
const ms = 365 * 24 * 60 * 60 * 1000;
return (new Date() - this.birthday) / ms;
}
}
lea.age = 30;
console.log(lea.age);
let lea = {
birthday: new Date("1986-06-13T13:00"),
get age () {
const ms = 365 * 24 * 60 * 60 * 1000;
return (new Date() - this.birthday) / ms;
}
}
lea.birthday = new Date("1992-04-01T13:00");
console.log(lea.age);
let lea = {
birthday: new Date("1986-06-13T13:00"),
set age (a) {
const ms = 365 * 24 * 60 * 60 * 1000;
this.birthday = new Date((Date.now() - ms * a));
},
}
lea.birthday = new Date("1990-04-01T13:00");
lea.age = 3;
console.log(lea.birthday); // ... 2023 ...
obj.foo++;
vs
obj.setFoo(obj.getFoo() + 1);
export to allow visibility elsewhereimport to incorporate names from elsewhere
<script type="module">export const obj = 'square';export {obj, draw ...}import { obj, draw} from
'./modules/square.js';
import { obj as square} from
'./modules/square.js';
obj visible for importobj, but call it
square in my namespace
let, function, const
as lets you avoid name
conflicts between modules