Javascript was a language that had…
…the functionality of Scheme
(This Lecture)
…the object orientation of Self
(This Lecture)
…and the syntax of Java
(First JS lecture)
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);
map
filter
reduce
reduceRight
every
some
flat
flatMap
fill
forEach
groupBy
groupByToMap
function counter(start = 0) {
let i = start;
return () => ++i;
}
function counter(i = 0) {
return () => ++i;
}
let a = counter();
console.log(a(), a()); // 1, 2
What is happening here?
counter
defines local variable i
i
variablecounter
returns
<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>
for (let i = 1; i <= 3; i++) {
button.addEventListener("click", evt => {
console.log(i);
});
}
this
?this
is a special parameter that is always defined.
It is called the function context.
Always? What if we’re not in a method?
<script type="module">
console.log(this);
</script>
<script type="module">
let logContext = function() {
console.log(this);
}
logContext();
</script>
globalThis
objectglobalThis
=== 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>
let person = {
name: "David",
hello: () => console.log(this)
};
person.hello();
this
just points to the context of whatever scope they're defined in
button.addEventListener("click", function(event) {
console.log(this);
});
func.call(context, ...args)
func.apply(context, args)
function logContext() {
console.log(this);
}
logContext(); // logs undefined
logContext.call(document); // logs document
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}
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.
<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);
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); // 35.81898413454465
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); // 2017
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 namespacelet, function, const
as
lets you avoid name conflicts between modules