Advanced JavaScript in Practice
Congratulations on reaching the final lesson! In this capstone lesson, we'll bring together everything you've learned by building a complete real-world application, exploring modern development workflows, and discussing your next steps as a JavaScript developer.
Complete Real-World Project: Task Management App
Let's build a modern task management application that demonstrates advanced JavaScript concepts:
// Project Structure
/*
task-manager/
├── index.html
├── css/
│ └── styles.css
├── js/
│ ├── app.js
│ ├── store.js
│ ├── api.js
│ ├── components/
│ │ ├── TaskList.js
│ │ ├── TaskItem.js
│ │ └── TaskForm.js
│ └── utils/
│ ├── debounce.js
│ └── validators.js
└── sw.js
*/
// store.js - State Management with Proxy
class Store {
constructor(initialState = {}) {
this.state = new Proxy(initialState, {
set: (target, property, value) => {
target[property] = value;
this.notify(property, value);
return true;
}
});
this.listeners = new Map();
}
subscribe(property, callback) {
if (!this.listeners.has(property)) {
this.listeners.set(property, new Set());
}
this.listeners.get(property).add(callback);
// Return unsubscribe function
return () => {
this.listeners.get(property).delete(callback);
};
}
notify(property, value) {
if (this.listeners.has(property)) {
this.listeners.get(property).forEach(callback => {
callback(value);
});
}
}
getState() {
return { ...this.state };
}
setState(updates) {
Object.assign(this.state, updates);
}
}
// Initialize store
const store = new Store({
tasks: [],
filter: 'all',
isLoading: false,
error: null
});
// api.js - API Layer with Error Handling
class TaskAPI {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
async getTasks() {
return this.request('/tasks');
}
async createTask(task) {
return this.request('/tasks', {
method: 'POST',
body: JSON.stringify(task)
});
}
async updateTask(id, updates) {
return this.request(`/tasks/${id}`, {
method: 'PATCH',
body: JSON.stringify(updates)
});
}
async deleteTask(id) {
return this.request(`/tasks/${id}`, {
method: 'DELETE'
});
}
}
const api = new TaskAPI('https://api.example.com');
// TaskItem.js - Component with Custom Events
class TaskItem {
constructor(task) {
this.task = task;
this.element = null;
this.render();
}
render() {
this.element = document.createElement('div');
this.element.className = `task-item ${this.task.completed ? 'completed' : ''}`;
this.element.innerHTML = `
<input type="checkbox" ${this.task.completed ? 'checked' : ''}>
<span class="task-title">${this.escapeHtml(this.task.title)}</span>
<span class="task-priority ${this.task.priority}">${this.task.priority}</span>
<button class="edit-btn">Edit</button>
<button class="delete-btn">Delete</button>
`;
this.attachEventListeners();
return this.element;
}
attachEventListeners() {
const checkbox = this.element.querySelector('input[type="checkbox"]');
const editBtn = this.element.querySelector('.edit-btn');
const deleteBtn = this.element.querySelector('.delete-btn');
checkbox.addEventListener('change', () => {
this.dispatchEvent('toggle', { id: this.task.id });
});
editBtn.addEventListener('click', () => {
this.dispatchEvent('edit', { task: this.task });
});
deleteBtn.addEventListener('click', () => {
this.dispatchEvent('delete', { id: this.task.id });
});
}
dispatchEvent(type, detail) {
this.element.dispatchEvent(new CustomEvent(type, {
bubbles: true,
detail
}));
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
update(task) {
this.task = task;
const newElement = this.render();
this.element.replaceWith(newElement);
this.element = newElement;
}
}
// TaskList.js - Container Component with Virtual Scrolling
class TaskList {
constructor(container) {
this.container = container;
this.tasks = [];
this.components = new Map();
this.observer = null;
this.setupIntersectionObserver();
this.attachEventListeners();
}
setupIntersectionObserver() {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
},
{ threshold: 0.1 }
);
}
attachEventListeners() {
this.container.addEventListener('toggle', async (e) => {
const { id } = e.detail;
await this.handleToggle(id);
});
this.container.addEventListener('edit', (e) => {
const { task } = e.detail;
this.handleEdit(task);
});
this.container.addEventListener('delete', async (e) => {
const { id } = e.detail;
await this.handleDelete(id);
});
}
async handleToggle(id) {
try {
const task = this.tasks.find(t => t.id === id);
const updated = await api.updateTask(id, {
completed: !task.completed
});
this.updateTask(updated);
} catch (error) {
store.setState({ error: 'Failed to update task' });
}
}
handleEdit(task) {
// Emit event for parent to handle
document.dispatchEvent(new CustomEvent('edit-task', {
detail: { task }
}));
}
async handleDelete(id) {
if (!confirm('Are you sure you want to delete this task?')) {
return;
}
try {
await api.deleteTask(id);
this.removeTask(id);
} catch (error) {
store.setState({ error: 'Failed to delete task' });
}
}
render(tasks) {
this.tasks = tasks;
this.container.innerHTML = '';
if (tasks.length === 0) {
this.container.innerHTML = '<p class="empty-state">No tasks found</p>';
return;
}
const fragment = document.createDocumentFragment();
tasks.forEach(task => {
const taskItem = new TaskItem(task);
this.components.set(task.id, taskItem);
fragment.appendChild(taskItem.element);
this.observer.observe(taskItem.element);
});
this.container.appendChild(fragment);
}
updateTask(task) {
const index = this.tasks.findIndex(t => t.id === task.id);
if (index !== -1) {
this.tasks[index] = task;
const component = this.components.get(task.id);
if (component) {
component.update(task);
}
}
}
removeTask(id) {
this.tasks = this.tasks.filter(t => t.id !== id);
const component = this.components.get(id);
if (component) {
component.element.remove();
this.components.delete(id);
}
}
destroy() {
this.observer.disconnect();
this.components.clear();
}
}
// TaskForm.js - Form with Validation
class TaskForm {
constructor(formElement) {
this.form = formElement;
this.editingTask = null;
this.validators = {
title: (value) => {
if (!value || value.trim().length === 0) {
return 'Title is required';
}
if (value.length > 100) {
return 'Title must be less than 100 characters';
}
return null;
},
priority: (value) => {
const validPriorities = ['low', 'medium', 'high'];
if (!validPriorities.includes(value)) {
return 'Invalid priority';
}
return null;
}
};
this.attachEventListeners();
}
attachEventListeners() {
this.form.addEventListener('submit', async (e) => {
e.preventDefault();
await this.handleSubmit();
});
// Real-time validation
const titleInput = this.form.querySelector('[name="title"]');
titleInput.addEventListener('blur', () => {
this.validateField('title', titleInput.value);
});
}
validateField(field, value) {
const error = this.validators[field](value);
const errorElement = this.form.querySelector(`[data-error="${field}"]`);
if (error) {
errorElement.textContent = error;
return false;
} else {
errorElement.textContent = '';
return true;
}
}
validateForm(data) {
let isValid = true;
Object.keys(this.validators).forEach(field => {
if (!this.validateField(field, data[field])) {
isValid = false;
}
});
return isValid;
}
async handleSubmit() {
const formData = new FormData(this.form);
const data = Object.fromEntries(formData);
if (!this.validateForm(data)) {
return;
}
try {
store.setState({ isLoading: true });
if (this.editingTask) {
await api.updateTask(this.editingTask.id, data);
} else {
await api.createTask(data);
}
await loadTasks();
this.reset();
} catch (error) {
store.setState({ error: 'Failed to save task' });
} finally {
store.setState({ isLoading: false });
}
}
edit(task) {
this.editingTask = task;
this.form.querySelector('[name="title"]').value = task.title;
this.form.querySelector('[name="priority"]').value = task.priority;
this.form.querySelector('[name="description"]').value = task.description || '';
}
reset() {
this.form.reset();
this.editingTask = null;
}
}
// app.js - Application Initialization
class TaskManagerApp {
constructor() {
this.taskList = null;
this.taskForm = null;
this.searchDebounced = null;
this.init();
}
async init() {
// Initialize components
this.taskList = new TaskList(document.querySelector('#task-list'));
this.taskForm = new TaskForm(document.querySelector('#task-form'));
// Setup search with debouncing
this.searchDebounced = this.debounce(this.handleSearch.bind(this), 300);
document.querySelector('#search').addEventListener('input', (e) => {
this.searchDebounced(e.target.value);
});
// Setup filter buttons
document.querySelectorAll('.filter-btn').forEach(btn => {
btn.addEventListener('click', () => {
store.setState({ filter: btn.dataset.filter });
});
});
// Subscribe to state changes
store.subscribe('tasks', (tasks) => {
this.renderTasks(tasks);
});
store.subscribe('filter', () => {
this.renderTasks(store.state.tasks);
});
store.subscribe('isLoading', (isLoading) => {
this.toggleLoading(isLoading);
});
store.subscribe('error', (error) => {
if (error) {
this.showError(error);
}
});
// Listen for edit events
document.addEventListener('edit-task', (e) => {
this.taskForm.edit(e.detail.task);
});
// Load initial data
await this.loadTasks();
// Register service worker
this.registerServiceWorker();
}
async loadTasks() {
try {
store.setState({ isLoading: true });
const tasks = await api.getTasks();
store.setState({ tasks, error: null });
} catch (error) {
store.setState({ error: 'Failed to load tasks' });
} finally {
store.setState({ isLoading: false });
}
}
renderTasks(tasks) {
const { filter } = store.state;
const filteredTasks = tasks.filter(task => {
if (filter === 'active') return !task.completed;
if (filter === 'completed') return task.completed;
return true;
});
this.taskList.render(filteredTasks);
}
handleSearch(query) {
const { tasks } = store.state;
if (!query) {
this.renderTasks(tasks);
return;
}
const filtered = tasks.filter(task =>
task.title.toLowerCase().includes(query.toLowerCase())
);
this.taskList.render(filtered);
}
debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
toggleLoading(isLoading) {
document.body.classList.toggle('loading', isLoading);
}
showError(message) {
const toast = document.createElement('div');
toast.className = 'toast error';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.classList.add('show');
}, 10);
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
async registerServiceWorker() {
if ('serviceWorker' in navigator) {
try {
await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registered');
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
}
}
// Initialize app
const app = new TaskManagerApp();
Key Patterns Used: This application demonstrates Proxy-based state management, component architecture, custom events, API abstraction, form validation, debouncing, IntersectionObserver, and Service Workers.
Modern JavaScript Workflow
Professional JavaScript development involves more than just writing code:
1. Development Environment:
// package.json
{
"name": "task-manager",
"version": "1.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "jest",
"lint": "eslint src",
"format": "prettier --write src"
},
"devDependencies": {
"vite": "^5.0.0",
"jest": "^29.0.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0"
}
}
2. Code Quality:
// .eslintrc.js
module.exports = {
env: {
browser: true,
es2021: true
},
extends: ['eslint:recommended'],
rules: {
'no-console': 'warn',
'no-unused-vars': 'error',
'prefer-const': 'error'
}
};
// .prettierrc
{
"semi": true,
"singleQuote": true,
"tabWidth": 4,
"trailingComma": "es5"
}
3. Testing:
// task.test.js
import { describe, it, expect } from 'jest';
import { TaskItem } from './TaskItem';
describe('TaskItem', () => {
it('should create task element', () => {
const task = {
id: 1,
title: 'Test task',
completed: false,
priority: 'medium'
};
const taskItem = new TaskItem(task);
expect(taskItem.element).toBeDefined();
expect(taskItem.element.querySelector('.task-title').textContent).toBe('Test task');
});
it('should mark task as completed', () => {
const task = { id: 1, title: 'Test', completed: true };
const taskItem = new TaskItem(task);
expect(taskItem.element.classList.contains('completed')).toBe(true);
});
});
4. Build Process:
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
outDir: 'dist',
minify: 'terser',
sourcemap: true
},
server: {
port: 3000
}
});
5. Version Control:
// .gitignore
node_modules/
dist/
.env
.DS_Store
// Commit messages (Conventional Commits)
feat: add task filtering
fix: resolve task deletion bug
docs: update README
refactor: improve state management
test: add TaskItem tests
Testing with Jest Basics
// Unit tests
test('debounce function delays execution', (done) => {
let count = 0;
const increment = debounce(() => count++, 100);
increment();
increment();
increment();
setTimeout(() => {
expect(count).toBe(1);
done();
}, 150);
});
// Async tests
test('API fetches tasks', async () => {
const api = new TaskAPI('https://api.example.com');
const tasks = await api.getTasks();
expect(Array.isArray(tasks)).toBe(true);
});
// Mocking
jest.mock('./api', () => ({
getTasks: jest.fn(() => Promise.resolve([
{ id: 1, title: 'Mock task' }
]))
}));
// Integration tests
describe('TaskList integration', () => {
let container;
beforeEach(() => {
container = document.createElement('div');
document.body.appendChild(container);
});
afterEach(() => {
container.remove();
});
it('should render tasks', () => {
const taskList = new TaskList(container);
const tasks = [
{ id: 1, title: 'Task 1', completed: false },
{ id: 2, title: 'Task 2', completed: true }
];
taskList.render(tasks);
expect(container.querySelectorAll('.task-item').length).toBe(2);
});
});
Linting with ESLint
// Install ESLint
npm install --save-dev eslint
// Initialize configuration
npx eslint --init
// Common rules
{
"rules": {
// Error prevention
"no-console": "warn",
"no-debugger": "error",
"no-unused-vars": "error",
// Best practices
"eqeqeq": ["error", "always"],
"no-eval": "error",
"no-implicit-globals": "error",
// ES6+
"prefer-const": "error",
"prefer-arrow-callback": "warn",
"no-var": "error",
"prefer-template": "warn",
// Style
"indent": ["error", 4],
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}
// Run ESLint
npm run lint
// Fix automatically
npm run lint -- --fix
Formatting with Prettier
// Install Prettier
npm install --save-dev prettier
// Configuration (.prettierrc)
{
"printWidth": 100,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
}
// Ignore files (.prettierignore)
node_modules
dist
build
coverage
// Format code
npm run format
// Integrate with ESLint
npm install --save-dev eslint-config-prettier eslint-plugin-prettier
Package Management with npm
// Initialize project
npm init -y
// Install dependencies
npm install lodash axios
// Install dev dependencies
npm install --save-dev jest eslint
// Update dependencies
npm update
// Audit security
npm audit
npm audit fix
// Scripts in package.json
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "webpack --mode production",
"test": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint .",
"format": "prettier --write ."
}
}
// Use npm scripts
npm run dev
npm test
npm run build
Build Tools and Deployment
Modern Build Tools:
1. Vite (Recommended for new projects)
- Instant server start
- Lightning-fast HMR
- Optimized builds
2. Webpack (Mature, widely used)
- Powerful configuration
- Large ecosystem
- Code splitting
3. Rollup (For libraries)
- Tree shaking
- ES modules
- Small bundles
Deployment Platforms:
// Vercel (vercel.json)
{
"builds": [
{
"src": "index.html",
"use": "@vercel/static"
}
]
}
// Netlify (netlify.toml)
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
// Deploy commands
npm run build # Build for production
vercel --prod # Deploy to Vercel
netlify deploy --prod # Deploy to Netlify
CI/CD Pipeline:
// .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Deploy
run: npm run deploy
Next Steps: TypeScript, React, Node.js
1. TypeScript - Add type safety
// Install
npm install --save-dev typescript
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"esModuleInterop": true
}
}
// Example TypeScript code
interface Task {
id: number;
title: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
}
class TaskManager {
private tasks: Task[] = [];
addTask(task: Task): void {
this.tasks.push(task);
}
getTask(id: number): Task | undefined {
return this.tasks.find(t => t.id === id);
}
}
2. React - Component-based UI library
// Install
npx create-react-app my-app
# or with Vite
npm create vite@latest my-app -- --template react
// Example React component
import { useState, useEffect } from 'react';
function TaskList() {
const [tasks, setTasks] = useState([]);
useEffect(() => {
fetchTasks().then(setTasks);
}, []);
return (
<div>
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
</div>
);
}
3. Node.js - Server-side JavaScript
// Install
# Download from nodejs.org
// Example Express server
const express = require('express');
const app = express();
app.use(express.json());
app.get('/api/tasks', (req, res) => {
res.json(tasks);
});
app.post('/api/tasks', (req, res) => {
const task = req.body;
tasks.push(task);
res.status(201).json(task);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
4. Additional Technologies to Explore:
- Vue.js or Angular (alternative frameworks)
- Next.js or Nuxt.js (meta-frameworks)
- GraphQL (alternative to REST APIs)
- MongoDB or PostgreSQL (databases)
- Docker (containerization)
- AWS or Azure (cloud platforms)
Career Skills and Continuing Education
Essential Skills for JavaScript Developers:
1. Core JavaScript Mastery
- ES6+ features
- Async programming
- Design patterns
- Performance optimization
2. Framework Knowledge
- React, Vue, or Angular
- State management (Redux, Zustand)
- Routing and navigation
3. Backend Development
- Node.js and Express
- RESTful APIs
- Database operations
- Authentication
4. Development Tools
- Git version control
- Package managers (npm, yarn)
- Build tools (Webpack, Vite)
- Testing frameworks
5. Soft Skills
- Problem-solving
- Code review
- Documentation
- Team collaboration
Learning Resources:
- MDN Web Docs (documentation)
- JavaScript.info (tutorials)
- Frontend Masters (courses)
- GitHub (open source projects)
- Dev.to and Medium (articles)
- Stack Overflow (community)
Building Your Portfolio:
1. Create 3-5 substantial projects
2. Contribute to open source
3. Write technical blog posts
4. Maintain an active GitHub profile
5. Participate in coding challenges
Job Search Tips:
- Build a strong portfolio
- Practice coding interviews
- Network in developer communities
- Contribute to open source
- Keep learning and staying current
Final Challenge:
Enhance the Task Manager application with these features:
- Add drag-and-drop task reordering
- Implement task categories/tags
- Add due dates with notifications
- Create a dark mode toggle
- Add data export to JSON/CSV
- Implement offline support with IndexedDB
- Add user authentication
- Create responsive mobile design
Apply all the concepts you've learned throughout this course!
Congratulations!
You've completed the Advanced JavaScript (ES6+) course! You've learned:
- Modern Syntax: ES6+ features, arrow functions, destructuring, spread/rest operators
- Functions: Closures, higher-order functions, this keyword, IIFE
- Async Programming: Promises, async/await, Fetch API, event loop
- Data Structures: Sets, Maps, Symbols, iterators, generators
- OOP: Classes, inheritance, prototypes, Proxy, Reflect
- Modules: ES6 modules, design patterns, error handling
- Advanced Topics: Debugging, regex, performance, modern APIs
- Real-World Skills: Testing, linting, build tools, deployment
What's Next? You're now ready to build professional JavaScript applications! Consider specializing in frontend frameworks (React, Vue, Angular), backend development with Node.js, or full-stack development. Keep coding, keep learning, and most importantly, keep building!
Final Thoughts
JavaScript is constantly evolving, and staying current is essential. Follow these principles:
- Practice regularly: Code every day, even if just for 30 minutes
- Build real projects: Theory is important, but practice is essential
- Learn from others: Read code, contribute to open source, attend meetups
- Stay curious: Explore new technologies and paradigms
- Share knowledge: Teaching others reinforces your own understanding
Thank you for completing this course! Happy coding!