JavaScript Foundations Β· Beginner Lecture

Functions,
Objects &
Arrays

The three building blocks you'll use in every JavaScript program you ever write.

Functions & Closures
Objects & this
Arrays
7 Live Sandboxes
10 Quiz Questions
⚑

Functions

A function is a reusable block of code. Understanding functions deeply β€” declarations vs expressions, closures, and how this works with arrows β€” unlocks everything else in JavaScript.

Declaration vs Expression

Two ways to define a function

They look similar but behave differently in one critical way: hoisting. The JavaScript engine processes declarations before any code runs β€” expressions are not.

declaration-vs-expression.js
// ── FUNCTION DECLARATION ─────────────────────────────
// Hoisted β€” can be called BEFORE it appears in the file

console.log(greet("Alice"));  // βœ… Works! "Hello, Alice"

function greet(name) {
  return "Hello, " + name;
}


// ── FUNCTION EXPRESSION ──────────────────────────────
// The variable "sayBye" exists, but holds undefined until this line

// console.log(sayBye("Bob")); ← ❌ TypeError: sayBye is not a function

const sayBye = function(name) {
  return "Goodbye, " + name;
};

console.log(sayBye("Bob"));  // βœ… "Goodbye, Bob"


// ── NAMED FUNCTION EXPRESSION ────────────────────────
// Useful for self-reference and better stack traces

const factorial = function fact(n) {
  return n <= 1 ? 1 : n * fact(n - 1);  // can call itself by name "fact"
};
console.log(factorial(5));  // 120
FeatureDeclarationExpression
Syntaxfunction name() {}const name = function() {}
Hoisted?Yes β€” callable anywhere in scopeNo β€” only usable after assignment
Has a name?Always namedCan be anonymous or named
Best forTop-level utility functionsCallbacks, conditionally assigned functions
Arrow vs Regular Function

More than just shorter syntax

Arrow functions are a compact shorthand β€” but there is a crucial semantic difference: they do not have their own this. They inherit this from the surrounding (lexical) scope, which makes them ideal for callbacks but wrong for object methods.

arrow-syntax.js
// Single param β€” no parens needed
const double  = n => n * 2;

// Multiple params β€” parens required
const add     = (a, b) => a + b;

// No params β€” empty parens
const greet   = () => "Hello!";

// Multi-line β€” needs { } and explicit return
const clamp   = (n, min, max) => {
  if (n < min) return min;
  if (n > max) return max;
  return n;
};

// Returning an object literal β€” wrap in ( )
const makePoint = (x, y) => ({ x, y });
this-in-callbacks.js
const timer = {
  count: 0,

  // ❌ Regular function loses "this" inside setTimeout
  startWrong() {
    setTimeout(function() {
      this.count++;  // ❌ "this" is undefined here
    }, 100);
  },

  // βœ… Arrow function inherits "this" from startCorrect
  startCorrect() {
    setTimeout(() => {
      this.count++;  // βœ… "this" = timer object
      console.log(this.count);  // 1
    }, 100);
  }
};
timer.startCorrect();
FeatureRegular functionArrow function
Own thisYes β€” depends on call siteNo β€” inherits from outer scope
Can be constructor?Yes β€” new Fn() worksNo β€” throws TypeError
arguments objectYesNo β€” use rest params ...args
Best forObject methods, constructorsCallbacks, array methods, closures
Closures

Functions that remember their birthplace

A closure is created when an inner function retains access to the variables of its outer (enclosing) function β€” even after the outer function has finished executing.

Think of it like a backpack: when the inner function is born, it packs all the variables it can see and carries them forever.

closure-counter.js
function makeCounter() {
  let count = 0;  // ← lives in the closure, private!

  return function() {
    count++;
    return count;
  };
}

const counter = makeCounter();  // makeCounter() is DONE β€” but count lives on

console.log(counter());  // 1
console.log(counter());  // 2
console.log(counter());  // 3

// Each makeCounter() call creates a SEPARATE closure
const counter2 = makeCounter();
console.log(counter2()); // 1  ← its own independent count
console.log(counter());  // 4  ← counter continues unaffected
closure-factory.js
// A factory that produces specialized functions
function makeMultiplier(factor) {
  return n => n * factor;  // "factor" is closed over
}

const double = makeMultiplier(2);
const triple = makeMultiplier(3);

console.log(double(5));   // 10
console.log(triple(5));   // 15
console.log(double(100)); // 200

// Greeting factory
const makeGreeter = greeting => name => `${greeting}, ${name}!`;

const hello = makeGreeter("Hello");
const hej   = makeGreeter("Hej");

console.log(hello("Anna"));   // "Hello, Anna!"
console.log(hej("Tomek"));   // "Hej, Tomek!"
πŸ’‘

Why closures matter: They are the foundation of module patterns, React hooks, event handlers, memoization, and virtually every advanced JavaScript pattern. Master closures β€” master JavaScript.

βš™οΈ
Sandbox 1 β€” Declaration & Expression
Ctrl+Enter to run Β· Tab to indent
Ctrl+Enter to run
// output
Waiting for you to run the code…
πŸ”’
Sandbox 2 β€” Closure Task: Score Tracker
Private state using closures
Ctrl+Enter to run
// output
Waiting for you to run the code…
🏭
Sandbox 3 β€” Closure Task: Function Factory
Use closures to generate specialized functions
Ctrl+Enter to run
// output
Waiting for you to run the code…

πŸ“¦

Objects

Objects group related data and behaviour. Beyond basics, you need to understand this, safe copying, and how to iterate over keys and values.

Basics

Creating and accessing objects

object-basics.js
const person = {
  name: "Alice",
  age:  25,
  city: "Warsaw"
};

person.job = "developer";  // add
person.age = 26;             // update
delete person.city;         // remove

// Bracket notation β€” useful when key is in a variable
const key = "name";
console.log(person[key]);   // "Alice"
The this keyword

this β€” referring to the current object

Inside a method, this refers to the object the method belongs to. Its value is determined by how the function is called β€” not where it is defined.

this-keyword.js
const dog = {
  name:  "Burek",
  breed: "Husky",
  age:   3,

  describe() {
    return `${this.name} is a ${this.breed}`;
  },

  birthday() {
    this.age++;
    return `Happy birthday ${this.name}! Now ${this.age}.`;
  }
};

console.log(dog.describe());   // "Burek is a Husky"
console.log(dog.birthday());   // "Happy birthday Burek! Now 4."

// ⚠ The "this" trap β€” loses context when detached
const fn = dog.describe;        // detached β€” no object prefix
// fn() β†’ TypeError in strict mode: this is undefined

// Fix 1: .bind() creates a new permanently bound function
const bound = dog.describe.bind(dog);
console.log(bound());           // βœ… "Burek is a Husky"

// Fix 2: .call() / .apply() β€” invoke with explicit this
console.log(dog.describe.call(dog)); // βœ… same result
⚠️

Arrow functions and this: Never use an arrow function as an object method if you need this. Arrow functions don't have their own this β€” they inherit it from the outer (module) scope, not from the object.

Iterating objects

Object.keys(), Object.values(), Object.entries()

Objects aren't iterable by default. These three static methods turn an object's contents into arrays so you can use map, filter, and forEach on them.

object-iteration.js
const scores = { Anna: 88, Tomek: 72, Kasia: 95, Marek: 61 };

// Keys only
console.log(Object.keys(scores));
// ["Anna", "Tomek", "Kasia", "Marek"]

// Values only
console.log(Object.values(scores));
// [88, 72, 95, 61]

// Key-value pairs β€” great for iteration
Object.entries(scores).forEach(([name, score]) => {
  console.log(`${name}: ${score}`);
});

// Average score using Object.values()
const vals = Object.values(scores);
const avg  = vals.reduce((s, v) => s + v, 0) / vals.length;
console.log("Average:", avg.toFixed(1));  // 79.0

// Rebuild object with transformed values (Object.fromEntries)
const boosted = Object.fromEntries(
  Object.entries(scores).map(([k, v]) => [k, v + 5])
);
console.log(boosted); // { Anna: 93, Tomek: 77, Kasia: 100, Marek: 66 }
Copying objects

Shallow copy vs Deep copy β€” a critical difference

Objects are stored by reference. Assigning const b = a does not copy β€” both names point to the same data. Modifying one modifies the other.

object-copy.js
// ❌ NOT a copy β€” same reference
const original = { name: "Alice", age: 25 };
const oops = original;
oops.age = 99;
console.log(original.age);   // 99 β€” original was mutated!

// βœ… SHALLOW COPY β€” spread operator
const copy1 = { ...original };
copy1.age = 30;
console.log(original.age);   // 99 β€” original safe βœ…

// βœ… SHALLOW COPY β€” Object.assign
const copy2 = Object.assign({}, original);

// ⚠ Shallow only goes ONE level deep!
const user = { name: "Bob", address: { city: "Warsaw" } };
const shallow = { ...user };
shallow.address.city = "KrakΓ³w";
console.log(user.address.city); // "KrakΓ³w" ← still shared!

// βœ… DEEP COPY β€” JSON round-trip (for plain data)
const deep = JSON.parse(JSON.stringify(user));
deep.address.city = "GdaΕ„sk";
console.log(user.address.city);  // "KrakΓ³w" β€” safe βœ…

// βœ… DEEP COPY β€” structuredClone (modern, handles more types)
const deep2 = structuredClone(user);
MethodNested objects?Use when
const b = aNot a copy at allNever for copying
{...a} spreadShallow β€” nested sharedFlat objects only
Object.assign({}, a)Shallow β€” nested sharedFlat objects, older code
JSON.parse(JSON.stringify(a))Deep β€” fully independentNested plain data, no functions/dates
structuredClone(a)Deep β€” handles more typesModern environments, best practice
🏦
Sandbox 4 β€” this: Bank Account
Use this inside object methods
Ctrl+Enter to run
// output
Waiting for you to run the code…
πŸ—‚οΈ
Sandbox 5 β€” Keys / Values / Entries & Copy
Iterate objects and copy them safely
Ctrl+Enter to run
// output
Waiting for you to run the code…

πŸ“‹

Arrays

An ordered list. Each item sits at a numbered index starting from zero. Arrays come with powerful built-in methods β€” map, filter, reduce and more β€” that accept functions as arguments.

Basics & Methods

Accessing and transforming arrays

push(item)
Add to end
pop()
Remove last item
unshift(item)
Add to beginning
shift()
Remove first item
map(fn)
Transform every item β†’ new array
filter(fn)
Keep items passing a test
reduce(fn)
Collapse to single value
find(fn)
First matching item
includes(v)
Check if value exists
forEach(fn)
Loop β€” no return value
arrays.js
const nums = [1, 2, 3, 4, 5];

const doubled = nums.map(n => n * 2);           // [2,4,6,8,10]
const evens   = nums.filter(n => n % 2 === 0);  // [2,4]
const total   = nums.reduce((s, n) => s + n, 0); // 15
βš™οΈ
Sandbox 6 β€” Arrays
map Β· filter Β· reduce Β· find
Ctrl+Enter to run
// output
Waiting for you to run the code…

πŸ”—

All Together

Real code combines closures, this, objects, and arrays in every file. Here is a task manager that uses all three at once.

πŸ”₯
Sandbox 7 β€” Challenge: Task Manager
Closures + objects + arrays combined
Ctrl+Enter to run
// output
Waiting for you to run the code…

🎯

Quiz

10 questions covering functions, closures, this, object copying, iteration, and arrays.

Question 01 / 10
What is the key difference between a function declaration and a function expression?
βœ“ Correct! Function declarations are hoisted to the top of their scope, so they can be called before their definition appears in the file. Expressions are not hoisted.
βœ— The key difference is hoisting. Declarations are moved to the top of scope automatically; expressions must appear before any call to them.
Question 02 / 10
What will this closure-based code log?
function makeAdder(x) { return n => n + x; } const add5 = makeAdder(5); console.log(add5(3));
βœ“ Correct! add5 closes over x = 5. When called with 3, it returns 3 + 5 = 8. The closure keeps x alive.
βœ— This is a closure. makeAdder(5) returns a function that remembers x = 5. Calling add5(3) gives 3 + 5 = 8.
Question 03 / 10
Two counters made from the same factory β€” what does c2() log?
const c1 = makeCounter(); const c2 = makeCounter(); c1(); c1(); // c1 is now at 2 console.log(c2());
βœ“ Correct! Each call to makeCounter() creates a brand new, independent closure with its own count. They never share state.
βœ— Each makeCounter() call creates a separate closure with its own count starting from 0. c2() gives 1.
Question 04 / 10
What does this refer to inside a regular object method when called normally?
βœ“ Correct! When you call dog.bark(), inside bark, this is the dog object β€” the thing before the dot.
βœ— Inside a method called on an object (e.g. obj.method()), this is that object β€” the thing before the dot.
Question 05 / 10
Why is using an arrow function as an object method problematic?
βœ“ Correct! Arrow functions inherit this from the enclosing scope (usually the module level), not from the object β€” so this.property would be undefined.
βœ— Arrow functions don't have their own this. As an object method, this would refer to the outer scope (module/window), not the object.
Question 06 / 10
What will original.age be after this code?
const original = { name: "Alice", age: 25 }; const copy = original; copy.age = 99;
βœ“ Correct! const copy = original doesn't copy the object β€” both variables point to the same memory location. Mutating one mutates both.
βœ— = doesn't copy objects β€” it copies the reference. Both copy and original point to the same object, so age becomes 99 everywhere.
Question 07 / 10
Which creates a fully independent deep copy of an object with nested data?
βœ“ Correct! Spread and Object.assign are shallow β€” nested objects are still shared. The JSON round-trip produces a completely independent deep copy.
βœ— Only the JSON round-trip (or structuredClone) produces a true deep copy. Spread and Object.assign are shallow β€” nested objects remain shared.
Question 08 / 10
What does Object.entries(obj) return?
βœ“ Correct! Object.entries() returns [["key1", val1], ["key2", val2], ...], which you can destructure as ([key, value]) in callbacks.
βœ— Object.entries() returns [key, value] pairs. Use Object.keys() for just keys, Object.values() for just values.
Question 09 / 10
Which array method should you use to transform every element into a new value and get a new array back?
βœ“ Correct! map() returns a new array of the same length where each element has been transformed by your callback. The original array is never changed.
βœ— Use map(). forEach loops without returning a value, filter keeps/removes items, and find returns the first match.
Question 10 / 10
Which best describes a closure?
βœ“ That's the definition! The inner function carries a reference to its outer scope β€” keeping those variables alive and accessible long after the outer function finished.
βœ— A closure is an inner function that keeps a reference to variables from its outer scope, even after that outer function has finished executing.