يوم جيد ، أيها الأصدقاء!
في الغالبية العظمى من الحالات ، بصفتنا مطوري JavaScript ، لا داعي للقلق بشأن العمل مع الذاكرة. المحرك يفعل ذلك لنا.
ومع ذلك ، ستواجه يومًا ما مشكلة تسمى "تسرب الذاكرة" ، والتي لا يمكن حلها إلا من خلال معرفة كيفية تخصيص الذاكرة في JavaScript.
في هذه المقالة ، سأشرح كيفية عمل تخصيص الذاكرة وجمع البيانات المهملة ، وكيفية تجنب بعض مشكلات تسرب الذاكرة الشائعة.
دورة حياة الذاكرة
عندما تقوم بإنشاء متغير أو وظيفة ، فإن محرك JavaScript يخصص لها ذاكرة ويطلقها عندما لا تكون هناك حاجة إليها.
تخصيص الذاكرة هو عملية حجز مساحة معينة في الذاكرة ، وتحرير الذاكرة هو تحرير تلك المساحة بحيث يمكن استخدامها لأغراض أخرى.
في كل مرة يتم فيها إنشاء متغير أو وظيفة ، تمر الذاكرة بالمراحل التالية:
- تخصيص الذاكرة - يقوم المحرك تلقائيًا بتخصيص ذاكرة للكائن الذي تم إنشاؤه
- استخدام الذاكرة - قراءة البيانات وكتابتها في الذاكرة ما هي إلا كتابة وقراءة البيانات من متغير
- تحرير الذاكرة - يتم تنفيذ هذه الخطوة تلقائيًا بواسطة المحرك. بمجرد تحرير الذاكرة ، يمكن استخدامها لأغراض أخرى.
كومة وكومة
السؤال التالي: ماذا تعني الذاكرة؟ أين يتم تخزين البيانات بالفعل؟
يحتوي المحرك على مكانين من هذا القبيل: الكومة والمكدس. الكومة والمكدس عبارة عن هياكل بيانات يستخدمها المحرك لأغراض مختلفة.
المكدس: تخصيص الذاكرة الثابتة
يتم تخزين جميع البيانات الموجودة في المثال على المكدس لأنها بدائية.
المكدس هو بنية بيانات تستخدم لتخزين البيانات الثابتة. البيانات الثابتة هي البيانات التي يعرف حجمها المحرك في مرحلة تجميع الكود. في JavaScript ، تكون هذه البيانات أولية (سلاسل ، أرقام ، منطقية ، غير محددة وخالية) ومراجع تشير إلى الكائنات والوظائف.
نظرًا لأن المحرك يعرف أن حجم البيانات لن يتغير ، فإنه يخصص حجمًا ثابتًا للذاكرة لكل قيمة. تسمى عملية تخصيص الذاكرة قبل تنفيذ التعليمات البرمجية تخصيص الذاكرة الثابتة. نظرًا لأن المحرك يخصص حجمًا ثابتًا للذاكرة ، فهناك حدود معينة لهذا الحجم ، والتي تعتمد بشكل كبير على المتصفح.
الكومة: تخصيص الذاكرة الديناميكي
الكومة لتخزين الأشياء والوظائف. على عكس المكدس ، لا يخصص المحرك حجم ذاكرة ثابتًا للأشياء. يتم تخصيص الذاكرة حسب الحاجة. يسمى تخصيص الذاكرة هذا ديناميكيًا. فيما يلي جدول مقارنة صغير:
| كومة | كومة |
|---|---|
| القيم والمراجع البدائية | الأشياء والوظائف |
| الحجم معروف في وقت الترجمة | الحجم معروف في وقت التشغيل |
| تخصيص الذاكرة الثابتة | حجم الذاكرة لكل كائن غير محدود |
أمثلة على
لنلق نظرة على بعض الأمثلة.
const person = {
name: "John",
age: 24,
};
يخصص المحرك ذاكرة لهذا الكائن على الكومة. ومع ذلك ، يتم تخزين قيم الخصائص في المكدس.
const hobbies = ["hiking", "reading"];
المصفوفات هي كائنات ، لذا يتم تخزينها في الكومة
let name = "John";
const age = 24;
name = "John Doe";
const firstName = name.slice(0, 4);
البدائية غير قابلة للتغيير. هذا يعني أنه بدلاً من تغيير القيمة الأصلية ، يقوم JavaScript بإنشاء قيمة جديدة.
الروابط
يتم تخزين جميع المتغيرات في المكدس. في حالة القيم غير الأولية ، يقوم المكدس بتخزين المراجع إلى كائن في الكومة. الذاكرة على الكومة مضطربة. هذا هو السبب في أننا بحاجة إلى روابط على المكدس. يمكنك التفكير في الروابط كعناوين وكائنات كمنازل في عنوان محدد.
في الصورة أعلاه ، يمكننا أن نرى كيف يتم تخزين القيم المختلفة. لاحظ أن هذا الشخص والشخص الجديد يشير إلى نفس الكائن
أمثلة على
const person = {
name: "John",
age: 24,
};
يؤدي هذا إلى إنشاء كائن جديد على الكومة ومرجع إليه في المكدس
جمع القمامة
بمجرد أن يلاحظ المحرك أن متغيرًا أو وظيفة لم تعد مستخدمة ، فإنه يحرر الذاكرة التي يشغلها.
في الواقع ، مشكلة تحرير الذاكرة غير المستخدمة غير قابلة للحل: لا توجد خوارزمية مثالية لحلها.
في هذه المقالة ، سنلقي نظرة على خوارزميتين تقدمان أفضل الحلول حتى الآن: حساب جمع البيانات المهملة ووضع العلامات والمسح.
جمع القمامة من خلال العد المرجعي
كل شيء بسيط هنا - الكائنات التي لا يتم حذف نقاط مرجعية لها من الذاكرة. لنلقي نظرة على مثال. تمثل الخطوط الروابط.
لاحظ أن كائن "الهوايات" فقط يبقى على الكومة ، حيث تتم الإشارة إلى هذا الكائن فقط في المكدس.
روابط دورية
تكمن مشكلة طريقة جمع البيانات المهملة في عدم القدرة على تحديد المراجع الدائرية. هذا هو الموقف الذي يشير فيه جسمان أو أكثر إلى بعضهما البعض ولكن لا تحتوي على xrefs. أولئك. لا يمكن الوصول إلى هذه الأشياء من الخارج.
const son = {
name: "John",
};
const dad = {
name: "Johnson",
};
son.dad = dad;
dad.son = son;
son = null;
dad = null;
نظرًا لأن الكائنين "ابن" و "أبي" يشيران إلى بعضهما البعض ، فإن خوارزمية حساب المرجع لا يمكنها تحرير الذاكرة. ومع ذلك ، لم تعد هذه الكائنات متاحة للتعليمات البرمجية الخارجية.
خوارزمية لوضع العلامات والتنظيف
هذه الخوارزمية تحل مشكلة المراجع الدائرية. بدلاً من حساب المراجع التي تشير إلى كائن ، فإنه يحدد إمكانية الوصول للكائن من الكائن الجذر. الكائن الجذر هو كائن "window" في المتصفح أو الكائن "global" في Node.js.
تقوم الخوارزمية بتمييز الكائنات على أنها لا يمكن الوصول إليها وتزيلها. وبالتالي ، لم تعد المراجع الدائرية مشكلة. في المثال أعلاه ، لا يمكن الوصول إلى الكائنين "أبي" و "ابن" من الكائن الجذر. سيتم وضع علامة عليها كمهملات وإزالتها. تم تنفيذ الخوارزمية المعنية في جميع المتصفحات الحديثة منذ عام 2012. التحسينات التي تم إجراؤها منذ ذلك الحين تتعلق بتحسينات التنفيذ والأداء ، ولكن ليس الفكرة الأساسية للخوارزمية.
تنازلات
يتيح لنا الجمع التلقائي للقمامة التركيز على إنشاء التطبيقات وعدم إضاعة الوقت في إدارة الذاكرة. ومع ذلك ، كل شيء يأتي بسعر.
استخدام الذاكرة
نظرًا لأن الخوارزميات تستغرق بعض الوقت لتحديد أن الذاكرة لم تعد مستخدمة ، تميل تطبيقات JavaScript إلى استخدام ذاكرة أكثر مما تحتاجه بالفعل.
على الرغم من وضع علامة على الكائنات على أنها مهملات ، يجب أن يقرر المجمع وقت تجميعها حتى لا يعيق تدفق البرنامج. إذا كنت تريد أن يكون التطبيق الخاص بك فعالاً قدر الإمكان فيما يتعلق باستخدام الذاكرة ، فمن الأفضل لك استخدام لغة برمجة منخفضة المستوى. لكن ضع في اعتبارك أن هذه اللغات لها مقايضات خاصة بها.
أداء
تعمل خوارزميات جمع البيانات المهملة بشكل دوري لتنظيف الكائنات غير المستخدمة. المشكلة هي أننا ، كمطورين ، لا نعرف بالضبط متى سيحدث هذا. يمكن أن تؤثر الكميات الكبيرة من عمليات جمع القمامة أو جمع القمامة بشكل متكرر على الأداء لأنها تتطلب قدرًا معينًا من قوة المعالجة. ومع ذلك ، يحدث هذا عادة دون أن يلاحظه أحد من قبل المستخدم والمطور.
تسريبات الذاكرة
دعنا نلقي نظرة سريعة على أكثر مشاكل تسرب الذاكرة شيوعًا.
المتغيرات العالمية
إذا قمت بتعريف متغير بدون استخدام إحدى الكلمات الأساسية (var ، let ، أو const) ، يصبح المتغير خاصية للكائن العام.
users = getUsers();
يؤدي تنفيذ التعليمات البرمجية الخاصة بك في الوضع المتشدد إلى تجنب ذلك.
في بعض الأحيان نعلن عن المتغيرات العالمية عن قصد. في هذه الحالة ، من أجل تحرير الذاكرة التي يشغلها مثل هذا المتغير ، يجب عليك تخصيص القيمة "فارغة" لها:
window.users = null;
نسي الموقتات وعمليات رد النداء
إذا نسيت الموقتات وعمليات الاسترجاعات ، يمكن أن يزيد استخدام ذاكرة التطبيق بشكل كبير. كن حذرًا ، خاصة عند إنشاء تطبيقات صفحة واحدة (SPA) حيث تتم إضافة معالجات الأحداث وعمليات الاسترجاعات ديناميكيًا.
المنسية توقيت
const object = {};
const intervalId = setInterval(function () {
// , , ,
// ,
doSomething(object);
}, 2000);
يقوم الكود أعلاه بتشغيل الوظيفة كل ثانيتين. إذا لم تعد بحاجة إلى المؤقت ، فيجب عليك إلغاؤه من خلال:
clearInterval(intervalId);
هذا مهم بشكل خاص لـ SPA. حتى إذا انتقلت إلى صفحة أخرى حيث لم يكن المؤقت قيد الاستخدام ، فسيتم تشغيله في الخلفية.
عمليات الاسترجاعات المنسية
لنفترض أنك قمت بتسجيل معالج لنقرة زر قمت بحذفها لاحقًا. في الواقع ، لم تعد هذه مشكلة ، ولكن لا يزال من المستحسن إزالة المعالجات التي لم تعد هناك حاجة إليها:
const element = document.getElementById("button");
const onClick = () => alert("hi");
element.addEventListener("click", onClick);
element.removeEventListener("click", onClick);
element.parentNode.removeChild(element);
روابط خارج DOM
تسرب الذاكرة هذا مشابه للتسرب السابق ، يحدث عند تخزين عناصر DOM في JavaScript:
const elements = [];
const element = document.getElementById("button");
elements.push(element);
function removeAllElements() {
elements.forEach((item) => {
document.body.removeChild(document.getElementById(item.id));
});
}
إذا قمت بإزالة أي من هذه العناصر ، فيجب عليك أيضًا إزالتها من المصفوفة. خلاف ذلك ، لا يمكن إزالة هذه العناصر بواسطة جامع القمامة:
const elements = [];
const element = document.getElementById("button");
elements.push(element);
function removeAllElements() {
elements.forEach((item, index) => {
document.body.removeChild(document.getElementById(item.id));
elements.splice(index, 1);
});
}
أتمنى أن تجد شيئًا مثيرًا للاهتمام لنفسك. شكرآ لك على أهتمامك.