نمونه اولیه و متاپروگرمینگ
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 — پایان زنجیره
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 | مخفیسازی ویژگی |
deleteProperty | delete | جلوگیری از حذف |
apply | فراخوانی تابع | احراز صلاحیت، کش |
construct | new | تزریق وابستگی |
متاپروگرمینگ کاربردی — اعتبارسنجی با 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 |
این مبحث بخشی از دوره آموزش کامل جاوا اسکریپت است.