JavaScript
JavaScript carries a lot of historical burdens due to browsers maintaining backward compatibility, which prevents the removal of old features. Meanwhile, new features continue to be added, as it is the most widely used programming language on earth. As a result, the language can feel chaotic.
Don't be afraid. JavaScript is a reasonable language if you only use the good parts.
Where to Run JavaScript
One option is to use a Browser Console. In Chrome, you can open the
JavaScript console by pressing Cmd + Opt + J on Mac or Ctrl + Shift + J on Windows.
Another option is to use Node.js REPL. Install Node.js using folowing command:
brew install node
Then start the REPL:
$ node
Welcome to Node.js v20.6.1.
Type ".help" for more information.
>
To exit, press Ctrl + D or Ctrl + C .
Basic Syntax
Variable
const variables can't be reassigned, which makes code easier to read.
let allows for reassignment. There is also var, don't use it.
let name = "Alice"
const age = 7
Array
const fruits = ["apple", "banana", "cherry"]
fruits
.filter(x => x.length < 6)
.map(x => x.toUpperCase()) // ["APPLE"]
fruits.slice(0, 2) // ["apple", "banana"]
Object
const obj = {name: "Alice", age: 7}
Object.fromEntries([["name", "Alice"], ["age", 7]])
Object.entries(obj)
Function
function greet(name) {
return `Hello, ${name}!`
}
Calling a function
greet("JS")
greet.call(null, "JS")
greet.apply(null, ["JS"])
The first argument to call and apply is the this context:
function greet() {
return `Hello, ${this.name}!`
}
greet.call({name: "JS"})
this can also be specified this way:
function greet() {
return `Hello, ${this.name}!`
}
const alice = {name: "Alice", greet}
alice.greet()
Arrow Function
The most significant difference between arrow functions and
traditional function is their lexical this binding. Unlike
traditional functions, arrow functions do not create their own this
context. Instead, they inherit the this value from the enclosing
scope. This is particularly useful in scenarios like callbacks.
const greet = (name) => {
return `Hello, ${name}!`
}
Arrow functions with a single expression implicitly returns the value of that expression:
const greet = name => `Hello, ${name}!`
Class
class Person {
constructor(name, age) {
this.name = name
this.age = age
}
greet() {
console.log(`Hello, my name is ${this.name}`)
}
}
const person = new Person("Alice", 7)
person.greet()
Destructuring
const person = { name: "Alice", age: 30 }
const { name, age } = person
const [first, ...rest] = [1, 2, 3]
Spread
const arr1 = [1, 2, 3]
const arr = [...arr1, 4, 5]
const dict1 = { key: "val" }
const dict = { ...dict1, key2: "val2" }
Asynchronous Programming
Asynchronous programming in JavaScript is typically done using callbacks, event pub/sub, coroutines, streams, and Promises with async/await.
In modern JavaScript, Promises and async/await are preferred for their improved readability and maintainability.
Creating a Promise:
function delay(milliseconds) {
return new Promise((resolve, reject) => setTimeout(resolve, milliseconds))
}
Comsume a promise:
delay(1000).then(result => {
console.log('one second passed')
}).catch(error => {
console.error(error)
})
Or use async/await:
async function myFunction() {
await delay(1000)
await op1()
await delay(1000)
await op2()
}
This is way better than Callbacks:
function myFunction(callback) {
setTimeout(() => {
op1().then(() => {
setTimeout(() => {
op2().then(() => callback()).catch(callback)
}, 1000)
}).catch(callback)
}, 1000)
}