IIFE ونمط الوحدة
تعبيرات الدوال المستدعاة فوراً (IIFE) هي نمط JavaScript قوي ينشئ نطاقات معزولة ويمكّن من تنظيم الكود بطريقة معيارية. في هذا الدرس، سنستكشف كيف يعمل IIFE وكيف يشكل أساس نمط الوحدة، أحد أهم أنماط التصميم في JavaScript.
ما هو IIFE؟
IIFE (يُنطق "iffy") هي دالة يتم تعريفها وتنفيذها فوراً. إنها طريقة لإنشاء نطاق خاص وتجنب تلويث النطاق العام.
// بناء جملة IIFE الأساسي
(function() {
console.log("هذا يعمل فوراً!");
})();
// IIFE دالة السهم
(() => {
console.log("IIFE دالة السهم تعمل أيضاً!");
})();
// IIFE مع معاملات
(function(name) {
console.log(`مرحباً، ${name}!`);
})("أحمد");
المفهوم الأساسي: الأقواس حول الدالة (function() {...}) تحولها إلى تعبير دالة، والأقواس اللاحقة () تستدعيها فوراً.
لماذا استخدام IIFE؟
تحل IIFEs عدة مشاكل مهمة في JavaScript:
// المشكلة: تلويث النطاق العام
var counter = 0;
var increment = function() { counter++; };
var decrement = function() { counter--; };
// الحل: IIFE ينشئ نطاقاً خاصاً
(function() {
var counter = 0; // متغير خاص
window.increment = function() { counter++; return counter; };
window.decrement = function() { counter--; return counter; };
})();
console.log(counter); // undefined (خاص!)
console.log(increment()); // 1
console.log(increment()); // 2
أشكال بناء جملة IIFE
هناك عدة طرق لكتابة IIFEs:
// IIFE قياسي
(function() {
console.log("IIFE قياسي");
})();
// بناء جملة بديل (أسلوب Douglas Crockford)
(function() {
console.log("IIFE بديل");
}());
// IIFE مُسمى (مفيد للتصحيح)
(function myIIFE() {
console.log("IIFE مُسمى");
})();
// عوامل أحادية (أقل شيوعاً)
!function() {
console.log("استخدام عامل !");
}();
+function() {
console.log("استخدام عامل +");
}();
void function() {
console.log("استخدام عامل void");
}();
نصيحة: بناء الجملة القياسي (function() {})() هو الأكثر قابلية للقراءة والأكثر استخداماً. التزم بهذا ما لم يكن لديك سبب محدد لاستخدام البدائل.
IIFE مع قيم الإرجاع
يمكن لـ IIFEs إرجاع قيم، وهو أمر حاسم لنمط الوحدة:
const calculator = (function() {
// متغيرات خاصة
const pi = 3.14159;
// دالة خاصة
function validate(num) {
return typeof num === 'number' && !isNaN(num);
}
// واجهة عامة
return {
add: function(a, b) {
if (validate(a) && validate(b)) {
return a + b;
}
return "مدخلات غير صالحة";
},
circleArea: function(radius) {
if (validate(radius)) {
return pi * radius * radius;
}
return "نصف قطر غير صالح";
}
};
})();
console.log(calculator.add(5, 3)); // 8
console.log(calculator.circleArea(10)); // 314.159
console.log(calculator.pi); // undefined (خاص)
console.log(calculator.validate); // undefined (خاص)
نمط الوحدة
يستخدم نمط الوحدة IIFE لإنشاء وحدات مع أعضاء خاصة وعامة. إنه أحد أهم الأنماط لتنظيم كود JavaScript:
const UserModule = (function() {
// أعضاء خاصة
let users = [];
let currentId = 1;
function validateUser(user) {
return user.name && user.email;
}
function generateId() {
return currentId++;
}
// واجهة عامة
return {
addUser: function(name, email) {
const user = { id: generateId(), name, email };
if (validateUser(user)) {
users.push(user);
return user;
}
return null;
},
getUser: function(id) {
return users.find(user => user.id === id);
},
getAllUsers: function() {
// إرجاع نسخة لمنع التعديل المباشر
return users.map(user => ({ ...user }));
},
updateUser: function(id, updates) {
const user = users.find(u => u.id === id);
if (user) {
Object.assign(user, updates);
return true;
}
return false;
},
deleteUser: function(id) {
const index = users.findIndex(u => u.id === id);
if (index !== -1) {
users.splice(index, 1);
return true;
}
return false;
},
getUserCount: function() {
return users.length;
}
};
})();
// الاستخدام
const user1 = UserModule.addUser("أحمد علي", "ahmed@example.com");
const user2 = UserModule.addUser("سارة محمد", "sara@example.com");
console.log(UserModule.getAllUsers());
console.log(UserModule.getUserCount()); // 2
نمط الوحدة الكاشف
نمط الوحدة الكاشف هو نسخة محسّنة تجعل الكود أكثر تنظيماً من خلال تعريف جميع الدوال أولاً والكشف عنها في النهاية:
const ShoppingCart = (function() {
// متغيرات خاصة
let items = [];
let total = 0;
// دوال خاصة
function calculateTotal() {
total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
return total;
}
function findItem(id) {
return items.find(item => item.id === id);
}
// دوال عامة
function addItem(id, name, price, quantity = 1) {
const existingItem = findItem(id);
if (existingItem) {
existingItem.quantity += quantity;
} else {
items.push({ id, name, price, quantity });
}
calculateTotal();
return { added: true, total: getTotal() };
}
function removeItem(id) {
const index = items.findIndex(item => item.id === id);
if (index !== -1) {
items.splice(index, 1);
calculateTotal();
return true;
}
return false;
}
function updateQuantity(id, quantity) {
const item = findItem(id);
if (item) {
item.quantity = quantity;
calculateTotal();
return true;
}
return false;
}
function getItems() {
return items.map(item => ({ ...item }));
}
function getTotal() {
return total;
}
function clearCart() {
items = [];
total = 0;
}
// الكشف عن الواجهة العامة
return {
addItem: addItem,
removeItem: removeItem,
updateQuantity: updateQuantity,
getItems: getItems,
getTotal: getTotal,
clearCart: clearCart
};
})();
// الاستخدام
ShoppingCart.addItem(1, "لابتوب", 999, 1);
ShoppingCart.addItem(2, "ماوس", 29, 2);
console.log(ShoppingCart.getItems());
console.log(ShoppingCart.getTotal()); // 1057
الميزة: يوضح نمط الوحدة الكاشف الدوال العامة (المكشوفة) والخاصة بوضوح، مما يحسن قابلية القراءة والصيانة.
نمط فضاء الأسماء
يمكن استخدام IIFEs لإنشاء فضاءات أسماء، مما يمنع تعارضات التسمية:
// إنشاء فضاء أسماء
const MyApp = MyApp || {};
// إضافة وحدات إلى فضاء الأسماء
MyApp.utilities = (function() {
function formatDate(date) {
return date.toLocaleDateString();
}
function formatCurrency(amount) {
return `$${amount.toFixed(2)}`;
}
return {
formatDate: formatDate,
formatCurrency: formatCurrency
};
})();
MyApp.validators = (function() {
function isEmail(email) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function isPhoneNumber(phone) {
return /^\d{10}$/.test(phone);
}
return {
isEmail: isEmail,
isPhoneNumber: isPhoneNumber
};
})();
// الاستخدام
console.log(MyApp.utilities.formatCurrency(1234.5)); // $1234.50
console.log(MyApp.validators.isEmail("test@example.com")); // true
اعتماديات الوحدة
يمكن أن تعتمد الوحدات على وحدات أخرى من خلال تمريرها كمعاملات:
// وحدة مشابهة لـ jQuery
const $ = { ajax: function() { console.log("استدعاء Ajax"); } };
// وحدة تعتمد على $
const DataService = (function($) {
function fetchUsers() {
$.ajax();
return ["مستخدم1", "مستخدم2", "مستخدم3"];
}
function fetchPosts() {
$.ajax();
return ["منشور1", "منشور2"];
}
return {
fetchUsers: fetchUsers,
fetchPosts: fetchPosts
};
})($); // تمرير $ كاعتمادية
console.log(DataService.fetchUsers());
// اعتماديات متعددة
const ComplexModule = (function($, UserModule, DataService) {
function initialize() {
const users = DataService.fetchUsers();
console.log(`تم تحميل ${users.length} مستخدمين`);
}
return {
init: initialize
};
})($, UserModule, DataService);
أفضل ممارسة: تمرير الاعتماديات صراحةً يجعل وحداتك أكثر قابلية للاختبار ويوثق بوضوح ما تحتاجه كل وحدة.
IIFE للتكوين
IIFEs رائعة لإنشاء وحدات قابلة للتكوين:
const Logger = (function(config) {
const settings = {
enabled: config.enabled !== undefined ? config.enabled : true,
level: config.level || 'info',
prefix: config.prefix || '[LOG]'
};
const levels = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
function shouldLog(level) {
return settings.enabled && levels[level] >= levels[settings.level];
}
function formatMessage(level, message) {
const timestamp = new Date().toISOString();
return `${settings.prefix} [${level.toUpperCase()}] ${timestamp}: ${message}`;
}
return {
debug: function(message) {
if (shouldLog('debug')) {
console.log(formatMessage('debug', message));
}
},
info: function(message) {
if (shouldLog('info')) {
console.log(formatMessage('info', message));
}
},
warn: function(message) {
if (shouldLog('warn')) {
console.warn(formatMessage('warn', message));
}
},
error: function(message) {
if (shouldLog('error')) {
console.error(formatMessage('error', message));
}
}
};
})({ enabled: true, level: 'info', prefix: '[MyApp]' });
Logger.debug("لن يظهر هذا"); // المستوى info
Logger.info("تم بدء التطبيق");
Logger.warn("ذاكرة منخفضة");
Logger.error("فشل الاتصال");
وحدات ES6 مقابل وحدات IIFE
JavaScript الحديثة لديها وحدات أصلية، لكن فهم وحدات IIFE لا يزال قيّماً:
// وحدة IIFE (متوافقة مع ES5)
const MathUtils = (function() {
function add(a, b) { return a + b; }
function multiply(a, b) { return a * b; }
return { add, multiply };
})();
// وحدة ES6 (نهج حديث)
// في mathUtils.js
export function add(a, b) { return a + b; }
export function multiply(a, b) { return a * b; }
// في main.js
import { add, multiply } from './mathUtils.js';
متى تستخدم كل واحدة:
- استخدم وحدات ES6 للمشاريع الحديثة مع أدوات البناء
- استخدم وحدات IIFE لدعم المتصفحات القديمة
- استخدم IIFE للسكريبتات السريعة بدون عملية بناء
- تعمل وحدات IIFE في جميع المتصفحات بدون transpilation
مهم: بينما وحدات ES6 هي المستقبل، لا تزال وحدات IIFE مستخدمة على نطاق واسع في الكود القديم والمكتبات. فهم كليهما ضروري لمطور JavaScript شامل.
حالات استخدام IIFE الشائعة
إليك سيناريوهات عملية حيث تتفوق IIFEs:
// 1. تجنب تعارضات المتغيرات في النطاق العام
(function() {
var $ = "متغير $ المخصص الخاص بي";
console.log($); // لا يتعارض مع $ الخاص بـ jQuery
})();
// 2. إنشاء عدادات فريدة
const counter1 = (function() {
let count = 0;
return () => ++count;
})();
const counter2 = (function() {
let count = 0;
return () => ++count;
})();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1 (عداد مستقل)
// 3. التهيئة لمرة واحدة
(function initialize() {
const config = loadConfiguration();
setupEventListeners();
initializeApp(config);
console.log("تم تهيئة التطبيق");
})();
// 4. حماية الكود من التعديل
const API = (function() {
const API_KEY = "مفتاح-سري-12345";
function makeRequest(endpoint) {
return fetch(endpoint, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
}
return { makeRequest };
})();
// API_KEY غير قابل للوصول تماماً من الخارج
تمرين تطبيقي:
التحدي: أنشئ وحدة TodoList باستخدام نمط الوحدة الكاشف. يجب أن يكون لها تخزين خاص للمهام وطرق عامة لإضافة وإزالة وتبديل الإكمال والحصول على جميع المهام.
الحل:
const TodoList = (function() {
// حالة خاصة
let todos = [];
let nextId = 1;
// دوال خاصة
function findTodo(id) {
return todos.find(todo => todo.id === id);
}
// دوال عامة
function addTodo(text) {
const todo = {
id: nextId++,
text: text,
completed: false,
createdAt: new Date()
};
todos.push(todo);
return todo;
}
function removeTodo(id) {
const index = todos.findIndex(todo => todo.id === id);
if (index !== -1) {
const removed = todos.splice(index, 1)[0];
return removed;
}
return null;
}
function toggleTodo(id) {
const todo = findTodo(id);
if (todo) {
todo.completed = !todo.completed;
return todo;
}
return null;
}
function getTodos(filter = 'all') {
if (filter === 'completed') {
return todos.filter(t => t.completed);
}
if (filter === 'active') {
return todos.filter(t => !t.completed);
}
return todos.slice(); // إرجاع نسخة
}
function clearCompleted() {
const beforeLength = todos.length;
todos = todos.filter(t => !t.completed);
return beforeLength - todos.length;
}
// الكشف عن الواجهة العامة
return {
add: addTodo,
remove: removeTodo,
toggle: toggleTodo,
getTodos: getTodos,
clearCompleted: clearCompleted
};
})();
// الاستخدام
TodoList.add("تعلم JavaScript");
TodoList.add("بناء مشروع");
TodoList.toggle(1);
console.log(TodoList.getTodos());
الملخص
في هذا الدرس، تعلمت:
- IIFE (تعبير الدالة المستدعاة فوراً) ينشئ نطاقات معزولة
- IIFEs تمنع تلويث النطاق العام وتعارضات المتغيرات
- يستخدم نمط الوحدة IIFE لإنشاء وحدات مع أعضاء خاصة
- نمط الوحدة الكاشف يحسن تنظيم الكود وقابلية القراءة
- أنماط فضاء الأسماء تساعد في تنظيم وحدات متعددة
- يمكن أن تقبل الوحدات اعتماديات كمعاملات
- تبقى IIFEs قيّمة حتى مع وحدات ES6
- حالات استخدام شائعة: التغليف، التهيئة لمرة واحدة، التكوين
التالي: في الدرس التالي، سنتقن الكلمة الأساسية 'this' ونتعلم كيفية التحكم في السياق في JavaScript!