هناك نوعان من خصائص الكائن.
النوع الأول هو خصائص البيانات . نحن نعرف بالفعل كيفية العمل معهم. جميع الخصائص التي كنا نستخدمها حتى الآن كانت خصائص بيانات.
النوع الثاني من الممتلكات هو شيء جديد. إنها خاصية الوصول . إنها في الأساس وظائف يتم تنفيذها عند الحصول على قيمة وتعيينها، ولكنها تبدو كخصائص عادية بالنسبة إلى رمز خارجي.
يتم تمثيل خصائص الولوج بطرق "getter" و"setter". في كائن حرفي يتم الإشارة إليهم بواسطة get
و set
:
دع الكائن = { الحصول على اسم الدعامة () { // getter، الكود الذي تم تنفيذه عند الحصول على obj.propName }, تعيين اسم الدعامة (القيمة) { // setter، الكود الذي تم تنفيذه عند الإعداد obj.propName = value } };
يعمل getter عند قراءة obj.propName
، ويعمل setter عندما يتم تعيينه.
على سبيل المثال، لدينا كائن user
name
surname
:
السماح للمستخدم = { الاسم: "جون"، اللقب: "سميث" };
نريد الآن إضافة خاصية fullName
، والتي يجب أن تكون "John Smith"
. بالطبع، لا نريد نسخ ولصق المعلومات الموجودة، حتى نتمكن من تنفيذها كمدخل:
السماح للمستخدم = { الاسم: "جون"، اللقب: "سميث"، الحصول على الاسم الكامل () { إرجاع `${this.name} ${this.surname}`; } }; تنبيه (user.fullName)؛ // جون سميث
من الخارج، تبدو الخاصية الملحقة وكأنها خاصية عادية. هذه هي فكرة خصائص الوصول. نحن لا نستدعي user.fullName
كدالة، بل نقرأها بشكل طبيعي: يعمل المُحضر خلف الكواليس.
اعتبارًا من الآن، يحتوي fullName
على حرف getter فقط. إذا حاولنا تعيين user.fullName=
، فسيكون هناك خطأ:
السماح للمستخدم = { الحصول على الاسم الكامل () { إرجاع `...`؛ } }; user.fullName = "اختبار"; // خطأ (الخاصية تحتوي على حرف getter فقط)
دعونا نصلح الأمر عن طريق إضافة أداة ضبط لـ user.fullName
:
السماح للمستخدم = { الاسم: "جون"، اللقب: "سميث"، الحصول على الاسم الكامل () { إرجاع `${this.name} ${this.surname}`; }, تعيين الاسم الكامل (القيمة) { [this.name, this.surname] = value.split(" "); } }; يتم تنفيذ // set fullName بالقيمة المحددة. user.fullName = "أليس كوبر"; تنبيه (اسم المستخدم) ؛ // أليس تنبيه (user.surname)؛ // كوبر
ونتيجة لذلك، أصبح لدينا خاصية "افتراضية" fullName
. وهي قابلة للقراءة والكتابة.
تختلف واصفات خصائص الوصول عن تلك الخاصة بخصائص البيانات.
بالنسبة لخصائص الوصول، لا توجد value
أو writable
، ولكن بدلاً من ذلك هناك وظائف get
و set
.
وهذا يعني أن واصف الوصول قد يحتوي على:
get
- دالة بدون وسيطات، تعمل عند قراءة خاصية ما،
set
- دالة ذات وسيطة واحدة، يتم استدعاؤها عند تعيين الخاصية،
enumerable
- نفس الشيء بالنسبة لخصائص البيانات،
configurable
- كما هو الحال بالنسبة لخصائص البيانات.
على سبيل المثال، لإنشاء fullName
للموصل باستخدام defineProperty
، يمكننا تمرير واصف باستخدام get
و set
:
السماح للمستخدم = { الاسم: "جون"، اللقب: "سميث" }; Object.defineProperty(user, 'fullName', { يحصل() { إرجاع `${this.name} ${this.surname}`; }, مجموعة (القيمة) { [this.name, this.surname] = value.split(" "); } }); تنبيه (user.fullName)؛ // جون سميث لـ (السماح بإدخال المستخدم) تنبيه (مفتاح) ؛ // الاسم واللقب
يرجى ملاحظة أن الخاصية يمكن أن تكون إما أداة وصول (تحتوي على أساليب get/set
) أو خاصية بيانات (تحتوي على value
)، وليس كليهما.
إذا حاولنا توفير كل من get
و value
في نفس الواصف، فسيكون هناك خطأ:
// خطأ: واصف الخاصية غير صالح. Object.defineProperty({}, 'prop', { يحصل() { العودة 1 }, القيمة: 2 });
يمكن استخدام Getters/setters كمغلفات لقيم الخصائص "الحقيقية" للحصول على مزيد من التحكم في العمليات معهم.
على سبيل المثال، إذا أردنا منع استخدام الأسماء القصيرة جدًا user
، فيمكننا الحصول على name
محدد والاحتفاظ بالقيمة في خاصية منفصلة _name
:
السماح للمستخدم = { الحصول على الاسم () { إرجاع هذا._name; }, تعيين الاسم (القيمة) { إذا (القيمة.الطول <4) { تنبيه("الاسم قصير جدًا، يحتاج إلى 4 أحرف على الأقل"); يعود؛ } this._name = value; } }; user.name = "بيت"; تنبيه (اسم المستخدم) ؛ // بيت اسم المستخدم = ""; // الاسم قصير جدًا...
لذلك، يتم تخزين الاسم في خاصية _name
، ويتم الوصول إليه عبر getter وsetter.
من الناحية الفنية، الكود الخارجي قادر على الوصول إلى الاسم مباشرة باستخدام user._name
. ولكن هناك اتفاقية معروفة على نطاق واسع مفادها أن الخصائص التي تبدأ بالشرطة السفلية "_"
هي خصائص داخلية ولا ينبغي لمسها من خارج الكائن.
أحد الاستخدامات الرائعة لأدوات الوصول هو أنها تسمح بالتحكم في خاصية البيانات "العادية" في أي وقت عن طريق استبدالها بأداة getter وsetter وتعديل سلوكها.
تخيل أننا بدأنا في تنفيذ كائنات المستخدم باستخدام خصائص البيانات name
age
:
وظيفة المستخدم (الاسم والعمر) { this.name = name; this.age = age; } Let john = new User("John", 25); تنبيه(john.age); // 25
…ولكن عاجلاً أم آجلاً، قد تتغير الأمور. بدلاً من age
قد نقرر تخزين birthday
، لأنه أكثر دقة وملاءمة:
وظيفة المستخدم (الاسم، تاريخ الميلاد) { this.name = name; this.birthday = عيد ميلاد؛ } Let john = new User("John", new Date(1992, 6, 1));
الآن ماذا تفعل بالكود القديم الذي لا يزال يستخدم خاصية age
؟
يمكننا أن نحاول العثور على جميع هذه الأماكن وإصلاحها، ولكن هذا يستغرق وقتًا وقد يكون من الصعب القيام به إذا تم استخدام هذا الرمز من قبل العديد من الأشخاص الآخرين. وإلى جانب ذلك، age
هو شيء جميل بالنسبة user
، أليس كذلك؟
دعونا نحتفظ بها.
إضافة getter age
يحل المشكلة:
وظيفة المستخدم (الاسم، تاريخ الميلاد) { this.name = name; this.birthday = عيد ميلاد؛ // يتم حساب العمر من التاريخ الحالي وعيد الميلاد Object.defineProperty(هذا، "age"، { يحصل() { دع todayYear = new Date().getFullYear(); العودة اليوم سنة - this.birthday.getFullYear(); } }); } Let john = new User("John", new Date(1992, 6, 1)); تنبيه (جون. عيد الميلاد)؛ // عيد ميلاد متاح تنبيه(john.age); // ... وكذلك العمر
الآن يعمل الكود القديم أيضًا ولدينا خاصية إضافية رائعة.