المواد ، التي ننشر ترجمتها اليوم ، أعدها ماتياس بينينز وبينديكت ميرير. إنهم يعملون على محرك
V8 JS في Google. هذه المقالة مخصصة لبعض الآليات الأساسية المميزة ليس فقط لمحرك V8 ، ولكن أيضًا لمحركات أخرى. الإلمام بالبنية الداخلية لمثل هذه الآليات يسمح للمشاركين في تطوير جافا سكريبت بالتنقل بشكل أفضل في مشكلات أداء التعليمات البرمجية. على وجه الخصوص ، سنتحدث هنا عن ميزات خطوط أنابيب تحسين المحرك ، وكيفية تسريع الوصول إلى خصائص النماذج الأولية للكائنات.

مستويات تحسين الشفرة والمفاضلات
تبدو عملية تحويل نصوص البرامج المكتوبة بلغة جافا سكريبت إلى كود مناسب للتنفيذ متشابهة تقريبًا في محركات مختلفة.
عملية تحويل كود JS المصدر إلى كود قابل للتنفيذيمكن العثور على التفاصيل
هنا . بالإضافة إلى ذلك ، تجدر الإشارة إلى أنه على الرغم من أنه على مستوى عالٍ ، فإن خطوط الأنابيب لتحويل شفرة المصدر إلى قابلة للتنفيذ متشابهة جدًا للمحركات المختلفة ، إلا أن أنظمة تحسين الشفرة الخاصة بها تختلف غالبًا. لماذا هذا؟ لماذا تمتلك بعض المحركات مستويات تحسين أكثر من غيرها؟ اتضح أن المحركات يجب أن تتنازل بطريقة أو بأخرى ، والتي تتكون في حقيقة أنها إما أن تتمكن من إنشاء رمز بسرعة ليس الأكثر كفاءة ولكنه مناسب للتنفيذ ، أو قضاء المزيد من الوقت في إنشاء مثل هذا الرمز ، ولكن بسبب ذلك ، تحقيق الأداء الأمثل.
إعداد سريع للرمز للتنفيذ والرمز الأمثل الذي يستغرق وقتًا أطول ولكنه يعمل بشكل أسرعإن المترجم قادر على توليد رمز ثانوي بسرعة ، ولكن هذا الرمز عادة لا يكون فعالاً للغاية. من ناحية أخرى ، يحتاج المترجم المحسن إلى مزيد من الوقت لإنشاء التعليمات البرمجية ، ولكن في النهاية يحصل على كود آلة محسن وأسرع.
هذا هو نموذج إعداد التعليمات البرمجية للتنفيذ الذي يستخدم في V8. يسمى مترجم V8 Ignition ، وهو أسرع المترجمين الحاليين (من حيث تنفيذ رمز المصدر المصدر). يسمى مترجم V8 الأمثل TurboFan ، وهو المسؤول عن إنشاء رمز آلة محسن للغاية.
مترجم الإشعال و TurboFan الأمثل المترجمالمفاضلة بين التأخير في بدء البرنامج وسرعة التنفيذ هو السبب في أن بعض محركات JS لديها مستويات إضافية من التحسين. على سبيل المثال ، في SpiderMonkey ، بين المترجم والمترجم الأمثل IonMonkey ، هناك مستوى متوسط يمثله المترجم الأساسي (يطلق عليه "مترجم الخط الأساسي" في
وثائق موزيلا ، ولكن "الخط الأساسي" ليس اسمًا مناسبًا).
مستويات تحسين رمز SpiderMonkeyيقوم المترجم بسرعة بإنشاء رمز البايت ، ولكن يتم تنفيذ هذا الرمز ببطء نسبي. يستغرق المحول البرمجي الأساسي وقتًا أطول لإنشاء الشفرة ، ولكن هذا الرمز أسرع بالفعل. أخيرًا ، يستغرق المترجم المحسن IonMonkey معظم الوقت لإنشاء رمز الجهاز ، ولكن يمكن تنفيذ هذا الرمز بكفاءة عالية.
دعنا نلقي نظرة على مثال محدد ونلقي نظرة على كيفية تعامل خطوط الأنابيب الخاصة بالمحركات المختلفة مع الكود. في المثال المقدم هنا ، هناك حلقة "ساخنة" تحتوي على كود يتكرر مرات عديدة.
let result = 0; for (let i = 0; i < 4242424242; ++i) { result += i; } console.log(result);
يبدأ V8 في تنفيذ رمز البايت في مترجم الإشعال. في وقت ما ، يكتشف المحرك أن الكود "ساخن" ويطلق الواجهة الأمامية TurboFan ، والتي تعد جزءًا من TurboFan التي تعمل مع بيانات التنميط وإنشاء تمثيل أساسي للآلة للكود. ثم يتم تمرير البيانات إلى محسن TurboFan ، الذي يعمل في دفق منفصل ، لمزيد من التحسينات.
تحسين الكود الساخن في V8أثناء التحسين ، يستمر V8 في تنفيذ البايت كود في Ignition. عند اكتمال المُحسِّن ، لدينا رمز جهاز قابل للتنفيذ يمكن استخدامه في المستقبل.
يبدأ محرك SpiderMonkey أيضًا في تنفيذ الرمز البايت في المترجم. ولكن لها مستوى إضافي يمثله المترجم الأساسي ، مما يؤدي إلى حقيقة أن الكود "الساخن" يصل أولاً إلى هذا المترجم. يقوم بإنشاء الرمز الأساسي في السلسلة الرئيسية ، ويتم الانتقال إلى تنفيذ هذا الرمز عندما يكون جاهزًا.
تحسين الكود الساخن في SpiderMonkeyإذا كانت الشفرة الأساسية تعمل لفترة كافية ، فسيطلق SpiderMonkey في النهاية الواجهة الأمامية والمحسن IonMonkey ، وهو ما يشبه إلى حد كبير ما يحدث في V8. يستمر تشغيل الكود الأساسي كجزء من عملية تحسين الكود التي يقوم بها IonMonkey. ونتيجة لذلك ، عند اكتمال التحسين ، يتم تنفيذ الكود المُحسّن بدلاً من الكود الأساسي.
تشبه بنية محرك شقرا إلى حد كبير بنية SpiderMonkey ، لكن شقرا تسعى إلى مستوى أعلى من التزامن لتجنب حظر الخيط الرئيسي. بدلاً من حل أي مهام ترجمة في الخيط الرئيسي ، تقوم Chakra بنسخ وإرسال بيانات البايت كود والتوصيف التي من المحتمل أن يحتاجها المترجم في عملية تجميع منفصلة.
تحسين الشفرة الساخنة في شقراعندما يكون الكود الذي تم إعداده بواسطة SimpleJIT جاهزًا ، سيقوم المحرك بتنفيذه بدلاً من البايت كود. تتكرر هذه العملية للمضي قدما في تنفيذ التعليمات البرمجية التي أعدتها FullJIT. ميزة هذا النهج هو أن فترات التوقف المؤقت المرتبطة بنسخ البيانات عادة ما تكون أقصر بكثير من تلك التي يسببها تشغيل مترجم كامل (الواجهة الأمامية). ومع ذلك ، فإن ناقص هذا النهج هو حقيقة أن خوارزميات النسخ الإرشادية قد تفوت بعض المعلومات التي قد تكون مفيدة لبعض أنواع التحسين. هنا نرى مثالاً على حل وسط بين جودة الشفرة المستلمة والتأخيرات.
في JavaScriptCore ، يتم تنفيذ جميع مهام التجميع المحسنة بالتوازي مع سلسلة المحادثات الرئيسية المسؤولة عن تنفيذ شفرة JavaScript. ومع ذلك ، لا توجد مرحلة نسخ. بدلاً من ذلك ، يستدعي مؤشر الترابط الرئيسي مهام الترجمة ببساطة في مؤشر ترابط آخر. ثم يستخدم المحول البرمجي نظام قفل معقد للوصول إلى بيانات التنميط من الخيط الرئيسي.
أمثلية الشفرة "الساخنة" في JavaScriptCoreميزة هذا النهج هو أنه يقلل من الحظر القسري لمؤشر الترابط الرئيسي الناجم عن حقيقة أنه يؤدي مهام تحسين التعليمات البرمجية. تتمثل عيوب هذه البنية في أن تنفيذها يتطلب حل المهام المعقدة لمعالجة البيانات متعددة الخيوط ، وأنه أثناء العمل ، لإجراء عمليات مختلفة ، يجب على المرء اللجوء إلى الأقفال.
لقد ناقشنا للتو المقايضات التي تُجبر المحركات على إجرائها ، والاختيار بين إنشاء كود سريع باستخدام المترجمين الفوريين وإنشاء كود سريع باستخدام المحولات المُحسّنة. ومع ذلك ، هذه بعيدة كل المشاكل التي تواجهها المحركات. تعد الذاكرة موردًا آخر للنظام عند الاستخدام وعليك اللجوء إلى الحلول الوسط. ولإثبات ذلك ، فكر في برنامج JS بسيط يضيف أرقامًا.
function add(x, y) { return x + y; } add(1, 2);
في ما يلي الرمز الثانوي لوظيفة
add
التي تم إنشاؤها بواسطة مترجم الإشعال في V8:
StackCheck Ldar a1 Add a0, [0] Return
لا يمكنك الدخول في معنى هذا الرمز الفرعي ، في الواقع ، محتوياته ليست ذات أهمية خاصة بالنسبة لنا. الشيء الرئيسي هنا هو أنه يحتوي على أربعة تعليمات فقط.
عندما تكون قطعة من هذا الرمز "ساخنة" ، يتم استخدام TurboFan ، مما يؤدي إلى إنشاء رمز الجهاز المحسن للغاية التالي:
leaq rcx,[rip+0x0] movq rcx,[rcx-0x37] testb [rcx+0xf],0x1 jnz CompileLazyDeoptimizedCode push rbp movq rbp,rsp push rsi push rdi cmpq rsp,[r13+0xe88] jna StackOverflow movq rax,[rbp+0x18] test al,0x1 jnz Deoptimize movq rbx,[rbp+0x10] testb rbx,0x1 jnz Deoptimize movq rdx,rbx shrq rdx, 32 movq rcx,rax shrq rcx, 32 addl rdx,rcx jo Deoptimize shlq rdx, 32 movq rax,rdx movq rsp,rbp pop rbp ret 0x18
كما ترون ، فإن حجم الشفرة ، مقارنة بالمثال أعلاه لأربعة تعليمات ، كبير جدًا. عادةً ما يكون الرمز الثانوي أصغر بكثير من رمز الجهاز ، ولا سيما رمز الجهاز المُحسَّن. من ناحية أخرى ، هناك حاجة إلى مترجم لتنفيذ رمز البايت ، ويمكن تنفيذ التعليمات البرمجية المحسنة مباشرة على المعالج.
هذا هو أحد الأسباب الرئيسية التي تجعل محركات جافا سكريبت لا تعمل على تحسين جميع التعليمات البرمجية على الإطلاق. كما رأينا سابقًا ، يستغرق إنشاء رمز الجهاز المُحسّن وقتًا طويلاً ، علاوة على ذلك ، كما اكتشفنا للتو ، يستغرق تخزين رمز الجهاز المُحسّن مزيدًا من الذاكرة.
استخدام الذاكرة ومستوى التحسيننتيجة لذلك ، يمكننا القول أن السبب وراء امتلاك محركات JS لمستويات مختلفة من التحسين هو المشكلة الأساسية في الاختيار بين التوليد السريع للرموز ، على سبيل المثال ، باستخدام المترجم ، والتوليد السريع للرموز ، والتي يتم تنفيذها عن طريق المترجم الأمثل. إذا تحدثنا عن مستويات تحسين الشفرة المستخدمة في المحركات ، فكلما زاد عدد التحسينات الدقيقة التي يمكن أن تخضع لها الشفرة ، ولكن يتم تحقيق ذلك نظرًا لتعقيد المحركات وبسبب الحمل الإضافي على النظام. بالإضافة إلى ذلك ، يجب ألا ننسى هنا أن مستوى تحسين الشفرة يؤثر على مقدار الذاكرة التي يشغلها هذا الرمز. هذا هو السبب في أن محركات JS تحاول تحسين الوظائف "الساخنة" فقط.
تحسين الوصول إلى خصائص النموذج الأولي للكائن
تعمل محركات JavaScript على تحسين الوصول إلى خصائص الكائن من خلال استخدام ما يسمى نماذج الكائنات (الشكل) وذاكرة التخزين المؤقت المضمنة (ذاكرة التخزين المؤقت المضمنة ، IC). يمكن قراءة تفاصيل هذا في
هذه المادة ، ولكن لوضعه باختصار ، يمكننا القول أن المحرك يخزن شكل الكائن بشكل منفصل عن قيم الكائن.
كائنات لها نفس الشكليجعل استخدام أشكال الكائنات من الممكن إجراء تحسين يسمى التخزين المؤقت المضمن. يسمح لك الاستخدام المشترك لأشكال الكائنات وذاكرة التخزين المؤقت المضمنة بتسريع العمليات المتكررة للوصول إلى خصائص الكائنات ، التي تتم من نفس المكان في الرمز.
تسريع الوصول إلى خاصية الكائنالفئات والنماذج
الآن بعد أن عرفنا كيفية تسريع الوصول إلى خصائص الكائن في JavaScript ، ألق نظرة على أحد ابتكارات JavaScript الحديثة - الفئات. إليك ما يبدو عليه إعلان الفصل:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } }
على الرغم من أنه قد يبدو وكأنه مظهر في JS لمفهوم جديد تمامًا ، إلا أن الفئات هي في الواقع مجرد سكر نحوي لنظام النموذج الأولي لبناء الكائنات ، والتي كانت موجودة دائمًا في JavaScript:
function Bar(x) { this.x = x; } Bar.prototype.getX = function getX() { return this.x; };
هنا نكتب الدالة إلى خاصية
getX
للكائن
getX
. تعمل هذه العملية بنفس الطريقة تمامًا عند إنشاء خصائص أي كائن آخر ، نظرًا لأن النماذج الأولية في JavaScript هي كائنات. في اللغات القائمة على استخدام النماذج الأولية ، مثل جافا سكريبت ، يتم تخزين الطرق التي يمكن لجميع الكائنات من نوع معين مشاركتها في النماذج الأولية ، ويتم تخزين حقول الكائنات الفردية في حالاتها.
دعونا نلقي نظرة على ما يحدث ، إذا جاز التعبير ، خلف الكواليس عندما نقوم بإنشاء مثيل جديد لكائن
Bar
، وتعيينه إلى
foo
المستمر.
const foo = new Bar(true);
بعد تنفيذ مثل هذا الرمز ، سيكون لمثيل الكائن الذي تم إنشاؤه هنا نموذج يحتوي على خاصية واحدة
x
. النموذج الأولي لكائن
foo
هو
Bar.prototype
، الذي ينتمي إلى
Bar
الصنف.
الكائن ونموذجه الأولييحتوي
Bar.prototype
على نموذج خاص به يحتوي على خاصية
getX
مفردة قيمتها هي دالة تقوم ، عند استدعاؤها ، بإرجاع قيمة
this.x
النموذج الأولي
Bar.prototype
هو
Object.prototype
، وهو جزء من اللغة.
Object.prototype
هو العنصر الجذر لشجرة النموذج الأولي ، لذا فإن النموذج الأولي
null
.
الآن دعونا نرى ما يحدث إذا قمت بإنشاء كائن آخر من النوع
Bar
.
عدة أشياء من نفس النوعكما ترون ،
qux
كل من كائن
foo
وكائن
qux
، وهما مثيلات لفئة
Bar
، كما قلنا بالفعل ، نفس شكل الكائن. كلاهما يستخدم نفس النموذج الأولي - كائن
Bar.prototype
.
الوصول إلى خصائص النموذج الأولي
إذن نحن الآن نعرف ما يحدث عندما نعلن عن فئة جديدة وننشئها. وماذا عن الدعوة إلى طريقة الكائن؟ خذ بعين الاعتبار مقتطف الرمز التالي:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
يمكن فهم استدعاء الأسلوب على أنه عملية تتكون من خطوتين:
const x = foo.getX(); // : const $getX = foo.getX; const x = $getX.call(foo);
في الخطوة الأولى ، يتم تحميل الطريقة ، وهي مجرد خاصية للنموذج الأولي (قيمتها هي الوظيفة). في الخطوة الثانية ، يتم استدعاء دالة مع
this
المجموعة. خذ بعين الاعتبار الخطوة الأولى في تحميل طريقة
getX
من كائن
foo
:
تحميل طريقة getX من كائن fooيحلل المحرك كائن
foo
ويكتشف أنه لا توجد خاصية
getX
في شكل كائن
foo
. هذا يعني أن المحرك يحتاج إلى النظر في سلسلة النموذج للكائن من أجل العثور على هذه الطريقة. يصل المحرك إلى النموذج الأولي
Bar.prototype
وينظر في شكل كائن هذا النموذج الأولي. هناك ، يجد الخاصية المطلوبة في الإزاحة 0. بعد ذلك ، يتم الوصول إلى القيمة المخزنة في هذا الإزاحة في
Bar.prototype
، يتم اكتشاف
JSFunction
getX
هناك - وهذا بالضبط ما نبحث عنه. هذا يكمل البحث عن الطريقة.
مرونة JavaScript تجعل من الممكن تغيير سلاسل النموذج الأولي. على سبيل المثال ، مثل هذا:
const foo = new Bar(true); foo.getX(); // true Object.setPrototypeOf(foo, null); foo.getX(); // Uncaught TypeError: foo.getX is not a function
في هذا المثال ، نسمي طريقة
foo.getX()
مرتين ، ولكن لكل من هذه المكالمات معنى ونتائج مختلفة تمامًا. هذا هو السبب ، على الرغم من أن النماذج الأولية لجافا سكريبت ليست سوى كائنات ، فإن تسريع الوصول إلى خصائص النموذج الأولي أكثر صعوبة لمحركات JS من تسريع الوصول إلى خصائصها الخاصة بالكائنات العادية.
إذا نظرنا إلى برامج واقعية ، فقد تبين أن تحميل خصائص النموذج الأولي عملية شائعة جدًا. يتم تنفيذها في كل مرة يتم استدعاء طريقة.
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const x = foo.getX();
في وقت سابق ، تحدثنا عن كيفية تحسين المحركات لتحميل الخصائص العادية والمخصصة للكائنات من خلال استخدام نماذج الكائنات وذاكرة التخزين المؤقت المضمنة. كيفية تحسين خصائص النموذج الأولي المتكررة لتحميل الكائنات ذات الشكل نفسه؟ أعلاه ، رأينا كيف يتم تحميل الخصائص.
تحميل طريقة getX من كائن fooمن أجل تسريع الوصول إلى الطريقة من خلال المكالمات المتكررة إليها ، في حالتنا ، تحتاج إلى معرفة ما يلي:
- لا يحتوي شكل كائن
foo
على طريقة getX
ولا يتغير. هذا يعني أن كائن foo
لا يتم تعديله عن طريق إضافة خصائص إليه أو حذفها أو تغيير سمات الخصائص. - لا يزال النموذج الأولي
foo
هو النموذج Bar.prototype
الأصلي. هذا يعني أن النموذج الأولي foo
لا يتغير باستخدام طريقة Object.setPrototypeOf()
أو عن طريق تعيين نموذج أولي جديد لخاصية _proto_
الخاصة. - يحتوي النموذج
Bar.prototype
على getX
ولا يتغير. بمعنى ، لا Bar.prototype
تغيير Bar.prototype
عن طريق حذف الخصائص أو إضافتها أو تغيير سماتها.
في الحالة العامة ، هذا يعني أننا بحاجة إلى إجراء فحص واحد للكائن نفسه ، وتحققين من كل نموذج أولي يصل إلى النموذج الأولي الذي يخزن الخاصية التي نبحث عنها. أي أنك تحتاج إلى إجراء عمليات فحص 1 + 2N (حيث N هو عدد النماذج الأولية المختبرة) ، والتي في هذه الحالة لا تبدو سيئة للغاية ، لأن سلسلة النموذج الأولي قصيرة جدًا. ومع ذلك ، غالبًا ما يتعين على المحركات العمل مع سلاسل نموذجية أطول بكثير. هذا ، على سبيل المثال ، نموذجي لعناصر DOM العادية. هنا مثال:
const anchor = document.createElement('a');
هنا لدينا
HTMLAnchorElement
getAttribute()
أسلوب
getAttribute()
. تتضمن سلسلة النموذج الأولي لهذا العنصر البسيط الذي يمثل رابط HTML 6 نماذج أولية! معظم طرق DOM المثيرة ليست في النموذج الأولي الخاص بها
HTMLAnchorElement
. وهي في نماذج أولية تقع أسفل السلسلة.
سلسلة النموذج الأولييمكن العثور على طريقة
getAttribute()
في
Element.prototype
. هذا يعني أنه في كل مرة ، عندما يتم
anchor.getAttribute()
الأسلوب
anchor.getAttribute()
، يضطر المحرك إلى تنفيذ الإجراءات التالية:
- للتحقق من كائن
anchor
نفسه لـ getAttribute
. - التحقق من أن النموذج الأولي للكائن هو
HTMLAnchorElement.prototype
. - معرفة أن
HTMLAnchorElement.prototype
ليس لديه طريقة getAttribute
. - التحقق من أن النموذج الأولي التالي هو
HTMLElement.prototype
. - معرفة أنه لا توجد طريقة ضرورية هنا.
- أخيرًا ، اكتشف أن النموذج الأولي التالي هو
Element.prototype
. - معرفة أن هناك طريقة
getAttribute
.
كما ترى ، يتم إجراء 7 عمليات فحص هنا. نظرًا لأن هذه الشفرة شائعة جدًا في برمجة الويب ، تستخدم المحركات التحسينات لتقليل عدد عمليات التحقق اللازمة لتحميل خصائص النموذج الأولي.
إذا عدنا إلى أحد الأمثلة السابقة ، يمكننا أن نتذكر أنه عندما نسمي طريقة
getX
لكائن
getX
، فإننا نجري 3 عمليات تحقق:
class Bar { constructor(x) { this.x = x; } getX() { return this.x; } } const foo = new Bar(true); const $getX = foo.getX;
لكل كائن موجود في سلسلة النموذج الأولي ، حتى الكائن الذي يحتوي على الخاصية المطلوبة ، نحتاج إلى التحقق من شكل الكائن فقط لمعرفة عدم وجود ما نبحث عنه. سيكون من اللطيف إذا تمكنا من تقليل عدد الشيكات عن طريق تقليل فحص النموذج الأولي للتحقق من وجود أو عدم وجود ما نبحث عنه. هذا ما يفعله المحرك بحركة بسيطة: بدلاً من تخزين رابط النموذج الأولي في المثيل نفسه ، يقوم المحرك بتخزينه في شكل كائن.
تخزين مرجع النموذج الأولييحتوي كل نموذج على رابط لنموذج أولي. وهذا يعني أيضًا أنه كلما تغير النموذج الأولي
foo
، ينتقل المحرك إلى الشكل الجديد للكائن. الآن نحن بحاجة فقط للتحقق من شكل الكائن لوجود خاصية فيه والعناية بحماية رابط النموذج الأولي.
بفضل هذا النهج ، يمكننا تقليل عدد عمليات التحقق من 1 + 2N إلى 1 + N ، مما سيسرع الوصول إلى خصائص النماذج الأولية. ومع ذلك ، لا تزال هذه العمليات كثيفة الاستخدام للموارد ، نظرًا لوجود علاقة خطية بين عددها وطول سلسلة النموذج الأولي. نفذت المحركات آليات مختلفة تهدف إلى التأكد من أن عدد الفحوصات لا يعتمد على طول سلسلة النموذج الأولي ، معبرًا عنه بالثابت. هذا صحيح بشكل خاص في المواقف التي يتم فيها تحميل نفس الخاصية عدة مرات.
خاصية ValidityCell
يشير V8 إلى أشكال النماذج على وجه التحديد للغرض أعلاه. يحتوي كل نموذج أولي على شكل فريد لا تتم مشاركته مع كائنات أخرى (على وجه الخصوص ، مع نماذج أولية أخرى) ، ولكل من نماذج الكائن النموذجي خاصية
ValidityCell
المرتبطة بها.
خاصية ValidityCellتم إعلان هذه الخاصية غير صالحة عند تغيير النموذج الأولي المرتبط بالنموذج ، أو أي نموذج أولي مغطي. النظر في هذه الآلية بمزيد من التفصيل.
من أجل تسريع العمليات المتتالية لخصائص التحميل من النماذج الأولية ، يستخدم V8 ذاكرة تخزين مؤقت مضمنة تحتوي على أربعة حقول:
ValidityCell
،
Prototype
،
Shape
،
Offset
.
حقول ذاكرة التخزين المؤقت المضمنةأثناء "تدفئة" ذاكرة التخزين المؤقت المضمنة في المرة الأولى التي يتم فيها تشغيل الكود ، يتذكر V8 الإزاحة التي تم العثور فيها على الخاصية في النموذج الأولي ، والنموذج الأولي الذي تم العثور على الخاصية فيه (في هذا المثال ،
Bar.prototype
) ، شكل الكائن (
foo
في هذه الحالة) ، بالإضافة إلى ذلك ، رابط إلى معلمة
ValidityCell
الحالية للنموذج الأولي المباشر ، وهو ارتباط يكون في شكل كائن (في هذه الحالة ، يكون أيضًا
Bar.prototype
).
في المرة التالية التي تصل فيها إلى ذاكرة التخزين المؤقت المضمنة ، سيحتاج المحرك إلى التحقق من شكل الكائن و
ValidityCell
. إذا كانت
ValidityCell
لا تزال صالحة ، فيمكن للمحرك الاستفادة مباشرة من الإزاحة المحفوظة مسبقًا في النموذج الأولي دون إجراء عمليات بحث إضافية.
عندما يتغير النموذج الأولي ، يتم إنشاء نموذج جديد ، ويتم إعلان خاصية
ValidityCell
السابقة غير صالحة. ونتيجة لذلك ، في المرة التالية التي تحاول فيها الوصول إلى ذاكرة التخزين المؤقت المضمنة ، لا تجلب أي فائدة ، مما يؤدي إلى ضعف الأداء.
عواقب تغيير النموذج الأوليإذا عدنا إلى المثال باستخدام عنصر DOM ، فإن هذا يعني أن أي تغيير ، على سبيل المثال ، في النموذج الأولي لـ
Object.prototype
، لن يؤدي فقط إلى إبطال ذاكرة التخزين المؤقت المضمنة لـ
Object.prototype
نفسه ، ولكن أيضًا لأي نماذج أولية تقع تحته في سلسلة النموذج الأولي بما في ذلك
EventTarget.prototype
و
Node.prototype
و
Element.prototype
وما إلى ذلك ، وصولاً إلى
HTMLAnchorElement.prototype
.
آثار تغيير Object.prototypeفي الواقع ، تعديل
Object.prototype
أثناء تنفيذ التعليمات البرمجية يعني إحداث ضرر خطير في الأداء. لا تفعل هذا.
ندرس ما سبق مع مثال. افترض أن لدينا فئة
Bar
، ووظيفة
loadX
، التي تستدعي طريقة الكائنات التي تم إنشاؤها من فئة
Bar
. نسمي وظيفة
loadX
عدة مرات ،
loadX
مثيلات من نفس الفئة.
function loadX(bar) { return bar.getX(); // IC 'getX' `Bar`. } loadX(new Bar(true)); loadX(new Bar(false)); // IC `loadX` `ValidityCell` // `Bar.prototype`. Object.prototype.newMethod = y => y; // `ValidityCell` IC `loadX` // `Object.prototype` .
تشير ذاكرة التخزين المؤقت
loadX
في
loadX
الآن إلى
ValidityCell
لـ
Bar.prototype
. , ,
Object.prototype
— JavaScript,
ValidityCell
, - , .
Object.prototype
— , - , . , :
Object.prototype.foo = function() { };
Object.prototype
, - , . , . - , . , « », , .
, , . .
Object.prototype
, , - .
, — , JS- - , . . , , . , , , .
الملخص
, JS- , , , -,
ValidityCell
, . JavaScript, , ( , , , ).
أعزائي القراء! , - , JS, ?
