W01 Learning Activity: JavaScript
Overview
In this activity, you will learn about key JavaScript concepts and syntax to ensure you have a solid foundation for the upcoming server-side development topics.
Make sure to go through this material in detail, because some of it may be new for you.
Preparation Material
The Evolution of JavaScript
Where It All Began
In 1995, Netscape Communications, a leading company in the early days of the internet, set out to make websites more interactive. To achieve this, they assigned Brendan Eich, a skilled programmer, the task of developing a new scripting language for their browser. Working under tight constraints, Eich created the foundation of what we now know as JavaScript in just 10 days. Originally named Mocha, the language was later renamed LiveScript. However, with Java being the most popular programming language at the time, Netscape's marketing team decided to rebrand it as JavaScript to capitalize on Java's widespread recognition. Despite the name, JavaScript and Java are distinct languages with different purposes and functionalities.
What is ECMAScript Modules (ESM)?
By the late 1990s, JavaScript was gaining popularity, but there was a problem: each browser implemented JavaScript slightly differently, which caused chaos for developers. To solve this, JavaScript was standardized under the name ECMAScript (ES), named after the organization ECMA International, a European body that creates technical standards. Every browser (e.g., Chrome, Firefox) implements these standards to ensure that JavaScript works consistently across the web. We refer to these standards as ECMAScript versions, such as ES6 (2015), ES7 (2016), and so on. Today, we generalize these standards under the term ECMAScript Modules (ESM).
Key Technical Changes in JavaScript's Evolution
Variable Declarations (ES6 - 2015)
Originally, JavaScript only had var to declare variables. This worked, but it had serious problems that caused bugs and confusion in larger programs.
The first problem with var is hoisting. You can reference a variable before you declare it, and instead of getting an error, JavaScript returns undefined:
console.log(myVariable); // undefined (should be an error!)
var myVariable = "Hello World";
The second and bigger problem is scope. Scope determines where in your code a variable can be accessed. Variables declared with var are function-scoped, meaning they're accessible anywhere within the entire function, even outside blocks like if statements:
function example() {
if (true) {
var leaksOut = "I escape the block";
}
console.log(leaksOut); // "I escape the block" - accessible here!
}
This scope behavior often caused unexpected bugs because variables would be accessible in places where you didn't intend them to be. ES6 (2015) introduced let and const to fix these problems by providing block scope instead of function scope.
Modern JavaScript uses let and const:
let y = 20; // Block-scoped, can be changed
const z = 30; // Block-scoped, cannot be changed
With block scope, variables stay inside their blocks (the curly braces where they're declared):
function example() {
if (true) {
let staysInside = "I'm contained";
const alsoStaysInside = "Me too";
}
console.log(staysInside); // ReferenceError - not accessible here
}
Use const when the value won't change, and let when it will:
const pi = 3.14159; // This means `pi` never changes
let counter = 0; // This allows `counter` to be updated
counter = 1; // `counter` updated to 1
pi = 3.14; // TypeError: Assignment to constant variable `pi`
Arrow Functions and this Behavior (ES6 - 2015)
Originally, JavaScript only had traditional function declarations and expressions. These worked fine for basic tasks, but they had a confusing problem with the this keyword that caused many bugs.
Here's how functions used to be written:
// Traditional function declaration
function greet(name) {
return "Hello, " + name;
}
// Traditional function expression
const greet = function(name) {
return "Hello, " + name;
};
The problem with traditional functions is that this changes depending on how the function is called, not where it's defined. This often caused unexpected behavior:
const person = {
name: "Alice",
greet: function() {
console.log("Hello, I'm " + this.name);
},
delayedGreet: function() {
setTimeout(function() {
console.log("Hello, I'm " + this.name); // this.name is undefined!
}, 1000);
}
};
person.greet(); // "Hello, I'm Alice" ✓
person.delayedGreet(); // "Hello, I'm undefined" ✗
The callback function inside setTimeout loses the original this value, causing bugs. ES6 introduced arrow functions to solve this problem by inheriting this from their surrounding context:
const person = {
name: "Alice",
greet: function() {
console.log("Hello, I'm " + this.name);
},
delayedGreet: function() {
setTimeout(() => {
console.log("Hello, I'm " + this.name); // this.name works correctly!
}, 1000);
}
};
person.greet(); // "Hello, I'm Alice" ✓
person.delayedGreet(); // "Hello, I'm Alice" ✓
Arrow functions also provide cleaner syntax for simple functions:
// Traditional function
const greet = function(name) {
return "Hello, " + name;
};
// Arrow function - shorter and cleaner
const greet = (name) => "Hello, " + name;
String Template Literals (ES6 - 2015)
Originally, JavaScript built strings by combining pieces together using string concatenation. Concatenation means joining multiple strings into one by using the plus (+) operator.
Here's how strings used to be built:
const name = "JavaScript";
const version = 6;
const message = "Hello, " + name + "! ES" + version + " is great.";
This concatenation approach became messy and error-prone with longer strings. It was easy to forget spaces, mix up quotes, or make syntax errors:
const user = "Alice";
const score = 95;
const time = "2:30";
// This gets hard to read and maintain:
const result = "Congratulations " + user + "! You scored " + score + "% in " + time + " minutes.";
ES6 introduced template literals to make string building cleaner and easier. Instead of regular quotes, you use backticks (`) and insert variables directly with ${}:
const user = "Alice";
const score = 95;
const time = "2:30";
// Much cleaner and easier to read:
const result = `Congratulations ${user}! You scored ${score}% in ${time} minutes.`;
Template literals also support multi-line strings without needing special characters:
// Old way required \n for new lines
const oldMessage = "Line 1\nLine 2\nLine 3";
// Template literals preserve line breaks naturally
const newMessage = `Line 1
Line 2
Line 3`;
Handling Asynchronous Code
JavaScript evolved from using callbacks to Promises and eventually Async/Await for handling asynchronous tasks like fetching data from a server.
Callbacks (Pre-ES6)
Using callbacks often led to deeply nested code, commonly referred to as callback hell, making it difficult to read and maintain.
fetchData((data) => {
processData(data, (result) => {
displayResult(result);
});
});
Promises (ES6 - 2015)
Promises were introduced to address callback hell by providing a cleaner, more readable way to handle asynchronous operations.
fetchData()
.then(processData)
.then(displayResult)
.catch(handleError);
Async/Await (ES8 - 2017)
Async/Await further simplified asynchronous code, making it look and behave more like synchronous code, thus improving readability and maintainability.
async function fetchAndProcess() {
try {
const data = await fetchData();
const result = await processData(data);
displayResult(result);
} catch (error) {
handleError(error);
}
}
Modules for Cleaner Code Organization (ES6 - 2015)
Modules allow developers to organize code into separate files, making it easier to manage, reuse, and scale projects. Instead of writing all functionality in one large file, developers can split code into smaller, self-contained pieces, each handling a specific task.
With modules, a file can export functions, objects, or variables using the export keyword. Other parts of the application can then import only what they need using the import keyword. This modular approach improves readability, maintainability, and reusability, making it easier for teams to collaborate and for projects to grow without becoming messy or difficult to manage.
// math.js - Module exporting multiple functions
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => b !== 0 ? a / b : 'Cannot divide by zero';
// app.js - Importing specific functions from math.js
import { add, subtract, multiply, divide } from './math.js';
console.log("Addition:", add(2, 3)); // Output: 5
console.log("Subtraction:", subtract(5, 2)); // Output: 3
console.log("Multiplication:", multiply(3, 4)); // Output: 12
console.log("Division:", divide(10, 2)); // Output: 5
console.log("Division by zero:", divide(5, 0)); // Output: Cannot divide by zero
JavaScript Today and Tomorrow
Today, JavaScript is everywhere, powering a vast array of applications from frontend development to backend systems, mobile applications, and even AI tools. The language has evolved significantly since its inception, and it now follows a yearly update cycle, introducing incremental improvements rather than dramatic changes. This approach ensures that developers can adopt new features gradually without facing major disruptions.
In recent years, the naming convention has shifted away from referencing specific ECMAScript versions like ES6. Instead, updates are simply referred to by the year they are released, such as ECMAScript 2023 (ES14). This change reflects the continuous and steady evolution of the language.
Looking ahead, there are ongoing discussions about splitting JavaScript into two parts: JS0 and JSSugar. JS0 would focus on the core language features, while JSSugar would include syntactic sugar and higher-level abstractions. This proposal aims to reduce the burden on JavaScript engines and improve performance. However, it has sparked debate among developers regarding potential complexity and increased tooling dependency.
(Not required) Selected Works Cited
GeeksforGeeks: History of JavaScript
Provides an overview of JavaScript's origin and major milestones, detailing how it was created by Brendan Eich at Netscape and its evolution over the years.
W3Schools: JavaScript History
Offers a concise timeline of JavaScript's development, highlighting key events and versions that have shaped the language.
GeeksforGeeks: Callback and Callback Hell
Explains asynchronous programming in JavaScript, the challenges of callback hell, and the evolution to modern practices like Promises and Async/Await.
Caolan's Notes: JS0 and JSSugar
Discusses the proposed split of JavaScript into two layers: JS0 for core language features and JSSugar for syntactic sugar and higher-level abstractions, and the implications of this proposal.
Activity Instructions
Practice your knowledge of Javascript by answering the following questions.
- What are the differences between
var,let, andconstin JavaScript?Answer (click to expand)
varis function-scoped and can be redeclared and updated. It is hoisted, meaning it can be used before its declaration, but will be undefined until assigned.letis block-scoped, can be updated but not redeclared within the same scope, and is not hoisted in the same way asvar.constis also block-scoped, cannot be updated or redeclared, and must be initialized at the time of declaration. - What is the syntax for an arrow function that accepts two parameters, adds them together and returns the result?
Answer (click to expand)
The syntax for an arrow function that accepts two parameters and returns their sum is:
const add = (a, b) => a + b; - How do you export a function from a module using modern, ESM syntax?
Answer (click to expand)
To export a function from a module using ESM syntax, you can use the
exportkeyword before the function declaration. For example:const myFunction = () => { // function body }; export { myFunction }; - How do you import that function into another module using modern, ESM syntax?
Answer (click to expand)
To import a function from another module using ESM syntax, you can use the
importkeyword followed by the function name in curly braces and the path to the module. For example:import { myFunction } from './path/to/module.js'; - What are template literals, and how do they differ from regular strings in JavaScript?
Answer (click to expand)
Template literals are string literals that allow embedded expressions and multi-line strings. They are enclosed by backticks
`instead of single or double quotes. Template literals can include placeholders for variables or expressions using the${}syntax. This differs from regular strings, which require concatenation using the plus (+) operator and do not support multi-line strings without special characters. - Explain the difference between Promises and Async/Await in handling asynchronous code in JavaScript.
Answer (click to expand)
Promises are objects that represent the eventual completion (or failure) of an asynchronous operation and its resulting value. They use
.then()and.catch()methods to handle success and error cases. Async/Await is syntactic sugar built on top of Promises that allows writing asynchronous code in a more synchronous-looking manner. Theasynckeyword is used to declare a function as asynchronous, and theawaitkeyword is used to pause execution until a Promise is resolved or rejected, making the code easier to read and maintain.
Submission
After you have completed all the learning activities for this lesson, return to Canvas to submit a quiz.
Other Links:
- Return to: Week Overview | Course Home