سؤال

كنت أتساءل، هل هناك فرق في الأداء بين استخدام الوظائف المسماة والوظائف المجهولة في Javascript؟

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = function() {
        // do something
    };
}

ضد

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

الأول أكثر ترتيبًا لأنه لا يسبب فوضى في التعليمات البرمجية الخاصة بك بوظائف نادرًا ما تستخدم، ولكن هل يهم أنك تعيد الإعلان عن هذه الوظيفة عدة مرات؟

هل كانت مفيدة؟

المحلول

مشكلة الأداء هنا هي تكلفة إنشاء كائن دالة جديد عند كل تكرار للحلقة وليس حقيقة أنك تستخدم دالة مجهولة:

for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = function() {
        // do something    
    };
}

أنت تقوم بإنشاء آلاف الكائنات الوظيفية المميزة على الرغم من أنها تحتوي على نفس نص التعليمات البرمجية ولا يوجد ارتباط بالنطاق المعجمي (إنهاء).من ناحية أخرى، يبدو ما يلي أسرع، لأنه يقوم ببساطة بتعيين نفس مرجع الدالة إلى عناصر المصفوفة في جميع أنحاء الحلقة:

function myEventHandler() {
    // do something
}

for (var i = 0; i < 1000; ++i) {
    myObjects[i].onMyEvent = myEventHandler;
}

إذا كنت ستنشئ دالة مجهولة قبل الدخول إلى الحلقة، فقم فقط بتعيين مراجع لها لعناصر المصفوفة أثناء وجودك داخل الحلقة، وستجد أنه لا يوجد أي اختلاف في الأداء أو الدلالات على الإطلاق عند مقارنتها بإصدار الوظيفة المسماة:

var handler = function() {
    // do something    
};
for (var i = 0; i < 1000; ++i) {    
    myObjects[i].onMyEvent = handler;
}

باختصار، لا توجد تكلفة أداء يمكن ملاحظتها لاستخدام وظائف مجهولة المصدر.

وعلى الجانب قد يبدو من الأعلى أنه لا يوجد فرق بين:

function myEventHandler() { /* ... */ }

و:

var myEventHandler = function() { /* ... */ }

السابق هو أ إعلان الوظيفة في حين أن الأخير عبارة عن إسناد متغير لوظيفة مجهولة.على الرغم من أنها قد تبدو أن لها نفس التأثير، إلا أن JavaScript تتعامل معها بشكل مختلف قليلاً.لفهم الفرق أنصح بقراءة "غموض إعلان وظيفة جافا سكريبت”.

سيتم تحديد وقت التنفيذ الفعلي لأي نهج إلى حد كبير من خلال تنفيذ المتصفح للمترجم ووقت التشغيل.للحصول على مقارنة كاملة لأداء المتصفح الحديث، قم بزيارة موقع JS Perf

نصائح أخرى

إليك رمز الاختبار الخاص بي:

var dummyVar;
function test1() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = myFunc;
    }
}

function test2() {
    for (var i = 0; i < 1000000; ++i) {
        dummyVar = function() {
            var x = 0;
            x++;
        };
    }
}

function myFunc() {
    var x = 0;
    x++;
}

document.onclick = function() {
    var start = new Date();
    test1();
    var mid = new Date();
    test2();
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "\n Test 2: " + (end - mid));
}

النتائج:
الاختبار 1:اختبار 142ms 2:1983 مللي ثانية

يبدو أن محرك JS لا يتعرف على أنها نفس الوظيفة في Test2 ويقوم بتجميعها في كل مرة.

كمبدأ عام للتصميم، يجب عليك تجنب تطبيق نفس الكود عدة مرات.بدلاً من ذلك، يجب عليك رفع التعليمات البرمجية الشائعة إلى دالة وتنفيذ هذه الوظيفة (عامة ومختبرة جيدًا وسهلة التعديل) من أماكن متعددة.

إذا كنت (على عكس ما تستنتجه من سؤالك) تعلن عن الوظيفة الداخلية مرة واحدة وتستخدم هذا الرمز مرة واحدة (وليس لديك أي شيء آخر مماثل في برنامجك) فستكون وظيفة مجهولة من المحتمل (هذا تخمين يا جماعة) يتم التعامل معه بنفس الطريقة من قبل المترجم كدالة عادية مسماة.

إنها ميزة مفيدة جدًا في حالات محددة، ولكن لا ينبغي استخدامها في العديد من المواقف.

لا أتوقع فرقًا كبيرًا ولكن إذا كان هناك فرق فمن المحتمل أن يختلف حسب محرك البرمجة النصية أو المتصفح.

إذا وجدت أن الكود أسهل في التعامل معه، فلن يمثل الأداء مشكلة إلا إذا كنت تتوقع استدعاء الوظيفة ملايين المرات.

الكائنات المجهولة أسرع من الكائنات المسماة.لكن استدعاء المزيد من الوظائف يعد أكثر تكلفة، وإلى درجة تتفوق على أي توفير قد تحصل عليه من استخدام وظائف مجهولة.تضيف كل وظيفة يتم استدعاؤها إلى مكدس الاستدعاءات، مما يؤدي إلى مقدار صغير ولكن غير تافه من الحمل.

ولكن ما لم تكن تكتب إجراءات تشفير/فك تشفير أو شيئًا مشابهًا حساسًا للأداء، فكما لاحظ كثيرون آخرون، من الأفضل دائمًا تحسين التعليمات البرمجية الأنيقة وسهلة القراءة بدلاً من التعليمات البرمجية السريعة.

بافتراض أنك تكتب تعليمات برمجية جيدة التصميم، فإن مشكلات السرعة يجب أن تكون من مسؤولية أولئك الذين يكتبون المترجمين الفوريين/المترجمين.

حيث يمكننا أن يكون لنا تأثير على الأداء في عملية إعلان الوظائف.فيما يلي معيار للإعلان عن الوظائف داخل سياق وظيفة أخرى أو خارجها:

http://jsperf.com/function-context-benchmark

في Chrome تكون العملية أسرع إذا أعلنا عن الوظيفة في الخارج، أما في Firefox فالأمر عكس ذلك.

في مثال آخر، نرى أنه إذا لم تكن الوظيفة الداخلية وظيفة خالصة، فسيكون أداءها ناقصًا أيضًا في Firefox:http://jsperf.com/function-context-benchmark-3

ما سيجعل حلقتك أسرع بالتأكيد عبر مجموعة متنوعة من المتصفحات، وخاصة متصفحات IE، هو التكرار على النحو التالي:

for (var i = 0, iLength = imgs.length; i < iLength; i++)
{
   // do something
}

لقد قمت بإدخال 1000 بشكل تعسفي في حالة الحلقة، ولكنك ستحصل على الانجراف الخاص بي إذا كنت تريد الاطلاع على جميع العناصر الموجودة في المصفوفة.

سيكون المرجع دائمًا أبطأ من الشيء الذي يشير إليه.فكر في الأمر بهذه الطريقة - لنفترض أنك تريد طباعة نتيجة إضافة 1 + 1.وهو الأمر الأكثر منطقية:

alert(1 + 1);

أو

a = 1;
b = 1;
alert(a + b);

أدرك أن هذه طريقة مبسطة حقًا للنظر إلى الأمر، لكنها توضيحية، أليس كذلك؟استخدم مرجعًا فقط إذا كان سيتم استخدامه عدة مرات - على سبيل المثال، أي من هذه الأمثلة أكثر منطقية:

$(a.button1).click(function(){alert('you clicked ' + this);});
$(a.button2).click(function(){alert('you clicked ' + this);});

أو

function buttonClickHandler(){alert('you clicked ' + this);}
$(a.button1).click(buttonClickHandler);
$(a.button2).click(buttonClickHandler);

والثاني هو التدريب الأفضل، حتى لو كان يحتوي على المزيد من الأسطر.نأمل أن يكون كل هذا مفيدًا.(ولم يؤدي بناء جملة jquery إلى طرد أي شخص)

@nicf

(أتمنى لو كان لدي مندوب للتعليق فقط، لكنني وجدت هذا الموقع للتو)

نقطتي هي أن هناك ارتباكًا هنا بين الوظائف المسماة/المجهولة وحالة استخدام التنفيذ + التجميع في التكرار.كما أوضحت، فإن الفرق بين anon+named لا يكاد يذكر في حد ذاته - أقول إن حالة الاستخدام الخاطئة.

يبدو الأمر واضحًا بالنسبة لي، ولكن إذا لم يكن الأمر كذلك فأعتقد أن أفضل نصيحة هي "لا تفعل أشياء غبية" (من بينها النقل المستمر للكتل + إنشاء الكائن لحالة الاستخدام هذه) وإذا لم تكن متأكدًا، فاختبر!

نعم!الوظائف المجهولة أسرع من الوظائف العادية.ربما إذا كانت السرعة هي ذات أهمية قصوى...أكثر أهمية من إعادة استخدام التعليمات البرمجية، ثم فكر في استخدام وظائف مجهولة المصدر.

هناك مقالة جيدة حقًا حول تحسين وظائف جافا سكريبت والمجهولة هنا:

http://dev.opera.com/articles/view/efficiency-javascript/?page=2

@nicf

هذا اختبار سخيف إلى حد ما، على الرغم من أنك تقارن التنفيذ والتجميع الوقت هناك والذي من الواضح أنه سيكلف الطريقة 1 (يجمع N مرات، اعتمادًا على محرك JS) مع الطريقة 2 (يجمع مرة واحدة).لا أستطيع أن أتخيل مطور JS الذي سيجتاز كود كتابة الاختبار بهذه الطريقة.

النهج الأكثر واقعية بكثير هو التعيين المجهول، حيث إنك في الواقع تستخدم لطريقة document.onclick الخاصة بك وهي أشبه بما يلي، والتي في الواقع تفضل بشكل طفيف الطريقة anon.

باستخدام إطار اختبار مماثل لك:


function test(m)
{
    for (var i = 0; i < 1000000; ++i) 
    {
        m();
    }
}

function named() {var x = 0; x++;}

var test1 = named;

var test2 = function() {var x = 0; x++;}

document.onclick = function() {
    var start = new Date();
    test(test1);
    var mid = new Date();
    test(test2);
    var end = new Date();
    alert ("Test 1: " + (mid - start) + "ms\n Test 2: " + (end - mid) + "ms");
}

كما هو موضح في التعليقات على إجابة @nickf :الجواب على

يتم إنشاء وظيفة مرة واحدة بشكل أسرع من إنشائها مليون مرة

هو ببساطة نعم.ولكن كما يُظهر أداء JS الخاص به، فإنه ليس أبطأ بمعامل مليون، مما يدل على أنه يصبح أسرع بمرور الوقت.

السؤال الأكثر إثارة للاهتمام بالنسبة لي هو:

كيف يتكرر إنشاء + تشغيل قارن لإنشاء مرة واحدة + متكررة يجري.

إذا كانت الدالة تنفذ عملية حسابية معقدة، فمن المرجح أن يكون الوقت اللازم لإنشاء كائن الدالة ضئيلًا.ولكن ماذا عن الرأس الزائد يخلق في الحالات التي يجري سريع؟على سبيل المثال:

// Variant 1: create once
function adder(a, b) {
  return a + b;
}
for (var i = 0; i < 100000; ++i) {
  var x = adder(412, 123);
}

// Variant 2: repeated creation via function statement
for (var i = 0; i < 100000; ++i) {
  function adder(a, b) {
    return a + b;
  }
  var x = adder(412, 123);
}

// Variant 3: repeated creation via function expression
for (var i = 0; i < 100000; ++i) {
  var x = (function(a, b) { return a + b; })(412, 123);
}

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

من المحتمل أن يصبح الفرق مهمًا فقط في الحالات التي يكون فيها إنشاء كائن الوظيفة معقدًا، مع الحفاظ على وقت تشغيل ضئيل، على سبيل المثال، إذا تم تغليف نص الوظيفة بالكامل في ملف if (unlikelyCondition) { ... }.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top