Environment, Types & Your First Real Function
Environment, Types & Your First Real Function
Why JavaScript First?
JavaScript is the only language that runs natively in every browser — no install, no compile step. It also runs server-side via Node.js. React, Next.js, TypeScript — they all compile down to JavaScript. Mastering fundamentals removes the magic from every framework above them.
Before you write a single line: your environment must be correct.
---
Setting Up VS Code for JavaScript
Open VS Code. Install these extensions once — they will make every lesson easier:
- ESLint — catches errors before you run code
- Prettier — auto-formats on save
- JavaScript (ES2022+) snippets — built into VS Code already
Create your project folder (Terminal > New Terminal in VS Code):
mkdir js-crash-course
cd js-crash-course
code .Enable auto-format on save — open Settings (Ctrl+,), search "format on save", check the box.
---
Running JavaScript: Two Environments
In the browser — open DevTools (F12), Console tab. Type JS directly. Use this for DOM work.
In Node.js — run any .js file from terminal:
node main.jsYou'll use Node.js for everything in this crash course.
---
Variables: Three Keywords, One Rule
const name = 'Jordan' // cannot be reassigned — use this 90% of the time
let count = 0 // reassignable — use only when value changes
var old = 'avoid' // function-scoped, hoisted — legacy code onlyThe rule: default to `const`. Use `let` when the value must change. Never `var`.
Why does this matter? var is hoisted to the top of its function, which causes subtle bugs:
// var leaks out of if blocks
if (true) {
var x = 10
}
console.log(x) // 10 — var escapes the block
// const stays in its block
if (true) {
const y = 10
}
console.log(y) // ReferenceError — y doesn't exist here---
The 8 Data Types
typeof 'hello' // 'string'
typeof 42 // 'number' (covers integers and floats — JS has no int)
typeof 9007199254740991n // 'bigint' (for numbers larger than Number.MAX_SAFE_INTEGER)
typeof true // 'boolean'
typeof undefined // 'undefined' (default of unassigned variables)
typeof null // 'object' ← a 25-year-old JS bug, never fixed
typeof Symbol() // 'symbol' (unique identifiers, used in advanced JS)
typeof {} // 'object' (arrays are also objects)Primitives are immutable and passed by value. Objects are passed by reference.
let a = 'hello'
let b = a
b = 'world'
console.log(a) // 'hello' — primitives copied
const obj1 = { x: 1 }
const obj2 = obj1
obj2.x = 99
console.log(obj1.x) // 99 — objects share reference---
Functions: Three Forms
// 1. Function declaration — hoisted, callable before its definition
function greet(name) {
return `Hello, ${name}`
}
// 2. Arrow function — concise, no own this binding
const greet = (name) => `Hello, ${name}`
// 3. Function expression — not hoisted
const greet = function(name) { return `Hello, ${name}` }Use arrow functions for callbacks and short functions. Use declarations for top-level named functions.
---
Your First Multi-File Program
Real projects split code across files. Here's how it works:
utils.js — utility functions exported for other files to use:
// utils.js
export function formatCurrency(amount, currency = 'USD') {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency,
}).format(amount)
}
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
}main.js — the entry point that imports and uses utils.js:
// main.js
import { formatCurrency, capitalize } from './utils.js'
const products = [
{ name: 'laptop', price: 1299 },
{ name: 'keyboard', price: 89 },
]
products.forEach(p => {
console.log(`${capitalize(p.name)}: ${formatCurrency(p.price)}`)
})Run it:
node --input-type=module < main.js
# Or add "type": "module" to package.json and run: node main.jsThe export keyword makes a function available. The import keyword pulls it in. Files connect through explicit imports — nothing is global by default in modern JS.
---
Closures: How State Lives Inside Functions
This is the most important pattern in JavaScript — React's useState is built on it:
function makeCounter(start = 0) {
let count = start // this variable lives INSIDE makeCounter's scope
return {
inc: () => ++count, // this arrow function closes over count
dec: () => --count,
get: () => count,
}
}
const counter = makeCounter(10)
counter.inc() // 11
counter.inc() // 12
counter.dec() // 11
console.log(counter.get()) // 11count outlives makeCounter's execution because the returned functions hold a reference to it. That's a closure.
---
Truthiness & Safe Access
Falsy values: false, 0, '', null, undefined, NaN. Everything else is truthy.
const user = null
const name = user?.name ?? 'Guest' // optional chaining + nullish coalescing
// user?.name = undefined (safe, doesn't throw)
// ?? 'Guest' = use right side only when left is null/undefined