پروژه‌ها و تمرین‌ها

پروژه‌های مقدماتی، متوسط و پیشرفته، تمرین‌های Syntax و شرط و حلقه و آرایه و Object و Function و DOM و Event و API و Async و پروژه‌های بازی و کاربردی.

چرا پروژه مهم است؟

دانش بدون تمرین فراموش می‌شود — پروژه‌ها مفاهیم را تثبیت می‌کنند:

نقشه راه یادگیری

مقدماتی ──── متوسط ──── پیشرفته
  │           │            │
  ماشین‌      TODO        PWA
  حساب       اپ          با AI
  
ماشین‌      بازی         Chart
حساب       Snake        Library

پروژه‌های مقدماتی

سطح: مبانی تا آرایه‌ها و توابع

۱. ماشین‌حساب ساده

// چالش‌ها:
// - چهار عمل اصلی + درصد
// - دکمه C برای پاک‌سازی
// - اعمال اولویت عملگرها (ساده)

class Calculator {
            #display = "0";
            #operator = null;
            #firstOperand = null;

            inputDigit(d) {
                this.#display = this.#display === "0"
                    ? String(d)
                    : this.#display + d;
                return this.#display;
            }

            setOperator(op) {
                this.#firstOperand = parseFloat(this.#display);
                this.#operator = op;
                this.#display = "0";
            }

            calculate() {
                const second = parseFloat(this.#display);
                const ops = {
                    "+": (a, b) => a + b,
                    "-": (a, b) => a - b,
                    "*": (a, b) => a * b,
                    "/": (a, b) => b === 0 ? "خطا" : a / b
                };
                this.#display = String(
                    ops[this.#operator](this.#firstOperand, second)
                );
                return this.#display;
            }
        }

۲. تبدیل واحد

// تبدیل دما، ارز، طول
const converter = {
    cToF: c => c * 9/5 + 32,
    kmToMile: km => km * 0.621371,
    kgToLb: kg => kg * 2.20462
};

۳. بازی حدس عدد

function guessingGame() {
    const secret = Math.floor(Math.random() * 100) + 1;
    let attempts = 0;

    return function guess(n) {
        attempts++;
        if (n === secret) return `درست! در ${attempts} تلاش`;
        return n < secret ? "بالاتر!" : "پایین‌تر!";
    };
}

const game = guessingGame();
console.log(game(50)); // بالاتر!

۴. لیست کارها (نسخه ساده)

// با localStorage:
const todos = JSON.parse(localStorage.getItem("todos") || "[]");

function addTodo(text) {
    todos.push({ text, done: false, id: Date.now() });
    localStorage.setItem("todos", JSON.stringify(todos));
}

function toggleTodo(id) {
    const todo = todos.find(t => t.id === id);
    if (todo) todo.done = !todo.done;
    localStorage.setItem("todos", JSON.stringify(todos));
}

پروژه‌های متوسط

سطح: DOM، رویداد، Async، API

۱. اپ آب‌وهوا

// با API واقعی:
class WeatherApp {
            #apiKey;
            #cache = new Map();

            constructor(apiKey) {
                this.#apiKey = apiKey;
                this.#bindUI();
            }

            async getWeather(city) {
                // کش ۱۰ دقیقه‌ای:
                if (this.#cache.has(city)) {
                    const { data, time } = this.#cache.get(city);
                    if (Date.now() - time < 600000) return data;
                }

                const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${this.#apiKey}`;
                const res = await fetch(url);

                if (!res.ok) throw new Error("شهر پیدا نشد");

                const data = await res.json();
                this.#cache.set(city, { data, time: Date.now() });
                return this.#transform(data);
            }

            #transform(raw) {
                return {
                    temp: Math.round(raw.main.temp - 273.15),
                    desc: raw.weather[0].description,
                    icon: raw.weather[0].icon,
                    humidity: raw.main.humidity
                };
            }

            #bindUI() {
                document.querySelector("#search")
                    .addEventListener("submit", async (e) => {
                        e.preventDefault();
                        const city = e.target.city.value;
                        try {
                            const weather = await this.getWeather(city);
                            this.#render(weather);
                        } catch (err) {
                            this.#showError(err.message);
                        }
                    });
            }

            #render(w) { /* نمایش در DOM */ }
            #showError(msg) { /* پیام خطا */ }
        }

۲. بازی Snake

با Canvas — به مثال درس ۲۸ مراجعه کنید

۳. Quiz اپلیکیشن

class QuizApp {
            #questions;
            #current = 0;
            #score = 0;

            constructor(questions) {
                this.#questions = questions;
            }

            getCurrent() {
                return this.#questions[this.#current];
            }

            answer(choiceIndex) {
                const correct = choiceIndex === this.getCurrent().correct;
                if (correct) this.#score++;
                this.#current++;
                return { correct, finished: this.#isFinished() };
            }

            #isFinished() {
                return this.#current >= this.#questions.length;
            }

            getResults() {
                return {
                    score: this.#score,
                    total: this.#questions.length,
                    percent: Math.round(this.#score / this.#questions.length * 100)
                };
            }
        }

۴. Markdown Previewer

// textarea → preview زنده:
const input = document.querySelector("#editor");
const preview = document.querySelector("#preview");

// پارسر ساده:
function parseMarkdown(md) {
    return md
        .replace(/^## (.+)$/gm, "<h2>$1</h2>")
        .replace(/^# (.+)$/gm, "<h1>$1</h1>")
        .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
        .replace(/\*(.+?)\*/g, "<em>$1</em>")
        .replace(/`(.+?)`/g, "<code>$1</code>");
}

input.addEventListener("input", () => {
    preview.innerHTML = parseMarkdown(input.value);
});

پروژه‌های پیشرفته

۱. PWA کامل

// Service Worker + Cache + Offline:
// manifest.json + sw.js + push notification

۲. کتابخانه نمودار

// Canvas API از صفر:
class Chart {
            constructor(canvas, options) {
                this.ctx = canvas.getContext("2d");
                this.options = options;
                this.#setup();
            }

            #setup() {
                // devicePixelRatio برای وضوح:
                const dpr = window.devicePixelRatio || 1;
                const rect = this.ctx.canvas.getBoundingClientRect();
                this.ctx.canvas.width = rect.width * dpr;
                this.ctx.canvas.height = rect.height * dpr;
                this.ctx.scale(dpr, dpr);
            }

            draw(data) {
                this.#drawGrid();
                this.#drawAxes();
                this.#drawData(data);
            }

            #drawData(data) {
                // رسم خط با bezier:
                this.ctx.beginPath();
                data.forEach((point, i) => {
                    const { x, y } = this.#scale(point);
                    i === 0 ? this.ctx.moveTo(x, y) : this.ctx.lineTo(x, y);
                });
                this.ctx.stroke();
            }

            #scale(point) { /* تبدیل داده به مختصات */ }
            #drawGrid() { /* خطوط راهنما */ }
            #drawAxes() { /* محورها */ }
        }

۳. Chat Application

// WebSocket + مدیریت وضعیت:
class ChatStore {
            #messages = [];
            #subscribers = new Set();
            #socket;

            constructor(url) {
                this.#socket = new WebSocket(url);
                this.#socket.onmessage = (e) => {
                    const msg = JSON.parse(e.data);
                    this.#addMessage(msg);
                };
            }

            send(text) {
                this.#socket.send(JSON.stringify({ text, time: Date.now() }));
            }

            #addMessage(msg) {
                this.#messages.push(msg);
                this.#notify();
            }

            subscribe(fn) {
                this.#subscribers.add(fn);
                return () => this.#subscribers.delete(fn);
            }

            #notify() {
                this.#subscribers.forEach(fn => fn([...this.#messages]));
            }
        }

تمرین‌های Syntax

// ۱. خروجی چیست؟
console.log(typeof null);           // "object"!
console.log(0.1 + 0.2 === 0.3);     // false!
console.log([] + []);               // ""
console.log("5" + 3, "5" - 3);      // "53", 2

// ۲. این کد را اصلاح کن:
function scopeTest() {
    if (true) {
        var x = 1;
        let y = 2;
    }
    console.log(x);  // 1 — hoisted
    // console.log(y); // خطا!
}

// ۳. نتیجه؟
const [a, ...rest] = [1, 2, 3, 4];
console.log(a, rest);  // 1 [2,3,4]

تمرین‌های شرط و حلقه

// ۱. FizzBuzz کلاسیک:
function fizzBuzz(n) {
    const result = [];
    for (let i = 1; i <= n; i++) {
        let out = "";
        if (i % 3 === 0) out += "Fizz";
        if (i % 5 === 0) out += "Buzz";
        result.push(out || i);
    }
    return result;
}

// ۲. مثلث ستاره‌ای:
function pyramid(h) {
    for (let i = 1; i <= h; i++) {
        console.log(" ".repeat(h - i) + "*".repeat(2*i - 1));
    }
}

// ۳. جست‌وجو در دنباله:
function isArmstrong(n) {
    const digits = String(n).split("");
    const sum = digits.reduce((a, d) => a + d ** digits.length, 0);
    return sum === n;
}
console.log(isArmstrong(153)); // true

تمرین‌های آرایه

// ۱. بدون متد آماده chunk بنویس:
function chunk(arr, size) {
    const result = [];
    for (let i = 0; i < arr.length; i += size) {
        result.push(arr.slice(i, i + size));
    }
    return result;
}
console.log(chunk([1,2,3,4,5], 2)); // [[1,2],[3,4],[5]]

// ۲. شمارش تکرار:
function countBy(arr) {
    return arr.reduce((acc, item) => {
        acc[item] = (acc[item] || 0) + 1;
        return acc;
    }, {});
}
console.log(countBy(["a","b","a"])); // {a:2, b:1}

// ۳. چک کردن تو در تو:
const hasNested = arr => arr.some(Array.isArray);

// ۴. یکسان‌سازی سطح:
const flatten = arr => arr.flat(Infinity);

تمرین‌های Object

// ۱. مقایسه عمیق:
function deepEqual(a, b) {
    if (a === b) return true;
    if (typeof a !== "object" || typeof b !== "object") return false;
    if (Object.keys(a).length !== Object.keys(b).length) return false;

    return Object.keys(a).every(key => deepEqual(a[key], b[key]));
}

// ۲. گرفتن مسیر:
function getByPath(obj, path) {
    return path.split(".").reduce((o, key) => o?.[key], obj);
}
console.log(getByPath({a:{b:{c:42}}}, "a.b.c")); // 42

تمرین‌های Function

// ۱. Curry کردن:
function curry(fn) {
    return function curried(...args) {
        if (args.length >= fn.length) {
            return fn.apply(this, args);
        }
        return (...more) => curried.apply(this, [...args, ...more]);
    };
}

const add3 = curry((a, b, c) => a + b + c);
console.log(add3(1)(2)(3));  // 6
console.log(add3(1, 2)(3));  // 6

// ۲. Memoize:
function memoize(fn) {
    const cache = new Map();
    return function (...args) {
        const key = JSON.stringify(args);
        if (cache.has(key)) return cache.get(key);
        const result = fn.apply(this, args);
        cache.set(key, result);
        return result;
    };
}

تمرین‌های DOM و Event

// ۱. Modal بدون کتابخانه:
class Modal {
            #el;

            constructor() {
                this.#el = document.createElement("div");
                this.#el.className = "modal";
                this.#el.innerHTML = `
                    <div class="modal-content">
                        <span class="close">&times;</span>
                        <div class="modal-body"></div>
                    </div>`;
                document.body.appendChild(this.#el);

                this.#el.querySelector(".close")
                    .addEventListener("click", () => this.close());
            }

            open(content) {
                this.#el.querySelector(".modal-body").innerHTML = content;
                this.#el.classList.add("visible");
            }

            close() {
                this.#el.classList.remove("visible");
            }
        }

تمرین‌های API و Async

// ۱. درخواست موازی با محدودیت:
async function limitedParallel(tasks, limit = 3) {
    const results = [];
    let executing = [];

    for (const task of tasks) {
        const p = task().then(result => {
            executing = executing.filter(e => e !== p);
            return result;
        });
        executing.push(p);
        results.push(p);

        if (executing.length >= limit) {
            await Promise.race(executing);
        }
    }

    return Promise.all(results);
}

پروژه‌های بازی

بازیسطحمفاهیم
Tic-Tac-Toeمقدماتیمنطق، آرایه دوبعدی
Memory MatchمقدماتیDOM، رویداد، تایمر
SnakeمتوسطCanvas، حلقه بازی
Tetrisپیشرفتهالگوریتم، انیمیشن
Flappy Birdمتوسطفیزیک ساده، rAF

سطح‌بندی پروژه‌ها

سطحزمانفناوری
مقدماتی۱-۳ روزJS خالص
متوسط۱-۲ هفته+ API، localStorage
پیشرفته۱ ماه++ PWA، Worker، WebGL

نکات موفقیت پروژه

این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.

ابزارهای مرتبط