يقدم
في هذا الفصل ، سنشرح استراتيجية تمرير المعلمات لوظيفة الوظائف في ECMASCRIPT.
في علوم الكمبيوتر ، تسمى هذه الاستراتيجية عمومًا "استراتيجية التقييم" (ملاحظة العم: يقول بعض الأشخاص أنها تُرجم إلى استراتيجية التقييم ، بينما يترجمها الآخرون إلى استراتيجية مهمة. بالنظر إلى المحتوى التالي ، أعتقد أنه من المناسب أن نسميها استراتيجية مهمة. على سبيل المثال ، في لغات البرمجة ، تحديد قواعد التقييم أو تعبيرات الحساب. استراتيجية تمرير المعلمات إلى وظيفة هي حالة خاصة.
http://dmitrysoshnikov.com/ecmascript/chapter-8-evaluation-strategy/
سبب كتابة هذا المقال هو أن شخصًا ما في المنتدى طلب شرح بعض الاستراتيجيات لتمرير المعلمات بدقة. لقد قدمنا التعريف المقابل هنا ، على أمل أن يكون مفيدًا للجميع.
يقتنع العديد من المبرمجين أنه في JavaScript (حتى في بعض اللغات الأخرى) ، يتم تمرير الكائنات بالرجوع إليها ، بينما يتم تمرير نوع القيمة الأصلية بالقيم. بالإضافة إلى ذلك ، تتحدث العديد من المقالات عن هذه "الحقيقة" ، لكن الكثير من الناس يفهمون هذا المصطلح حقًا ، وكم عدد الصحيح؟ سنشرح ذلك واحدًا تلو الآخر في هذه المقالة.
النظرية العامة
تجدر الإشارة إلى أنه في نظرية المهمة ، هناك عمومًا استراتيجيتين للتخصيص: صارم - يعني أن المعلمات يتم حسابها قبل إدخال البرنامج ؛ غير مكثف - يعني أن حساب المعلمات يتم حسابه بناءً على متطلبات الحساب (أي ، يعادل تأخير الحساب).
ثم ، هنا نعتبر استراتيجية نقل المعلمات الأساسية ، والتي تعتبر مهمة للغاية من نقطة الانطلاق في ECMASCRIPT. أول شيء يجب ملاحظته هو أن استراتيجيات تمرير المعلمة الصارمة تستخدم في ECMAScript (حتى في لغات أخرى مثل C و Java و Python و Ruby).
بالإضافة إلى ذلك ، فإن ترتيب الحساب للمعلمات التي تم تمريرها مهم أيضًا - في ECMASCRIPT ، ويترك إلى اليمين ، ويمكن أيضًا استخدام الترتيب التمهيدي (من اليمين إلى اليمين) المنفذ بلغات أخرى.
تنقسم استراتيجية الإرسال الصارمة أيضًا إلى العديد من استراتيجيات البذور ، وسنناقش أهم الاستراتيجيات بالتفصيل في هذا الفصل.
لا يتم استخدام جميع الاستراتيجيات التي تمت مناقشتها أدناه في ECMAScript ، لذلك عند مناقشة السلوك المحدد لهذه الاستراتيجيات ، استخدمنا الرمز الزائفة لإظهارها.
تمرير بالقيمة
بالمرور حسب القيمة ، يدرك العديد من المطورين جيدًا أن قيمة المعلمة هي نسخة من قيمة الكائن التي يمررها المتصل. لن يؤثر تغيير قيمة المعلمة داخل الوظيفة على الكائن الخارجي (قيمة المعلمة في الخارج). بشكل عام ، يتم إعادة تخصيص الذاكرة الجديدة (لا نولي اهتمامًا لكيفية تنفيذ الذاكرة المخصصة - إنها أيضًا تخصيص الذاكرة أو التخصيص الديناميكي). قيمة كتلة الذاكرة الجديدة هي نسخة من الكائن الخارجي ، ويتم استخدام قيمته داخل الوظيفة.
نسخة الكود كما يلي:
شريط = 10
الإجراء FOO (Bararg):
Bararg = 20 ؛
نهاية
فو (بار)
// تغيير القيمة داخل FOO لن تؤثر على قيمة الشريط الداخلي
طباعة (شريط) // 10
ومع ذلك ، إذا لم تكن معلمات الوظيفة هي القيمة الأصلية ولكن كائنًا هيكليًا معقدًا ، فسيؤدي ذلك إلى مشكلات رائعة في الأداء. C ++ لديه هذه المشكلة. عند تمرير الهيكل كقيمة في الوظيفة ، فهي نسخة كاملة.
دعنا نعطي مثالًا عامًا ، استخدم استراتيجية المهمة التالية لاختبارها. فكر في وظيفة تقبل 2 معلمات. المعلمة الأولى هي قيمة الكائن ، والثاني هو علامة منطقية لتمييز ما إذا كان الكائن تم تعديله بالكامل (إعادة تعيين الكائن إلى الكائن) ، أو يتم تعديل بعض خصائص الكائن فقط.
نسخة الكود كما يلي:
// ملاحظة: ما يلي كلها رمز زائف ، وليس تنفيذ JS
شريط = {
X: 10 ،
Y: 20
}
الإجراء FOO (Bararg ، isfullchange):
إذا isfullchange:
bararg = {z: 1 ، س: 2}
مخرج
نهاية
bararg.x = 100
bararg.y = 200
نهاية
فو (بار)
// تمرير بالقيمة ، لن يتم تغيير الكائنات الخارجية
print (bar) // {x: 10 ، y: 20}
// قم بتغيير الكائن بالكامل (تعيين قيمة جديدة)
فو (بار ، صحيح)
// لا تغيير سواء
print (bar) // {x: 10 ، y: 20} ، بدلاً من {z: 1 ، س: 2}
تمرير بالرجوع إليه
آخر مرجعية معروفة على حدة ليست نسخة قيمة ، ولكن مرجعًا ضمنيًا للكائن ، مثل العنوان المرجعي المباشر للكائن في الخارج. أي تغييرات على المعلمات داخل الوظيفة تؤثر على قيمة الكائن خارج الوظيفة ، لأن كلاهما يشير إلى نفس الكائن ، أي أن المعلمة تعادل الاسم المستعار للكائن الخارجي.
الكود الكاذب:
نسخة الكود كما يلي:
الإجراء FOO (Bararg ، isfullchange):
إذا isfullchange:
bararg = {z: 1 ، س: 2}
مخرج
نهاية
bararg.x = 100
bararg.y = 200
نهاية
// استخدم نفس الكائن على النحو الوارد أعلاه
شريط = {
X: 10 ،
Y: 20
}
// نتيجة المكالمة بالرجوع إليها على النحو التالي:
فو (بار)
// تم تغيير قيمة خاصية الكائن
print (bar) // {x: 100 ، y: 200}
// إعادة تعيين القيم الجديدة تؤثر أيضًا على الكائن
فو (بار ، صحيح)
// هذا الكائن هو بالفعل كائن جديد
print (bar) // {z: 1 ، q: 2}
يمكن أن تمر هذه الاستراتيجية بشكل أكثر كفاءة كائنات معقدة ، مثل كائنات الهيكل الكبيرة مع دفعات كبيرة من السمات.
اتصل بالمشاركة
يعلم الجميع الاستراتيجيتين أعلاه ، لكنك قد لا تعرف الاستراتيجية التي تريد التحدث عنها هنا (في الواقع ، إنها استراتيجية أكاديمية). ومع ذلك ، سنرى قريبًا أن هذه هي بالضبط الاستراتيجية التي تلعب دورًا رئيسيًا في استراتيجية تسليم المعلمة في ECMASCRIPT.
تحتوي هذه الاستراتيجية أيضًا على بعض المرادفات: "تمرير بواسطة كائن" أو "تمرير بواسطة مشاركة الكائن".
تم اقتراح هذه الاستراتيجية في عام 1974 من قبل باربرا ليسكوف للغة برمجة CLU.
النقطة الرئيسية لهذه الاستراتيجية هي أن الوظيفة تتلقى نسخة (نسخة) من الكائن ، وترتبط نسخة المرجع بالمعلمات الرسمية وقيمها.
لا يمكننا استدعاء المراجع التي تظهر هنا "تمرير بالرجوع" ، لأن المعلمات التي تلقاها الوظيفة ليست مستعملة مباشرة للكائنات ، ولكن نسخة من العنوان المرجعي.
الاختلاف الأكثر أهمية هو أن الوظيفة تعيد تعيين قيم جديدة للمعلمة داخل الكائن الخارجي (كما تم تمرير الحالة بالرجوع في المثال أعلاه) ، ولكن لأن المعلمة هي نسخة عنوان ، نفس الكائن الذي يتم الوصول إليه في الخارج والداخل (على سبيل المثال ، الكائن الخارجي ليس نسخة كاملة كما تريد تمريرها حسب القيمة). سيؤثر تغيير قيمة السمة لكائن المعلمة على الكائن الخارجي.
نسخة الكود كما يلي:
الإجراء FOO (Bararg ، isfullchange):
إذا isfullchange:
bararg = {z: 1 ، س: 2}
مخرج
نهاية
bararg.x = 100
bararg.y = 200
نهاية
// لا يزال يستخدم بنية الكائن هذا
شريط = {
X: 10 ،
Y: 20
}
// تمرير عن طريق المساهمة سيؤثر على الكائن
فو (بار)
// تم تعديل خصائص الكائن
print (bar) // {x: 100 ، y: 200}
// إعادة التعيين لا تعمل
فو (بار ، صحيح)
// لا تزال القيمة أعلاه
print (bar) // {x: 100 ، y: 200}
افتراض هذه المعالجة هو أن الكائنات المستخدمة في معظم اللغات ليست القيم الأصلية.
تمريرة بالمشاركة هي حالة خاصة للمرور بالقيمة
يتم استخدام استراتيجية تقديمها عن طريق المشاركة في العديد من اللغات: Java ، Ecmascript ، Python ، Ruby ، Visual Basic ، إلخ. بالإضافة إلى ذلك ، استخدم مجتمع Python هذا المصطلح ، ويمكن استخدامه بلغات أخرى ، حيث تميل الأسماء الأخرى إلى جعل الناس يشعرون بالارتباك. في معظم الحالات ، كما هو الحال في Java أو Ecmascript أو Visual Basic ، تسمى هذه الاستراتيجية أيضًا بالمرور - المعنى: القيمة الخاصة - نسخة مرجعية (نسخة).
من ناحية ، يشبه هذا - المعلمات التي تم تمريرها إلى الوظيفة هي مجرد اسم للقيمة المرجعية (عنوان مرجع) ولن تؤثر على الكائن الخارجي.
من ناحية أخرى ، تعتبر هذه المصطلحات حقًا تأكل خاطئًا دون الخوض فيها ، حيث تتحدث العديد من المنتديات عن كيفية تمرير الأشياء إلى وظائف JavaScript).
هناك بالفعل قول في النظرية العامة يمر بالقيمة: ولكن في هذا الوقت هذه القيمة هي ما نسميه نسخة العنوان (نسخة) ، لذلك لا يكسر القواعد.
في روبي ، تسمى هذه الاستراتيجية بالمرور. اسمحوا لي أن أقول مرة أخرى: لا يتم تمريرها من حيث نسخة من بنية كبيرة (على سبيل المثال ، وليس بالقيمة) ، ومن ناحية أخرى ، لا نعالج الإشارات إلى الكائن الأصلي ولا يمكننا تعديله ؛ لذلك ، قد يكون مفهوم المصطلحات عبر المدى أكثر إرباكًا.
لا توجد حالة خاصة تم إقرارها بالرجوع إليها من الناحية النظرية مثل حالة خاصة مررت بالقيمة.
ولكن لا يزال من الضروري فهم هذه الاستراتيجيات في التقنيات المذكورة أعلاه (Java ، Ecmascript ، Python ، Ruby ، آخر). في الواقع ، فإن الاستراتيجية التي يستخدمونها هي تمريرها عن طريق المشاركة.
اضغط على المشاركة والمؤشر
بالنسبة إلى с/с ++ ، فإن هذه الاستراتيجية هي نفسها التي تمريرها حسب قيمة المؤشر ، ولكن هناك فرقًا مهمًا - يمكن لهذه الاستراتيجية أن تشرب المؤشرات وتغيير الكائنات تمامًا. ومع ذلك ، بشكل عام ، تخصيص مؤشر قيمة (عنوان) إلى كتلة ذاكرة جديدة (أي ، لا تزال كتلة الذاكرة المشار إليها مسبقًا دون تغيير) ؛ سيؤثر تغيير خصائص الكائن من خلال مؤشر على الكائن الخارجي لـ Adong.
لذلك ، وفئات المؤشر ، يمكننا أن نرى بوضوح أن هذا يتم تمريره حسب قيمة العنوان. في هذه الحالة ، يكون التمرير عن طريق المشاركة مجرد "سكر اصطناعي" ، مثل سلوك مهمة المؤشر (ولكن ليس للإصابة) ، أو تعديل الخصائص مثل المراجع (لا توجد عملية دسمة مطلوبة) ، وأحيانًا يمكن تسميتها "مؤشر آمن".
ومع ذلك ، يحتوي с/с + + أيضًا على سكر نحوي خاص عند الإشارة إلى خصائص الكائنات دون إزالة الأشرطة الواضحة:
نسخة الكود كما يلي:
obj-> x بدلاً من (*obj) .x
يمكن رؤية هذه الأيديولوجية التي ترتبط ارتباطًا وثيقًا بـ C ++ من تنفيذ "المؤشرات الذكية". على سبيل المثال ، في Boost :: shared_ptr ، يتم زيادة تحميل مشغل الواجبات ومشغل النسخ ، ويستخدم عداد مرجع الكائن أيضًا لحذف الكائن من خلال GC. هذا النوع من البيانات يحتوي على اسم مشابه - share_ptr.
تنفيذ ecmascript
الآن نعلم أن سياسة تمرير الكائنات كمعلمات في ECMAScript - تمريرها عن طريق المشاركة: سيؤثر تعديل خصائص المعلمة على الخارج ، في حين أن إعادة تعيين القيمة لن يؤثر على الكائن الخارجي. ومع ذلك ، كما ذكرنا أعلاه ، يطلق عليه مطورو ECMASCRIPT عمومًا: تمرير حسب القيمة ، باستثناء أن القيمة هي نسخة من العنوان المرجعي.
كتب JavaScript Inventor Brendan Ashe أيضًا: ما تم تمريره هو نسخة من المرجع (نسخة من العنوان). إذن ما ذكره الجميع في المنتدى هو أيضًا صحيح بموجب هذا التفسير.
بتعبير أدق ، يمكن فهم هذا السلوك على أنه مهمة بسيطة. يمكننا أن نرى أن الداخلية كائنات مختلفة تمامًا ، ولكن يتم الرجوع إلى نفس القيمة - أي نسخة العنوان.
رمز ecmascript:
نسخة الكود كما يلي:
var foo = {x: 10 ، y: 20} ؛
var bar = foo ؛
ALERT (BAR === FOO) ؛ // حقيقي
bar.x = 100 ؛
bar.y = 200 ؛
تنبيه ([foo.x ، foo.y]) ؛ // [100 ، 200]
أي أن اثنين من المعرفات (ملزمة الاسم) مرتبطة بنفس الكائن في الذاكرة ، وشارك هذا الكائن:
Foo Value: Addr (0xff) => {x: 100 ، y: 200} (العنوان 0xff) <= Bar Value: Addr (0xff)
لإعادة التعيين ، يكون الربط هو معرف كائن جديد (عنوان جديد) دون التأثير على الكائن الذي تم ربطه مسبقًا:
نسخة الكود كما يلي:
BAR = {z: 1 ، Q: 2} ؛
تنبيه ([foo.x ، foo.y]) ؛ // [100 ، 200] لا تغيير
تنبيه ([bar.z ، bar.q]) ؛ // [1 ، 2] ولكن الآن المرجع هو كائن جديد
أي أن Foo و Bar لديها الآن قيم مختلفة وعناوين مختلفة:
نسخة الكود كما يلي:
Foo Value: Addr (0xff) => {x: 100 ، y: 200} (العنوان 0xff)
قيمة الشريط: addr (0xfa) => {z: 1 ، س: 2} (العنوان 0xfa)
اسمحوا لي أن أؤكد مرة أخرى على أن قيمة الكائن المذكور هنا هي العنوان ، وليس بنية الكائن نفسها ، وتعيين المتغير إلى متغير آخر - مرجع إلى القيمة المخصصة. لذلك ، يشير المتغيران إلى نفس عنوان الذاكرة. المهمة التالية هي العنوان الجديد ، وهو حل الربط بعنوان الكائن القديم ثم ربط عنوان الكائن الجديد. هذا هو الفرق الأكثر أهمية بين المرور بالرجوع.
بالإضافة إلى ذلك ، إذا نظرنا فقط إلى مستوى التجريد الذي يوفره معيار ECMA-262 ، فإننا نرى فقط مفهوم "القيمة" في الخوارزمية ، والذي يبرز "القيمة" التي تم تمريرها (يمكن أن تكون القيمة الأصلية أو الكائن) ، ولكن وفقًا لتعريفنا أعلاه ، يمكن أيضًا أن يطلق عليه "تم تمرير القيمة" تمامًا لأن عنوان المرجع هو أيضًا قيمة.
ومع ذلك ، من أجل تجنب سوء الفهم (لماذا يمكن تغيير خصائص الكائنات الخارجية داخل الوظيفة) ، لا تزال تفاصيل مستوى التنفيذ بحاجة إلى النظر فيها هنا - ما نراه يتم تمريره عن طريق المشاركة ، أو بعبارة أخرى - تم تمريره بواسطة مؤشر آمن ، والذي لا يمكن إلغاء تحديده وتغيير الكائن ، ولكن يمكن تعديل قيمة الملكية للكائن.
نسخة مصطلح
دعنا نحدد مصطلح إصدار هذه السياسة في ECMASCRIPT.
يمكن أن يطلق عليه "تمرير حسب القيمة" - القيمة المذكورة هنا هي حالة خاصة ، أي أن القيمة هي نسخة عنوان. من هذا المستوى ، يمكننا أن نقول: يتم تمرير الكائنات في ECMASCRIPT باستثناء الاستثناءات بالقيمة ، وهو في الواقع مستوى تجريد ECMASCRIPT.
أو في هذه الحالة ، يطلق عليه على وجه التحديد "تم تمريره عن طريق المشاركة". من خلال هذا ، يمكنك رؤية الفرق بين التمرير التقليدي بالقيمة والمرجع. يمكن تقسيم هذا الموقف إلى حالتين: 1: يتم تمرير القيمة الأصلية بالقيمة ؛ 2: يتم تمرير الكائن عن طريق المشاركة.
إن عبارة "العمل من خلال المرجع لأنواع" لا علاقة لها بـ ECMASCRIPT ، وهي خاطئة.
ختاماً
آمل أن يساعد هذا المنشور في معرفة المزيد من التفاصيل على مستوى الماكرو والتنفيذ في ECMASCRIPT. كما هو الحال دائمًا ، إذا كانت هناك أي أسئلة ، فلا تتردد في مناقشتها.