يتم إنشاء الكائنات عادةً لتمثيل كيانات العالم الحقيقي، مثل المستخدمين والأوامر وما إلى ذلك:
السماح للمستخدم = { الاسم: "جون"، العمر: 30 };
وفي العالم الحقيقي، يمكن للمستخدم التصرف : اختيار شيء ما من عربة التسوق، تسجيل الدخول، تسجيل الخروج وما إلى ذلك.
يتم تمثيل الإجراءات في JavaScript بواسطة الوظائف الموجودة في الخصائص.
في البداية، دعونا نعلم user
أن يقول مرحبًا:
السماح للمستخدم = { الاسم: "جون"، العمر: 30 }; user.sayHi = وظيفة () { تنبيه("مرحبا!"); }; user.sayHi(); // مرحبًا!
لقد استخدمنا هنا للتو تعبير دالة لإنشاء دالة وتعيينها للخاصية user.sayHi
للكائن.
ثم يمكننا أن نسميها user.sayHi()
. يمكن للمستخدم الآن التحدث!
تسمى الوظيفة التي تعد خاصية لكائن ما بالطريقة الخاصة به.
لذا، لدينا هنا طريقة sayHi
user
الكائن.
بالطبع، يمكننا استخدام دالة معلنة مسبقًا كطريقة، مثل هذا:
السماح للمستخدم = { // ... }; // أولاً أعلن الدالة sayHi() { تنبيه("مرحبا!"); } // ثم أضف كطريقة user.sayHi = sayHi; user.sayHi(); // مرحبًا!
البرمجة الشيئية
عندما نكتب الكود الخاص بنا باستخدام كائنات لتمثيل الكيانات، فهذا يسمى البرمجة الموجهة للكائنات، باختصار: "OOP".
OOP هو شيء كبير، وهو علم مثير للاهتمام في حد ذاته. كيفية اختيار الكيانات المناسبة؟ كيف تنظم التفاعل بينهما؟ هذه هي الهندسة المعمارية، وهناك كتب رائعة حول هذا الموضوع، مثل "أنماط التصميم: عناصر البرامج الموجهة للكائنات القابلة لإعادة الاستخدام" بقلم E. Gamma، R. Helm، R. Johnson، J. Vissides أو "التحليل والتصميم الموجه للكائنات باستخدام "التطبيقات" بقلم ج. بوتش، وأكثر من ذلك.
يوجد بناء جملة أقصر للطرق في كائن حرفي:
// هذه الكائنات تفعل الشيء نفسه المستخدم = { قل مرحبا: وظيفة () { تنبيه("مرحبا"); } }; // الطريقة المختصرة تبدو أفضل، أليس كذلك؟ المستخدم = { sayHi() { // مثل "sayHi: function(){...}" تنبيه("مرحبا"); } };
كما هو موضح، يمكننا حذف "function"
ونكتب فقط sayHi()
.
لقول الحقيقة، الرموز ليست متطابقة تماما. هناك اختلافات دقيقة تتعلق بوراثة الكائنات (سيتم تغطيتها لاحقًا)، لكنها لا تهم في الوقت الحالي. في جميع الحالات تقريبًا، يُفضل استخدام الصيغة الأقصر.
من الشائع أن تحتاج طريقة الكائن إلى الوصول إلى المعلومات المخزنة في الكائن للقيام بعملها.
على سبيل المثال، قد يحتاج الكود الموجود داخل user.sayHi()
إلى اسم user
.
للوصول إلى الكائن، يمكن للأسلوب استخدام الكلمة الأساسية this
.
قيمة this
هي الكائن "قبل النقطة"، وهو الكائن المستخدم لاستدعاء الطريقة.
على سبيل المثال:
السماح للمستخدم = { الاسم: "جون"، العمر: 30, قل مرحبا () { // "هذا" هو "الكائن الحالي" تنبيه(هذا.اسم); } }; user.sayHi(); // جون
هنا أثناء تنفيذ user.sayHi()
، ستكون قيمة this
هي user
.
من الناحية الفنية، من الممكن أيضًا الوصول إلى الكائن بدون this
، من خلال الرجوع إليه عبر المتغير الخارجي:
السماح للمستخدم = { الاسم: "جون"، العمر: 30, قل مرحبا () { تنبيه (اسم المستخدم) ؛ // "المستخدم" بدلاً من "هذا" } };
…ولكن مثل هذا الكود لا يمكن الاعتماد عليه. إذا قررنا نسخ user
إلى متغير آخر، على سبيل المثال admin = user
واستبدال user
بشيء آخر، فسوف يصل إلى الكائن الخطأ.
وهذا موضح أدناه:
السماح للمستخدم = { الاسم: "جون"، العمر: 30, قل مرحبا () { تنبيه (اسم المستخدم) ؛ // يؤدي إلى خطأ } }; دع المشرف = المستخدم؛ المستخدم = فارغ؛ // الكتابة فوق لجعل الأمور واضحة admin.sayHi(); // TypeError: لا يمكن قراءة خاصية "اسم" فارغة
إذا استخدمنا this.name
بدلاً من user.name
داخل alert
، فسيعمل الكود.
في JavaScript، تعمل الكلمة الأساسية this
على عكس معظم لغات البرمجة الأخرى. يمكن استخدامه في أي دالة، حتى لو لم تكن طريقة لكائن ما.
لا يوجد خطأ في بناء الجملة في المثال التالي:
الدالة sayHi() { تنبيه(هذا.اسم); }
يتم تقييم قيمة this
أثناء وقت التشغيل، اعتمادًا على السياق.
على سبيل المثال، هنا يتم تعيين نفس الوظيفة إلى كائنين مختلفين ولها "هذا" مختلف في الاستدعاءات:
السماح للمستخدم = { الاسم: "جون" }؛ دع المشرف = { الاسم: "المسؤول" }; الدالة sayHi() { تنبيه(هذا.اسم); } // استخدم نفس الوظيفة في كائنين user.f = sayHi; admin.f = sayHi; // هذه المكالمات مختلفة this // "هذا" داخل الدالة هو الكائن "قبل النقطة" user.f(); // جون (هذا == المستخدم) admin.f(); // المشرف (هذا == المشرف) المشرف['f'](); // المشرف (نقطة أو أقواس مربعة للوصول إلى الطريقة - لا يهم)
القاعدة بسيطة: إذا تم استدعاء obj.f()
، this
هو obj
أثناء استدعاء f
. لذلك فهو إما user
أو admin
في المثال أعلاه.
الاتصال بدون كائن: this == undefined
يمكننا أيضًا استدعاء الدالة بدون كائن على الإطلاق:
الدالة sayHi() { تنبيه(هذا); } sayHi(); // غير محدد
في هذه الحالة، this
undefined
في الوضع الصارم. إذا حاولنا الوصول إلى this.name
، فسيكون هناك خطأ.
في الوضع غير الصارم، ستكون قيمة this
في مثل هذه الحالة هي الكائن العام ( window
في المتصفح، سنصل إليها لاحقًا في فصل الكائن العام). هذا سلوك تاريخي "use strict"
.
عادةً ما تكون هذه المكالمة خطأً في البرمجة. إذا كان هناك this
داخل دالة، فإنه يتوقع أن يتم استدعاؤه في سياق كائن.
عواقب this
غير منضم
إذا كنت قادمًا من لغة برمجة أخرى، فمن المحتمل أنك معتاد على فكرة "ربط this
"، حيث تشير الأساليب المحددة في كائن دائمًا إلى this
الكائن.
في JavaScript يكون this
"مجانيًا"، ويتم تقييم قيمته في وقت الاتصال ولا يعتمد على مكان الإعلان عن الطريقة، بل على الكائن الموجود "قبل النقطة".
إن مفهوم وقت التشغيل الذي تم تقييمه this
إيجابيات وسلبيات. من ناحية، يمكن إعادة استخدام الوظيفة لكائنات مختلفة. ومن ناحية أخرى، فإن المرونة الأكبر تخلق احتمالات أكبر للأخطاء.
موقفنا هنا ليس الحكم على ما إذا كان قرار تصميم اللغة هذا جيدًا أم سيئًا. سوف نفهم كيفية التعامل معها، وكيفية الحصول على الفوائد وتجنب المشاكل.
وظائف السهم خاصة: ليس لديهم "خاص بهم" this
. إذا أشرنا إلى this
من مثل هذه الوظيفة، فهو مأخوذ من الدالة "العادية" الخارجية.
على سبيل المثال، هنا يستخدم arrow()
this
من التابع user.sayHi()
الخارجي:
السماح للمستخدم = { الاسم الأول: "إيليا"، قل مرحبا () { دع السهم = () => تنبيه (this.firstName)؛ سهم()؛ } }; user.sayHi(); // ايليا
هذه ميزة خاصة لوظائف الأسهم، وهي مفيدة عندما لا نريد فصل this
، بل نريد أخذها من السياق الخارجي. لاحقًا في الفصل الذي تمت إعادة النظر فيه في وظائف الأسهم، سنتعمق أكثر في وظائف الأسهم.
تسمى الوظائف المخزنة في خصائص الكائن "الطرق".
تسمح الطرق للكائنات "بالتصرف" مثل object.doSomething()
.
يمكن للطرق الرجوع إلى الكائن بهذا this
.
يتم تعريف قيمة this
في وقت التشغيل.
عندما يتم الإعلان عن دالة، يجوز لها استخدام this
، لكن this
ليس له قيمة حتى يتم استدعاء الدالة.
يمكن نسخ وظيفة بين الكائنات.
عندما يتم استدعاء دالة في صيغة "الطريقة": object.method()
، فإن قيمة this
أثناء الاستدعاء هي object
.
يرجى ملاحظة أن وظائف السهم خاصة: لا تحتوي على this
. عندما يتم الوصول إلى this
داخل وظيفة السهم، يتم أخذه من الخارج.
الأهمية: 5
هنا تقوم الدالة makeUser
بإرجاع كائن.
ما هي نتيجة الوصول إلى ref
الخاص به؟ لماذا؟
وظيفة MakeUser () { يعود { الاسم: "جون"، المرجع: هذا }; } دع المستخدم = makeUser(); تنبيه (user.ref.name ); // ما هي النتيجة؟
الجواب: خطأ.
جربه:
وظيفة MakeUser () { يعود { الاسم: "جون"، المرجع: هذا }; } دع المستخدم = makeUser(); تنبيه (user.ref.name ); // خطأ: لا يمكن قراءة خاصية "اسم" غير محددة
وذلك لأن القواعد التي تحدد this
لا تنظر إلى تعريف الكائن. فقط لحظة الاتصال هي التي تهم.
هنا قيمة this
داخل makeUser()
undefined
، لأنه يتم استدعاؤها كدالة، وليس كأسلوب مع بناء جملة "نقطة".
قيمة this
هي واحدة للوظيفة بأكملها، ولا تؤثر كتل التعليمات البرمجية والكائنات الحرفية عليها.
لذلك ref: this
في الواقع يأخذ this
الوظيفة الحالية.
يمكننا إعادة كتابة الدالة وإرجاعها this
undefined
:
وظيفة ميك المستخدم () { رد هذا؛ // هذه المرة لا يوجد كائن حرفي } تنبيه (makeUser().name ); // خطأ: لا يمكن قراءة خاصية "اسم" غير محددة
كما ترى فإن نتيجة alert( makeUser().name )
هي نفس نتيجة alert( user.ref.name )
من المثال السابق.
وهنا الحالة المعاكسة:
وظيفة ميك المستخدم () { يعود { الاسم: "جون"، المرجع () { رد هذا؛ } }; } دع المستخدم = makeUser(); تنبيه (user.ref().name ); // جون
الآن يعمل، لأن user.ref()
هو أسلوب. ويتم تعيين قيمة this
على الكائن قبل النقطة .
.
الأهمية: 5
قم بإنشاء calculator
للكائنات بثلاث طرق:
تطالب الدالة read()
بقيمتين وتحفظهما كخصائص كائن بالاسمين a
و b
على التوالي.
تُرجع الدالة sum()
مجموع القيم المحفوظة.
mul()
يضاعف القيم المحفوظة ويعيد النتيجة.
دع الآلة الحاسبة = { // ... الكود الخاص بك ... }; آلة حاسبة. قراءة ()؛ تنبيه (آلة حاسبة. مجموع ())؛ تنبيه(حاسبة.مول());
قم بتشغيل العرض التوضيحي
افتح صندوق الرمل مع الاختبارات.
دع الآلة الحاسبة = { مجموع() { إرجاع this.a + this.b; }, مول () { إرجاع this.a * this.b; }, يقرأ() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; آلة حاسبة. قراءة ()؛ تنبيه (آلة حاسبة. مجموع ())؛ تنبيه(حاسبة.مول());
افتح الحل بالاختبارات في وضع الحماية.
الأهمية: 2
هناك كائن ladder
يسمح لك بالصعود والنزول:
دع السلم = { الخطوة: 0، أعلى() { this.step++; }, تحت() { this.step--; }, showStep: function() { // يُظهر الخطوة الحالية تنبيه (هذه الخطوة)؛ } };
الآن، إذا أردنا إجراء عدة مكالمات متتالية، فيمكننا القيام بذلك على النحو التالي:
Ladder.up(); Ladder.up(); سلم. أسفل ()؛ Ladder.showStep(); // 1 سلم. أسفل ()؛ Ladder.showStep(); // 0
قم بتعديل كود up
و down
و showStep
لجعل المكالمات قابلة للتسلسل، مثل هذا:
سلم.up().up().down().showStep().down().showStep(); // يظهر 1 ثم 0
يُستخدم هذا النهج على نطاق واسع عبر مكتبات JavaScript.
افتح صندوق الرمل مع الاختبارات.
الحل هو إرجاع الكائن نفسه من كل مكالمة.
دع السلم = { الخطوة: 0، أعلى() { this.step++; رد هذا؛ }, تحت() { this.step--; رد هذا؛ }, شوستيب () { تنبيه (هذه الخطوة)؛ رد هذا؛ } }; سلم.up().up().down().showStep().down().showStep(); // يظهر 1 ثم 0
يمكننا أيضًا كتابة مكالمة واحدة في كل سطر. بالنسبة للسلاسل الطويلة فهي أكثر قابلية للقراءة:
سُلُّم .أعلى() .أعلى() .تحت() .showStep() // 1 .تحت() .showStep(); // 0
افتح الحل بالاختبارات في وضع الحماية.