نمونه اولیه و متاپروگرمینگ

Prototype و Prototype Chain، __proto__ و Object.getPrototypeOf، Symbol و Well-Known Symbols، Reflection با Reflect API، Proxy و Proxy Traps و متاپروگرمینگ.

Prototype چیست؟

Prototype مکانیزم ارث‌بری در جاوا اسکریپت است. هر شیء یک لینک مخفی به شیء دیگری دارد — اگر ویژگی در خود شیء نبود، در Prototype جست‌وجو می‌شود.

const animal = {
    eat() { return "غذا می‌خورم"; }
};

// dog.__proto__ به animal اشاره می‌کند:
const dog = Object.create(animal);
dog.bark = () => "هاپ هاپ!";

console.log(dog.bark());  // خودش — هاپ هاپ!
console.log(dog.eat());   // از prototype — غذا می‌خورم
console.log(dog.toString()); // از Object.prototype

Prototype Chain — زنجیره نمونه اولیه

زنجیره‌ای از اشیاء که تا null ادامه دارد:

const arr = [1, 2, 3];
// arr → Array.prototype → Object.prototype → null

console.log(arr.__proto__ === Array.prototype);        // true
console.log(arr.__proto__.__proto__ === Object.prototype); // true
console.log(arr.__proto__.__proto__.__proto__);        // null — پایان زنجیره
جست‌وجوی ویژگی در کل زنجیره انجام می‌شود — به این Property Lookup می‌گویند.

Constructor Prototype

هر تابع سازنده یک ویژگی prototype دارد که برای همه نمونه‌ها مشترک است:

function User(name) {
    this.name = name;
}

// متد روی prototype — یک‌بار تعریف، همه نمونه‌ها:
User.prototype.greet = function () {
    return "سلام " + this.name;
};

const u1 = new User("علی");
const u2 = new User("سارا");

console.log(u1.greet()); // سلام علی
console.log(u1.greet === u2.greet); // true — همان متد مشترک!
console.log(u1.__proto__ === User.prototype); // true

__proto__ — دسترسی قدیمی

__proto__ یک Getter/Setter قدیمی است — امروزه از متدهای استاندارد استفاده کنید:

const obj = {};
console.log(obj.__proto__ === Object.prototype); // true

// روش استاندارد و توصیه‌شده:
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
از __proto__ برای تغییر prototype استفاده نکنید — عملکرد ضعیف و منسوخ است.

Object.getPrototypeOf و setPrototypeOf

const base = { greet() { return "سلام"; } };
const child = {};

// تنظیم prototype:
Object.setPrototypeOf(child, base);
console.log(child.greet()); // سلام

// خواندن prototype:
console.log(Object.getPrototypeOf(child) === base); // true

// ساخت مستقیم با prototype مشخص — روش سریع‌تر:
const direct = Object.create(base, {
    name: { value: "مستقیم", enumerable: true }
});
console.log(direct.name);    // مستقیم
console.log(direct.greet()); // سلام

Symbol — شناسه‌های یکتا

Symbol نوع داده اولیه‌ای است که همیشه یکتاست — برای کلیدهای بدون تداخل:

const id1 = Symbol("id");
const id2 = Symbol("id");

console.log(id1 === id2); // false — هر Symbol یکتاست!

const user = {
    name: "علی",
    [id1]: 123           // کلید Symbol
};

console.log(user[id1]);       // 123
console.log(Object.keys(user)); // ["name"] — Symbol دیده نمی‌شود!

Well-Known Symbols

Symbol‌های داخلی رفتار اشیا را سفارشی می‌کنند:

// Symbol.iterator — ساخت شیء قابل پیمایش:
const range = {
    from: 1,
    to: 5,
    [Symbol.iterator]() {
        let current = this.from;
        const last = this.to;
        return {
            next() {
                return current <= last
                    ? { done: false, value: current++ }
                    : { done: true };
            }
        };
    }
};

console.log([...range]); // [1, 2, 3, 4, 5]

// Symbol.toPrimitive — کنترل تبدیل نوع:
const price = {
    amount: 100,
    [Symbol.toPrimitive](hint) {
        if (hint === "string") return this.amount + " تومان";
        if (hint === "number") return this.amount;
        return true;
    }
};

console.log(String(price)); // 100 تومان
console.log(+price);        // 100
console.log(price == 100);  // true

Reflection و Reflect API

Reflect متدهایی برای اعمال عملیات متاپروگرمینگ به‌صورت استاندارد:

const user = { name: "علی", age: 25 };

// Reflect.get — خواندن ویژگی:
console.log(Reflect.get(user, "name")); // علی

// Reflect.has — مثل in:
console.log(Reflect.has(user, "age"));  // true

// Reflect.set — تنظیم مقدار:
Reflect.set(user, "city", "تهران");
console.log(user.city); // تهران

// Reflect.ownKeys — همه کلیدها (شامل Symbol):
console.log(Reflect.ownKeys(user)); // ["name", "age", "city"]

// Reflect.construct — ساخت با constructor:
const newObj = Reflect.construct(Object, []);
console.log(newObj instanceof Object); // true

// Reflect.apply — فراخوانی تابع:
const max = Reflect.apply(Math.max, null, [10, 20, 5]);
console.log(max); // 20

Proxy — واسط کنترل‌شده

Proxy رفتار شیء را می‌پوشاند و در Traps قابل کنترل است:

const user = { name: "علی", age: 25 };

const proxy = new Proxy(user, {
    get(target, prop) {
        console.log("خواندن: " + String(prop));
        return prop in target ? target[prop] : "پیش‌فرض";
    },
    set(target, prop, value) {
        console.log("نوشتن " + String(prop) + " = " + value);
        if (prop === "age" && value < 0) {
            throw new RangeError("سن نمی‌تواند منفی باشد");
        }
        target[prop] = value;
        return true;
    }
});

console.log(proxy.name);  // خواندن: name → علی
proxy.city = "شیراز";     // نوشتن city = شیراز
console.log(proxy.missing); // خواندن: missing → پیش‌فرض

Proxy Traps — تله‌های مهم

Trapعملیات مربوطهکاربرد
getخواندن ویژگیاعتبارسنجی، لاگ، مقدار پیش‌فرض
setنوشتن ویژگیاعتبارسنجی داده، reactive
hasعملگر inمخفی‌سازی ویژگی
deletePropertydeleteجلوگیری از حذف
applyفراخوانی تابعاحراز صلاحیت، کش
constructnewتزریق وابستگی

متاپروگرمینگ کاربردی — اعتبارسنجی با Proxy

function createValidator(schema) {
    return new Proxy({}, {
        set(target, prop, value) {
            const rule = schema[prop];
            if (!rule) throw new Error("ویژگی مجاز نیست: " + String(prop));
            if (rule.type === "number" && typeof value !== "number") {
                throw new TypeError(String(prop) + " باید عدد باشد");
            }
            if (rule.min !== undefined && value < rule.min) {
                throw new RangeError(String(prop) + " >= " + rule.min);
            }
            target[prop] = value;
            return true;
        }
    });
}

const product = createValidator({
    price: { type: "number", min: 0 },
    quantity: { type: "number", min: 1 }
});

product.price = 100;    // OK
product.quantity = 5;   // OK
// product.price = -50; // RangeError!
// product.name = "x";  // Error: ویژگی مجاز نیست

Proxy برای آرایه Reactive

function reactiveArray(callback) {
    const arr = [];
    return new Proxy(arr, {
        set(target, prop, value) {
            target[prop] = value;
            if (prop !== "length") callback("add", value);
            return true;
        },
        deleteProperty(target, prop) {
            callback("remove", target[prop]);
            delete target[prop];
            return true;
        }
    });
}

const watched = reactiveArray((action, item) => {
    console.log(action + ": " + item);
});

watched.push("سیب");    // add: سیب
watched.push("موز");    // add: موز
watched.pop();          // remove: موز

جمع‌بندی

ابزارکاربرد
Object.create(proto)ساخت شیء با prototype مشخص
Object.getPrototypeOfخواندن prototype
Symbolکلید یکتا و بدون تداخل
Symbol.iteratorساخت شیء قابل پیمایش
Reflect.*عملیات متاستاندارد
Proxyکنترل رفتار شیء با Traps

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

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