ملاحظة المترجم: أنا المرة الأولى التي أترجم فيها لغة أجنبية ، وكلماتي غامضة حتماً ، لكنني حاولت قصارى جهدي للتعبير عن نية المؤلف الأصلية ولم يكن لدي الكثير من البولندية. النقد والتصحيح موضع ترحيب. بالإضافة إلى ذلك ، هذه المقالة طويلة ولديها كمية كبيرة من المعلومات ، والتي قد يكون من الصعب هضمها. يرجى ترك رسالة لمناقشة التفاصيل. تركز هذه المقالة بشكل أساسي على تحسين أداء V8 ، وبعض المحتوى لا ينطبق على جميع محركات JS. أخيرًا ، يرجى الإشارة إلى المصدر عند إعادة الطباعة :)
====================================================================
تم تصميم العديد من محركات JavaScript ، مثل محرك V8 من Google (المستخدمة بواسطة Chrome و Node) ، خصيصًا لتطبيقات JavaScript الكبيرة التي تتطلب تنفيذًا سريعًا. إذا كنت مطورًا وكنت قلقًا بشأن استخدام الذاكرة وأداء الصفحة ، فيجب أن تفهم كيف يعمل محرك JavaScript في متصفحك. سواء كان V8 أو Spidermonkey (Firefox) Carakan (Opera) أو شقرا (IE) أو غيرها من المحركات ، يمكن أن يساعدك ذلك في تحسين تطبيقك بشكل أفضل . هذا لا يعني أنه يجب عليك تحسين متصفح أو محرك معين ، ولا تفعل ذلك أبدًا.
ومع ذلك ، يجب أن تسأل نفسك بعض الأسئلة:
يشبه موقع التحميل السريع سيارة رياضية سريعة تتطلب قطع غيار مخصصة خصيصًا. مصدر الصورة: dhybridcars.
هناك بعض المزالق الشائعة عند كتابة رمز عالي الأداء ، وفي هذه المقالة سنعرض بعض الطرق المثبتة وأفضل لكتابة الكود.
إذا لم يكن لديك فهم عميق لمحركات JS ، فلا توجد مشكلة في تطوير تطبيق ويب كبير ، تمامًا مثل الشخص الذي يمكنه قيادة السيارة فقط لم يشاهد الغطاء ولكن ليس المحرك داخل الغطاء. بالنظر إلى أن Chrome هو خيار متصفحي الأول ، فلنتحدث عن محرك JavaScript الخاص به. يتكون V8 من الأجزاء الأساسية التالية:
مجموعة Garbage هي شكل من أشكال إدارة الذاكرة ، وهو في الواقع مفهوم لجامع ، يحاول إعادة تدوير الذاكرة التي تشغلها كائنات لم تعد تستخدم. في لغة جمع القمامة مثل JavaScript ، لن تتم مسح الكائنات التي لا تزال تتم الرجوع إليها في التطبيق.
القضاء يدويًا على مراجع الكائنات ليست ضرورية في معظم الحالات. سيعمل كل شيء على ما يرام ببساطة عن طريق وضع المتغيرات حيث تكون هناك حاجة إليها (من الناحية المثالية ، كما هو الحال في النطاق المحلي قدر الإمكان ، أي الوظيفة التي يتم استخدامها بدلاً من الطبقة الخارجية للوظيفة).
يحاول جامع القمامة إعادة تدوير الذاكرة. مصدر الصورة: Valtteri Mäki.
في JavaScript ، من المستحيل إجبار جمع القمامة. يجب ألا تفعل ذلك لأن عملية جمع القمامة يتم التحكم فيها في وقت التشغيل وهي تعرف ما هو أفضل وقت للتنظيف.
هناك العديد من المناقشات حول إعادة تدوير ذاكرة JavaScript على الإنترنت حول الكلمة الرئيسية حذف. على الرغم من أنه يمكن استخدامه لحذف السمات (المفاتيح) في الكائنات (MAP) ، إلا أن بعض المطورين يعتقدون أنه يمكن استخدامه لإجبار "إزالة الخطر". يوصى بتجنب استخدام الحذف كلما كان ذلك ممكنًا. في المثال أدناه delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x: 1} ؛ حذف الثور. // الثور الحقيقي ؛ // غير محددستجد بسهولة إزالة مرجعية في مكتبات JS الشهيرة - هذا هادف لغوي. تجدر الإشارة هنا إلى تجنب تعديل بنية الكائن "الساخن" في وقت التشغيل. يمكن لمحرك JavaScript اكتشاف مثل هذه الكائنات "الساخنة" ومحاولة تحسينها. إذا لم يتغير بنية الكائن بشكل كبير خلال دورة الحياة ، فسيكون المحرك أسهل في تحسين الكائن ، وستؤدي عملية الحذف فعليًا إلى تشغيل هذا التغيير الهيكلي الأكبر ، والذي لا يفضي إلى تحسين المحرك.
هناك أيضًا سوء فهم حول كيفية عمل NULL. لا يؤدي تعيين مرجع كائن إلى NULL إلى جعل الكائن "فارغًا" ، فهو يضع مرجعه فقط إلى فارغ. استخدام OX = NULL أفضل من استخدام الحذف ، ولكن قد لا يكون ضروريًا.
var o = {x: 1} ؛ o = null ؛ o ؛ // nullo.x // typeerrorإذا كان هذا المرجع هو آخر إشارة إلى الكائن الحالي ، فسيتم جمع الكائن. إذا لم يكن هذا المرجع هو آخر إشارة إلى الكائن الحالي ، فسيكون الكائن متاحًا ولن يتم جمع القمامة.
تجدر الإشارة أيضًا إلى أن المتغيرات العالمية لا يتم تنظيفها بواسطة جامع القمامة خلال دورة حياة الصفحة. بغض النظر عن المدة التي تنفتح فيها الصفحة ، ستكون المتغيرات في نطاق الكائن العالمي موجودة دائمًا عند تشغيل JavaScript.
var myglobalnamespace = {} ؛لن يتم تنظيف الكائنات العالمية إلا عند تحديث الصفحة أو التنقل إلى صفحة أخرى أو إغلاق علامة التبويب أو الخروج من المتصفح. سيتم تنظيف المتغيرات في نطاق الوظيفة عندما تكون خارج نطاقها ، أي عند خروج الوظيفة ، لا توجد مراجع ، وسيتم تنظيف هذه المتغيرات.
من أجل أن يقوم جامع القمامة بجمع أكبر عدد ممكن من الكائنات قدر الإمكان ، لا تحتفظ بكائنات لم تعد تستخدم . إليك بعض الأشياء التي يجب تذكرها:
بعد ذلك ، دعنا نتحدث عن الوظائف. كما قلنا بالفعل ، تعمل جمع القمامة من خلال إعادة تدوير كتل الذاكرة (الكائنات) التي لم تعد متاحة. لتوضيح هذا بشكل أفضل ، إليك بعض الأمثلة.
دالة foo () {var bar = new bargeObject () ؛ bar.somecall () ؛}عند إرجاع FOO ، سيتم إعادة تدوير الكائن الذي يشير إليه الشريط تلقائيًا بواسطة جامع القمامة لأنه لا يحتوي على مراجع موجودة.
يقارن:
دالة foo () {var bar = new bargeObject () ؛ bar.somecall () ؛ شريط الإرجاع ؛} // في مكان ما Elsevar b = foo () ؛الآن لدينا مرجع يشير إلى كائن الشريط ، بحيث تستمر دورة حياة كائن الشريط من المكالمة إلى Foo حتى يحدد المتصل متغيرًا آخر B (أو B خارج النطاق).
عندما ترى وظيفة ، إرجاع وظيفة داخلية ، والتي ستخرج من الوصول إلى النطاق ، حتى بعد تنفيذ الوظيفة الخارجية. هذا إغلاق أساسي - تعبير عن المتغيرات التي يمكن تعيينها في سياق محدد. على سبيل المثال:
وظيفة SUM (x) {function sumit (y) {return x + y ؛ } ؛ return sumit ؛} // usagevar suma = sum (4) ؛ var sumb = suma (3) ؛ console.log (sumb) ؛ // إرجاع 7لا يمكن إعادة تدوير كائن الوظيفة (SUMIT) الذي تم إنشاؤه في سياق المكالمة. يشار إليه من قبل المتغير العالمي (SUMA) ويمكن استدعاؤه من خلال SUMA (N).
دعونا نلقي نظرة على مثال آخر ، أين يمكننا الوصول إلى اللائق المتغير؟
var a = function () {var largestr = new array (1000000). join ('x') ؛ الإرجاع وظيفة () {return largestr ؛ } ؛} () ؛نعم ، يمكننا الوصول إلى Largestr من خلال A () ، لذلك لا يتم إعادة تدويرها. ماذا عن ما يلي؟
var a = function () {var smallstr = 'x' ؛ var largestr = new array (1000000). join ('x') ؛ وظيفة الإرجاع (n) {return smallstr ؛ } ؛} () ؛لم يعد بإمكاننا الوصول إلى Largestr ، فهو بالفعل مرشح لجمع القمامة. [ملاحظة المترجم: لأن Largestr لم يعد لديه مراجع خارجية]
أحد أسوأ الأماكن لتسرب الذاكرة في الحلقة ، أو في setTimeOut ()/setInterval () ، ولكن هذا أمر شائع جدًا. فكر في الأمثلة التالية:
var myobj = {callmemaybe: function () {var myref = this ؛ var val = setTimeOut (function () {console.log ('الوقت ينفد!') ؛ myref.callmemaybe () ؛} ، 1000) ؛ }} ؛ إذا قمنا بتشغيل myobj.callmemaybe () ؛ لبدء المؤقت ، يمكننا أن نرى أن وحدة التحكم تطرح "الوقت ينفد!" كل ثانية. إذا تم تشغيل myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
تجدر الإشارة أيضًا إلى أن المراجع في مكالمات SetTimeOut/SetInterval (مثل الوظائف) ستحتاج إلى تنفيذ وإكمالها قبل جمع القمامة.
لا تحسن الرمز أبدًا حتى تحتاج إليه حقًا. يمكنك الآن رؤية بعض المعايير التي تظهر أن N أكثر تحسينًا في V8 من M ، ولكن إذا قمت باختباره في رمز الوحدة النمطية أو التطبيق ، فستجد أن هذه التحسينات أصغر بكثير مما تتوقع.
من الأفضل أن تفعل شيئًا أكثر من فعل ذلك كثيرًا. مصدر الصورة: تيم شيرمان مطاردة.
على سبيل المثال ، نريد إنشاء مثل هذه الوحدة:
هناك العديد من العوامل المختلفة في هذه المشكلة ، على الرغم من أنه من السهل أيضًا حلها. كيف نقوم بتخزين البيانات ، وكيف نقوم برسم الجداول بكفاءة وإلحاقها إلى DOM ، وكيف نتعامل بشكل أفضل مع أحداث الجدول؟
يتمثل النهج الأولي (الساذج) في مواجهة هذه المشكلات في استخدام الكائنات لتخزين البيانات ووضعها في صفيف ، واستخدام jQuery لاجتياز البيانات لرسم جدول وإلحاقها إلى DOM ، وأخيراً استخدام الحدث ملزمة لسلوك النقر الذي نتوقعه.
ملاحظة: هذا ليس ما يجب عليك فعله
var modulea = function () {return {data: dataArrayObject ، init: function () {this.addtable () ؛ this.addevents () ؛ } ، addtable: function () {for (var i = 0 ؛ i <rows ؛ i ++) {$ tr = $ ('<tr> </r>') ؛ لـ (var j = 0 ؛ j <this.data.length ؛ j ++) {$ tr.append ('<td>' + this.data [j] ['id'] + '</td>') ؛ } $ tr.appendto ($ tbody) ؛ }} ، addevents: function () {$ ('table td'). on ('click' ، function () {$ (this) .toggleclass ('Active') ؛}) ؛ }} ؛} () ؛هذا الرمز ببساطة وفعالية يكمل المهمة.
ولكن في هذه الحالة ، فإن البيانات التي نعبرها هي مجرد معرف خاصية رقمية كان ينبغي تخزينها ببساطة في الصفيف. ومن المثير للاهتمام ، أنه من الأفضل استخدام أساليب DocumentFragment وطرق DOM المحلية مباشرة من إنشاء الجداول باستخدام jQuery (بهذه الطريقة) ، وبالطبع ، فإن وكيل الأحداث له أداء أعلى من ربط كل TD وحده.
لاحظ أنه على الرغم من أن jQuery يستخدم DocumentFragment داخليًا ، في مثالنا ، فإن إلحاق الكود بإلحاح حلقة وتتضمن هذه المكالمات بعض المعرفة الأخرى ، وبالتالي فإن تأثير التحسين هنا ليس جيدًا جدًا. نأمل ألا تكون هذه نقطة ألم ، ولكن تأكد من القيام بوضع معيار للتأكد من أن الرمز الخاص بك على ما يرام.
على سبيل المثال ، تجلب الممارسات المذكورة أعلاه تحسينات الأداء (المطلوبة). Proxying الأحداث هو تحسن في الربط البسيط ، كما يساعد DocumentFragment الاختياري أيضًا.
var moduled = function () {return {data: dataArray ، init: function () {this.addtable () ؛ this.addevents () ؛ } ، addtable: function () {var td ، tr ؛ var fragment = document.createdocumentFragment () ؛ var fragment2 = document.createdocumentFragment () ؛ لـ (var i = 0 ؛ i <rows ؛ i ++) {tr = document.createElement ('tr') ؛ لـ (var j = 0 ؛ j <this.data.length ؛ j ++) {td = document.createElement ('td') ؛ td.appendchild (document.createTextNode (this.data [j])) ؛ Frag2.AppendChild (TD) ؛ } tr.appendchild (frag2) ؛ Frag.AppendChild (TR) ؛ } tbody.appendChild (frag) ؛ } ، addevents: function () {$ ('table'). on ('click' ، 'td' ، function () {$ (this) .toggleclass ('active') ؛}) ؛ }} ؛} () ؛دعنا نلقي نظرة على طرق أخرى لتحسين الأداء. قد تكون قد قرأت أن استخدام وضع النموذج الأولي أفضل من وضع الوحدة النمطية ، أو سمعت أن استخدام أطر عمل قالب JS أفضل. في بعض الأحيان يكون هذا صحيحًا ، ولكن يتم استخدامه لجعل الكود أكثر قابلية للقراءة. بالمناسبة ، هناك مسبق! دعونا نرى كيف يعمل في الممارسة؟
moduleg = function () {} ؛ moduleg.prototype.data = dataArray ؛ moduleg.prototype.init = function () {this.addtable () ؛ this.addevents () ؛} ؛ moduleg.prototype.addtable = function () {var template = _.template ($ ('#template'). text ()) ؛ var html = template ({'data': this.data}) ؛ $ tbody.append (html) ؛} ؛ moduleg.prototype.اتضح أن تحسينات الأداء التي جلبت في هذا الموقف لا تذكر. لا يقدم اختيار القوالب والنماذج الأولية أي شيء آخر. وهذا يعني أن الأداء ليس هو السبب في أن المطورين يستخدمونها ، وقابلية القراءة ، ونموذج الميراث والقدرة على الصيانة التي يتم تقديمها إلى الكود هي الأسباب الحقيقية.
تشمل المشكلات الأكثر تعقيدًا في رسم الصور بكفاءة على قماش ومعالجة بيانات البكسل مع أو بدون صفائف من الأنواع.
قبل استخدام بعض الطرق لتطبيقك الخاص ، تأكد من معرفة المزيد حول قياس هذه الحلول. ربما لا يزال شخص ما يتذكر الإطلاق النار والامتدادات اللاحقة لقالب JS. تحتاج إلى معرفة أن القياس غير موجود في التطبيقات الافتراضية التي لا يمكنك رؤيتها ، ولكن يجب أن تختبر التحسينات التي جلبتها رمزك الفعلي.
يتم تقديم نقاط التحسين لكل محرك V8 بالتفصيل خارج نطاق هذه المقالة. بالطبع ، هناك العديد من النصائح الجديرة بالذكر هنا. تذكر هذه النصائح ويمكنك تقليل الكود الذي يعاني من ضعف الأداء.
وظيفة إضافة (x ، y) {return x+y ؛} add (1 ، 2) ؛ إضافة ('a' ، 'b') ؛ إضافة (my_custom_object ، غير محدد) ؛لمزيد من المحتوى ، يرجى الاطلاع على مشاركة دانييل كليفورد على Google I/O. كسر حد سرعة JavaScript مع V8. تحسين V8 - سلسلة تستحق القراءة أيضًا.
لا يوجد سوى فرق رئيسي واحد بين الكائنات والصفائف في JavaScript ، أي خاصية الطول السحري للمصفوفات. إذا كنت تحافظ على هذا العقار بنفسك ، فإن الكائنات والصفائف في V8 أسرع من تلك الموجودة في المصفوفات.
استنساخ الكائن يمثل مشكلة شائعة لمطوري التطبيقات. في حين أن المعايير المختلفة يمكن أن تثبت أن V8 يتعامل مع هذه المشكلة بشكل جيد ، كن حذرا. عادة ما يكون نسخ الأشياء الكبيرة أبطأ - لا تفعل ذلك. يعد For .. في حلقة JS سيئة بشكل خاص لأنها تحتوي على مواصفات شيطانية وقد لا تكون أسرع من أي كائن في أي محرك.
عندما تكون متأكدًا من نسخ الكائنات على مسار رمز الأداء الحاسم ، استخدم صفيفًا أو وظيفة "نسخ نسخ" مخصصة لنسخ كل خاصية بشكل صريح. ربما تكون هذه أسرع طريقة:
دالة استنساخ (أصلي) {this.foo = Original.foo ؛ this.bar = Original.Bar ؛} var copy = new clone (Original) ؛قد تؤدي وظائف التخزين المؤقت عند استخدام وضع الوحدة النمطية إلى تحسينات الأداء. انظر المثال أدناه ، لأنه يخلق دائمًا نسخة جديدة من وظيفة العضو ، قد تكون التغييرات التي تراها أبطأ.
لاحظ أيضًا أن استخدام هذه الطريقة أفضل ، وليس فقط الاعتماد على وضع النموذج الأولي (الذي تم تأكيده بواسطة اختبار JSPERF).
تحسينات الأداء عند استخدام وضع الوحدة النمطية أو وضع النموذج الأولي
هذا اختبار مقارنة للأداء لوضع النموذج الأولي ووضع الوحدة النمطية:
// potortype pattern klass1 = function () {} klass1.prototype.foo = function () {log ('foo') ؛ } klass1.prototype.bar = function () {log ('bar') ؛ } // module pattern klass2 = function () {var foo = function () {log ('foo') ؛ } ، bar = function () {log ('bar') ؛ } ؛ return {foo: foo ، bar: bar}} // نمط الوحدة النمطية مع وظائف مخبأة var foOfunction = function () {log ('foo') ؛ } ؛ var barfunction = function () {log ('bar') ؛ } ؛ klass3 = function () {return {foo: foofunction ، bar: barfunction}} // اختبارات التكرار // var var i = 1000 ، objs = [] ؛ بينما (i--) {var o = new Klass1 () objs.push (new Klass1 ()) ؛ O.Bar ؛ o.foo ؛ } // نمط الوحدة النمط var i = 1000 ، objs = [] ؛ بينما (i--) {var o = new Klass1 () objs.push (new Klass1 ()) ؛ O.Bar ؛ o.foo ؛ } // نمط الوحدة النمط var i = 1000 ، objs = [] ؛ بينما (i--) {var o = klass2 () objs.push (klass2 ()) ؛ O.Bar ؛ o.foo ؛ } // نمط الوحدة النمطية مع وظائف مخبأة var i = 1000 ، objs = [] ؛ بينما (i--) {var o = klass3 () objs.push (klass3 ()) ؛ O.Bar ؛ o.foo ؛ } // انظر الاختبار للحصول على التفاصيل الكاملةبعد ذلك ، دعنا نتحدث عن التقنيات المتعلقة بالمصفوفات. بشكل عام ، لا تحذف عناصر الصفيف ، والتي ستجعل الصفيف الانتقال إلى تمثيل داخلي أبطأ. عندما يصبح الفهرس متفرقًا ، سيحول V8 العنصر إلى نمط قاموس أبطأ.
تعتبر الأصفاد الحرفية مفيدة للغاية ، ويمكن أن تلمح إلى حجم ونوع صفيف VM. وعادة ما يستخدم في المصفوفات بأحجام صغيرة.
// هنا يمكن أن ترى V8 أنك تريد صفيفًا محتويًا على 4 عناصر: var a = [1 ، 2 ، 3 ، 4] ؛ // لا تفعل هذا: a = [] ؛ // هنا v8 لا يعرف شيئًا عن Arrayfor (var i = 1 ؛ i <= 4 ؛ i ++) {a.push (i) ؛}ليس من الجيد بأي حال تخزين بيانات الأنواع المختلطة (مثل الأرقام والسلاسل ، غير المحددة ، الحقيقية/خطأ) في صفيف. على سبيل المثال var arr = [1 ، "1" ، غير محدد ، صحيح ، "صحيح"]
اختبار أداء الاستدلال نوع
كما رأينا ، صفائف الأعداد الصحيحة هي الأسرع.
عند استخدام المصفوفات المتفرقة ، كن حذرًا في الوصول إلى العناصر سيكون أبطأ بكثير من المصفوفات الكاملة. لأن V8 لن يخصص مساحة كاملة إلى صفيف يستخدم جزءًا من المساحة فقط. بدلاً من ذلك ، تتم إدارتها في قاموس ، وتوفير كل من المساحة ولكن قضاء بعض الوقت للوصول.
اختبار المصفوفات المتفرقة والصفائف الكاملة
لا تقم بتخصيص المصفوفات الكبيرة المسبقة (مثل العناصر التي يزيد حجمها عن 64 كيلو بايت) ، وحجمها الأقصى ، ولكن يجب تخصيصه ديناميكيًا. قبل اختبار أدائنا في هذه المقالة ، تذكر أن هذا ينطبق فقط على بعض محركات JavaScript.
يتم اختبار الحرفية الفارغة والصفائف المسبقة مسبقًا في متصفحات مختلفة
Nitro (Safari) أكثر فائدة للمصفوفات المخصصة مسبقًا. في محركات أخرى (V8 ، SpidermonKey) ، فإن التخصيص المسبق غير فعال.
اختبار صفيف preallocated
]
في عالم تطبيقات الويب ، السرعة هي كل شيء. لا يريد أي مستخدم استخدام تطبيق جدول يستغرق ثوانٍ لحساب العدد الإجمالي للعمود أو لتلخيص المعلومات. هذا سبب مهم لضغط كل جزء من الأداء في الكود الخاص بك.
مصدر الصورة: Per Olof Forsberg.
يعد فهم وتحسين أداء تطبيقك مفيدًا للغاية ، لكنه صعب أيضًا. نوصي بالخطوات التالية لحل نقاط ألم الأداء:
يمكن أن تساعدك بعض الأدوات والتقنيات الموصى بها أدناه.
هناك العديد من الطرق لتشغيل معيار لمقتطفات رمز JavaScript لاختبار أدائها - الافتراض العام هو أن المعيار يقارن الطوابع الزمنية. يشير فريق JSPERF إلى هذا النمط ويستخدم في مجموعة Sunspider و Kraken:
var totaltime ، ابدأ = تاريخ جديد ، التكرارات = 1000 ؛ بينما (التكرار-) {// code snippet يذهب هنا} // TotalTime → عدد الملايين المأخوذة // لتنفيذ قصاصة الكود 1000 timestotaltime = تاريخ جديد-ابدأ ؛هنا ، يتم وضع الرمز الذي سيتم اختباره في حلقة وتشغيل عدد محدد من المرات (على سبيل المثال ، 6 مرات). بعد ذلك ، يتم طرح تاريخ البدء من تاريخ الانتهاء ، والوقت الذي استغرقته لأداء العملية في الحلقة مشتقة.
ومع ذلك ، فإن هذا المعايير يقوم بأشياء بسيطة للغاية ، خاصة إذا كنت ترغب في تشغيل معايير على متصفحات وبيئات متعددة. جامع القمامة نفسه له تأثير معين على النتائج. حتى إذا كنت تستخدم حلًا مثل Window.performance ، فيجب أخذ هذه العيوب في الاعتبار.
بغض النظر عما إذا كنت تقوم بتشغيل الجزء القياسي فقط من الكود ، فاكتب مجموعة اختبار أو رمز المكتبة القياسية ، فإن معايير JavaScript هي في الواقع أكثر مما تعتقد. لمزيد من معايير الدليل التفصيلية ، أوصي بشدة بقراءة معايير JavaScript التي توفرها Mathias Bynens و John-David Dalton.
تتمتع أدوات مطور Chrome بدعم جيد لتحليلات JavaScript. يمكنك استخدام هذه الميزة للكشف عن الوظائف التي تستغرق معظم الوقت حتى تتمكن من تحسينها. هذا أمر مهم ، حتى التغييرات الصغيرة في الكود يمكن أن يكون لها تأثير كبير على الأداء العام.
لوحة تحليل أدوات مطور Chrome
تبدأ عملية التحليل في الحصول على خط أساس أداء التعليمات البرمجية ثم يظهره في شكل جدول زمني. سيخبرنا هذا المدة التي سيستغرقها الرمز لتشغيلها. تمنحنا علامة التبويب ملفات التعريف وجهة نظر أفضل حول ما يجري في التطبيق. تُظهر ملفات تحليل وحدة المعالجة المركزية JavaScript مقدار وقت وحدة المعالجة المركزية المستخدمة في الكود الخاص بنا ، وملفات تحليل محدد CSS تُظهر مقدار الوقت الذي يتم إنفاقه في معالجة المحددات ، وتظهر لقطات الكومة مقدار الذاكرة المستخدمة في كائناتنا.
من خلال هذه الأدوات ، يمكننا فصل وضبط وإعادة تحليل لقياس ما إذا كانت تحسينات الأداء الوظيفية أو التشغيلية فعالة بالفعل.
تعرض علامة تبويب الملف الشخصي معلومات أداء الرمز.
مقدمة جيدة للتحليل ، اقرأ التنميط JavaScript من Zack Grossbart مع أدوات مطور Chrome.
نصيحة: من الناحية المثالية ، إذا كنت ترغب في التأكد من أن تحليلك لا يتأثر بأي تطبيقات أو امتدادات مثبتة ، فيمكنك استخدام علامة- --user-data-dir <empty_directory> لبدء تشغيل الكروم. في معظم الحالات ، يجب أن يكون اختبار تحسين الطريقة هذا كافياً ، ولكنه يتطلب أيضًا المزيد من الوقت لك. هذا ما يمكن أن يساعده شعار V8.
داخل Google ، يتم استخدام أدوات مطور Chrome على نطاق واسع من قبل فرق مثل Gmail للمساعدة في اكتشاف تسرب الذاكرة واستكشافها.
إحصائيات الذاكرة في أدوات مطور Chrome
تعتبر الذاكرة استخدام الذاكرة الخاصة ، وحجم كومة JavaScript ، وعدد عقد DOM ، وتنظيف التخزين ، وعدادات الاستماع الأحداث وجامعي القمامة الذي يهتم به فريقنا. أوصى قراءة تقنية Loreena Lee "3 لقطة". تتمثل النقطة الرئيسية لهذه التقنية في تسجيل بعض السلوك في التطبيق الخاص بك ، وجمع القمامة ، والتحقق مما إذا كان قد تمت استعادة عدد عقد DOM إلى خط الأساس المتوقع ، ثم تحليل لقطات من أكوام الثلاثة لتحديد ما إذا كان هناك تسرب ذاكرة.
إن إدارة الذاكرة لتطبيقات الصفحات الواحدة (مثل AngularJS ، العمود الفقري ، EMBER) مهمة للغاية ، فهي لا تعمل أبدًا على تحديث الصفحة. هذا يعني أن تسرب الذاكرة قد تكون واضحة تمامًا. تطبيقات صفحة واحدة على محطات المحمول مليئة بالمزالق لأن الجهاز يحتوي على ذاكرة محدودة ويقوم بتشغيل تطبيقات مثل عملاء البريد الإلكتروني أو الشبكات الاجتماعية لفترة طويلة. كلما زادت القدرة ، أثقل المسؤولية.
هناك العديد من الطرق لحل هذه المشكلة. في العمود الفقري ، تأكد من استخدام Dispose () للتعامل مع المشاهدات والمراجع القديمة (متوفرة حاليًا في العمود الفقري (الحافة). تتم إضافة هذه الوظيفة مؤخرًا ، وإزالة المعالج المضافة إلى "كائن" في العرض ، وسيتم تمرير مستمع الحدث من خلال النموذج أو المجموعة من المعلمة الثالثة (سياق الاتصال). أعلى المستمع لتجنب تسرب الذاكرة عندما يكتشفون أن العناصر تتم إزالة العناصر.
بعض النصائح الحكيمة من ديريك بيلي:
بدلاً من فهم كيفية عمل الأحداث والمراجع ، اتبع القواعد القياسية لإدارة الذاكرة في JavaScript. إذا كنت ترغب في تحميل البيانات في مجموعة من العمود الفقري المليء بكائنات المستخدم ، فأنت تريد مسح المجموعة بحيث لم تعد تشغل الذاكرة ، فكل الإشارات إلى التجميع والمراجع إلى الكائنات في المجموعة مطلوبة. بمجرد أن تكون المرجع المستخدم واضحًا ، يتم إعادة تدوير المورد. هذه هي قواعد جمع القمامة JavaScript القياسية.
في المقالة ، يغطي Derick العديد من عيوب الذاكرة الشائعة عند استخدام backbone.js وكيفية حل هذه المشكلات.
يستحق البرنامج التعليمي الخاص بتسربات ذاكرة تصحيح الأخطاء في العقدة التي كتبها Felix Geisendörfer القراءة ، خاصةً عندما يشكل جزءًا من مكدس سبا أوسع.
عندما يعيد المستعرض إعادة عرض عناصر في وثيقة ، يجب إعادة حسابها ومواقفهم وهندستها ، والتي نسميها REFOW. يقوم Respowlow بمنح عمليات المستخدمين في المتصفح ، لذلك من المفيد للغاية أن نفهم أن تحسين وقت التراجع قد تم تحسينه.
تراجع الرسم البياني وقت
يجب أن تؤدي إلى إعادة السحب أو إعادة رسمها على دفعات ، ولكن استخدام هذه الأساليب في الاعتدال. من المهم أيضًا محاولة عدم التعامل مع DOM. يمكنك استخدام DocumentFragment ، كائن مستند خفيف الوزن. يمكنك استخدامه كوسيلة لاستخراج جزء من شجرة المستندات ، أو إنشاء مستند جديد "جزء". بدلاً من إضافة عقد DOM باستمرار ، من الأفضل إجراء عمليات إدراج DOM مرة واحدة فقط بعد استخدام جزء المستند لتجنب الإرهاق المفرط.
على سبيل المثال ، نكتب وظيفة لإضافة 20 divs إلى عنصر. إذا قمت ببساطة بإلحاق Div بالعنصر في كل مرة ، فسيؤدي ذلك إلى تشغيل 20 إنشارًا.
وظيفة addDivs (element) {var div ؛ لـ (var i = 0 ؛ i <20 ؛ i ++) {div = distr.createElement ('div') ؛ div.innerhtml = 'Heya!' ؛ element.appendchild (div) ؛ }}لحل هذه المشكلة ، يمكننا استخدام DocumentFragment بدلاً من ذلك ، يمكننا إضافة Div جديد إليها في وقت واحد. إن إضافة DocumentFragment إلى DOM بعد الانتهاء لن يؤدي إلا إلى إبطال مرة واحدة.
وظيفة addDivs (element) {var div ؛ // ينشئ مستند وثيقة فارغة جديدة. var fragment = document.createdocumentFragment () ؛ لـ (var i = 0 ؛ i <20 ؛ i ++) {div = distr.createElement ('a') ؛ div.innerhtml = 'Heya!' ؛ Fragment.AppendChild (div) ؛ } element.appendchild (جزء) ؛}انظر اجعل الويب أسرع ، تحسين ذاكرة JavaScript ، وإيجاد تسرب الذاكرة.
للمساعدة في اكتشاف تسربات ذاكرة JavaScript ، قام مطورو Google (Marja Hölttä و Jochen Eisinger) بتطوير أداة تعمل بالاقتران مع أدوات مطور Chrome لاسترداد لقطات من الكومة واكتشاف الأشياء التي تسبب تسرب الذاكرة.
أداة اكتشاف تسرب الذاكرة JavaScript
هناك مقالة كاملة حول كيفية استخدام هذه الأداة. يوصى بالذهاب إلى صفحة مشروع كاشف تسرب الذاكرة لنفسك.
إذا كنت تريد أن تعرف لماذا لم يتم دمج هذه الأدوات في أدوات التطوير الخاصة بنا ، فهناك سببان. تم تصميمه في الأصل لمساعدتنا على التقاط بعض سيناريوهات الذاكرة المحددة في مكتبة الإغلاق ، والتي تعد أكثر ملاءمة كأداة خارجية.
يدعم Chrome تمرير بعض الأعلام مباشرة إلى V8 للحصول على نتائج إخراج تحسين المحرك أكثر تفصيلاً. على سبيل المثال ، يمكن أن يتتبع ذلك تحسين V8:
"/التطبيقات/Google Chrome/Google Chrome"-JS-Flags = "-Trace-Opt-Trace-Deopt"
يمكن لمستخدمي Windows تشغيل chrome.exe js-flags = "trace-opt trace-deopt"
عند تطوير تطبيق ، يمكن استخدام شعار V8 أدناه.
يستخدم البرنامج النصي لتجهيز V8 * (العلامة النجمية) لتحديد الوظائف المحسّنة والاستخدامات ~ (متوجة) لتمثيل وظائف غير متوقفة.
إذا كنت مهتمًا بمعرفة المزيد عن شعار V8 وكيفية عمل V8 الداخلي ، فمن المستحسن بشدة قراءة منشور Vyacheslav Egorov الممتاز على V8 Internals.
الوقت الدقيق العالي (HRT) عبارة عن واجهة زمنية عالية الدقة على مستوى الفرع لا توفر أي تأثير على وقت النظام وتعديلات المستخدم. يمكن اعتباره طريقة قياس أكثر دقة من التاريخ والتاريخ الجديد. الآن (). هذا يساعدنا كثيرًا في كتابة المعايير.
يوفر الوقت عالي الدقة (HRT) دقة الوقت الفرعي للوقت الفرعي
يتم استخدام HRT حاليًا في Chrome (إصدار مستقر) في Window.performance.webkitnow () ، ولكن يتم التخلص من البادئة في كروم كناري ، مما يجعل من الممكن الاتصال من خلال window.performance.now (). نشر بول الأيرلندي المزيد عن HRT في HTML5Rocks.
الآن بعد أن عرفنا الوقت الدقيق الحالي ، هل هناك واجهة برمجة تطبيقات يمكنها قياس أداء الصفحة بدقة؟ Well, now there is a Navigation Timing API that provides an easy way to get accurate and detailed time measurement records when web pages are loaded and presented to users. You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
Remembering these points can ensure that you have obtained most of the information in this article. آمل أن يكون ذلك مفيدًا لك!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani