بالمعنى الحقيقي ، فإن JavaScript ليس لغة موجهة نحو الكائن ولا توفر طرق الميراث التقليدية ، ولكنها توفر وسيلة للميراث النموذجي ، باستخدام خصائص النموذج الأولي الذي توفره لتحقيق الميراث.
النموذج الأولي وسلسلة النموذج الأولي
قبل الحديث عن ميراث النموذج الأولي ، يجب أن نتحدث عن النماذج الأولية وسلاسل النماذج الأولية أولاً. بعد كل شيء ، هذا هو الأساس لتحقيق ميراث النموذج الأولي.
في javaScript ، تحتوي كل وظيفة على نموذج أولي للنموذج الأولي الذي يشير إلى النموذج الأولي الخاص به ، والكائن الذي تم إنشاؤه بواسطة هذه الوظيفة يحتوي أيضًا على سمة __proto__ تشير إلى هذا النموذج الأولي. النموذج الأولي للوظيفة هو كائن ، لذلك سيكون لهذا الكائن أيضًا __proto__ يشير إلى النموذج الأولي الخاص به ، بحيث يمر طبقة أعمق حسب الطبقة حتى يتراوح النموذج الأولي للكائن ، وبالتالي تشكيل سلسلة النموذج الأولي. يشرح الشكل التالي العلاقة بين النماذج الأولية وسلاسل النموذج الأولي في JavaScript جيدًا.
كل وظيفة هي كائن تم إنشاؤه بواسطة وظيفة الوظيفة ، لذلك كل وظيفة لها أيضًا سمة __proto__ تشير إلى النموذج الأولي لوظيفة الوظيفة. يجب الإشارة هنا إلى أن ما يشكل حقًا سلسلة النموذج الأولي هو سمة __proto__ لكل كائن ، وليس سمة النموذج الأولي للوظيفة ، وهو أمر مهم للغاية.
النموذج الأولي الميراث
الوضع الأساسي
نسخة الكود كما يلي:
var parent = function () {
this.name = 'parent' ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function () {
this.name = 'child' ؛
} ؛
child.prototype = new Parent () ؛
var parent = new parent () ؛
var child = New Child () ؛
console.log (parent.getName ()) ؛ // الوالدين
console.log (child.getName ()) ؛ //طفل
هذه هي أسهل طريقة لتنفيذ ميراث النموذج الأولي ، وتخصيص كائن الفئة الأصل مباشرةً إلى النموذج الأولي لمؤسسة الفئة الفرعية ، بحيث يمكن للكائنات من الفئة الفرعية الوصول إلى الخصائص في الفئة الأصل والنموذج الأولي لمؤسس الفئة الأصل. مخطط ميراث النموذج الأولي لهذه الطريقة هو كما يلي:
مزايا هذه الطريقة واضحة ، التنفيذ بسيط للغاية ولا يتطلب أي عمليات خاصة ؛ العيوب واضحة أيضا. إذا احتاجت الفئة الفرعية إلى تنفيذ نفس إجراءات التهيئة كما في مُنشئ الفئة الأصل ، فيجب عليك تكرار العمليات في الفئة الأصل في مُنشئ الفئة الفرعية:
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
this.name = name || 'طفل' ؛
} ؛
child.prototype = new Parent () ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (parent.getName ()) ؛ // myparent
console.log (child.getName ()) ؛ // mychild
في الموقف أعلاه ، يتطلب تهيئة سمة الاسم فقط. إذا استمرت أعمال التهيئة في الزيادة ، فإن هذه الطريقة غير مريحة للغاية. لذلك ، هناك طريقة لتحسين ما يلي.
مُنشئ الاقتراض
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
child.prototype = new Parent () ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (parent.getName ()) ؛ // myparent
console.log (child.getName ()) ؛ // mychild
تؤدي الطريقة أعلاه نفس عمل التهيئة عن طريق تطبيق المكالمة على مُنشئ الفئة الأصل في مُنشئ الفئة الفرعية ، بحيث بغض النظر عن مقدار عمل التهيئة في الفئة الأصل ، يمكن للفئة الفرعية أداء نفس أعمال التهيئة. ومع ذلك ، هناك مشكلة أخرى في التنفيذ أعلاه. تم تنفيذ مُنشئ الفئة الأصل مرتين ، مرة واحدة في مُنشئ الفئة الفرعية ، ومرة واحدة في النموذج الأولي للفئة الفرعية ، وهذا كثير من الزائدة ، لذلك نحن بحاجة إلى إجراء تحسن:
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
child.prototype = parent.prototype ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (parent.getName ()) ؛ // myparent
console.log (child.getName ()) ؛ // mychild
وبهذه الطريقة ، نحتاج فقط إلى تنفيذ مُنشئ الفئة الأصل مرة واحدة في مُنشئ الفئة الفرعية ، وفي نفس الوقت يمكننا أن نرث الخصائص في النموذج الأولي للصف الأصل. هذا يتماشى أكثر مع النية الأصلية للنموذج الأولي ، وهو وضع المحتوى الذي يجب إعادة استخدامه في النموذج الأولي ، ونحن نورث المحتوى القابل لإعادة الاستخدام فقط في النموذج الأولي. مخطط النموذج الأولي للطريقة أعلاه هو كما يلي:
وضع المنشئ المؤقت (وضع الكأس المقدسة)
لا تزال هناك مشكلة في الإصدار الذي استعار نمط المنشئ أعلاه. يعين مباشرة النموذج الأولي للفئة الأصل إلى النموذج الأولي للنموذج الفرعي ، والذي سيؤدي إلى مشكلة ، أي ، إذا تم تعديل النموذج الأولي للفئة الفرعية ، فإن التعديل سيؤثر أيضًا على النموذج الأولي لفئة الأصل ، ثم يؤثر على كائن الفئة الأصل. هذا بالتأكيد ليس ما يأمل الجميع في رؤيته. لحل هذه المشكلة ، يتوفر نمط مُنشئ مؤقت.
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
var f = new function () {} ؛
f.prototype = parent.prototype ؛
child.prototype = new f () ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (parent.getName ()) ؛ // myparent
console.log (child.getName ()) ؛ // mychild
مخطط ميراث النموذج الأولي لهذه الطريقة هو كما يلي:
من السهل أن نرى أنه من خلال إضافة مُنشئ مؤقت F بين النموذج الأولي للطبقة الأصل والنموذج الأولي للفئة الفرعية ، يتم قطع العلاقة بين النموذج الأولي للفئة الفرعية والنموذج الأصل ، بحيث لا يتأثر النموذج الأولي للطبقة الوالدية عند تعديل النموذج الأولي للفئة الطفل.
طريقتي
ينتهي وضع Holy Grail في "وضع JavaScript" ، ولكن بغض النظر عن الطريقة المذكورة أعلاه ، هناك مشكلة ليس من السهل اكتشافها. يمكنك أن ترى أنني أضفت سمة حرفية لكائن OBJ إلى خاصية النموذج الأولي لـ "الوالد" ، لكنها لم تكن مفيدة أبدًا. دعونا نلقي نظرة على الموقف التالي بناءً على وضع الكأس المقدسة:
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
var f = new function () {} ؛
f.prototype = parent.prototype ؛
child.prototype = new f () ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (child.obj.a) ؛ // 1
console.log (parent.obj.a) ؛ // 1
child.obj.a = 2 ؛
console.log (child.obj.a) ؛ // 2
console.log (parent.obj.a) ؛ // 2
في الحالة المذكورة أعلاه ، عندما أقوم بتعديل الكائن الطفل OBJ.A ، سيتم أيضًا تعديل OBJ.A في النموذج الأولي للطبقة الأصل ، مما سيؤدي إلى نفس المشكلة مثل النموذج الأولي المشترك. يحدث هذا لأنه عند الوصول إلى child.obj.a ، سوف نتبع سلسلة النموذج الأولي ونجد النموذج الأولي للفئة الأصل ، ثم ابحث عن سمة OBJ ، ثم تعديل OBJ.A. لنلقي نظرة على الموقف التالي:
نسخة الكود كما يلي:
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: 1} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
var f = new function () {} ؛
f.prototype = parent.prototype ؛
child.prototype = new f () ؛
var parent = new Parent ('myparent') ؛
var child = طفل جديد ('mychild') ؛
console.log (child.obj.a) ؛ // 1
console.log (parent.obj.a) ؛ // 1
child.obj.a = 2 ؛
console.log (child.obj.a) ؛ // 2
console.log (parent.obj.a) ؛ // 2
هناك مشكلة رئيسية هنا. عندما يصل كائن إلى الخصائص في النموذج الأولي ، تكون الخصائص في النموذج الأولي قراءة فقط للكائن. وهذا يعني ، يمكن للكائن الطفل قراءة كائن OBJ ، ولكن لا يمكن تعديل مرجع كائن OBJ في النموذج الأولي. لذلك ، عندما يعدل الطفل OBJ ، لن يؤثر على OBJ في النموذج الأولي. إنه يضيف فقط سمة OBJ إلى كائنه الخاص ، والكتابة فوق سمة OBJ في النموذج الأولي. عندما يقوم الكائن الطفل بتعديل OBJ.A ، فإنه يقرأ أولاً الإشارة إلى OBJ في النموذج الأولي. في هذا الوقت ، يشير child.obj و parent.prototype.obj إلى نفس الكائن ، لذلك سيؤثر تعديل الطفل لـ OBJ.A على قيمة parent.prototype.obj.a ، وبالتالي يؤثر على كائن الفئة الأصل. يتم تنفيذ طريقة الميراث لنطاق $ في التعشيش في AngularJS من خلال نمذجة الميراث النموذج الأولي في Javasript.
وفقًا للوصف أعلاه ، طالما أن النموذج الأولي الذي تم الوصول إليه في كائن الفئة الفرعية هو نفس النموذج الأولي للفئة الأصل ، فسيحدث الموقف أعلاه. لذلك ، يمكننا نسخ النموذج الأولي للفئة الأصل ثم تعيينه إلى النموذج الأولي للفئة الفرعية. وبهذه الطريقة ، عندما تقوم الفئة الفرعية بتعديل الخصائص الموجودة في النموذج الأولي ، فإنها تعدل فقط نسخة من النموذج الأولي من الفئة الأصل ، ولن تؤثر على النموذج الأولي للصف الأصل. التنفيذ المحدد هو كما يلي:
نسخة الكود كما يلي:
var deepclone = function (المصدر ، الهدف) {
المصدر = المصدر || {} ؛
var tostr = object.prototy.tostring ،
arrstr = '[كائن صفيف]' ؛
لـ (var i in source) {
if (source.hasownproperty (i)) {
var item = source [i] ؛
if (typeof item === 'Object') {
Target [i] = (tostr.apply (item) .ToLowerCase () === Arrstr): []؟ {} ؛
DeepClone (البند ، الهدف [i]) ؛
}آخر{
DeepClone (البند ، الهدف [i]) ؛
}
}
}
الهدف الإرجاع ؛
} ؛
var parent = function (name) {
this.name = name || "الوالد" ؛
} ؛
parent.prototype.getName = function () {
إرجاع هذا.
} ؛
parent.prototype.obj = {a: '1'} ؛
var child = function (name) {
parent.apply (هذا ، الحجج) ؛
} ؛
child.prototype = deepclone (parent.prototype) ؛
var child = طفل جديد ('طفل') ؛
var parent = New Parent ('Parent') ؛
console.log (child.obj.a) ؛ // 1
console.log (parent.obj.a) ؛ // 1
child.obj.a = '2' ؛
console.log (child.obj.a) ؛ // 2
console.log (parent.obj.a) ؛ // 1
بناءً على جميع الاعتبارات المذكورة أعلاه ، يكون التنفيذ المحدد لميراث JavaScript كما يلي. فقط عندما يتم اعتبار وظائف الطفل والوالد:
نسخة الكود كما يلي:
var deepclone = function (المصدر ، الهدف) {
المصدر = المصدر || {} ؛
var tostr = object.prototy.tostring ،
arrstr = '[كائن صفيف]' ؛
لـ (var i in source) {
if (source.hasownproperty (i)) {
var item = source [i] ؛
if (typeof item === 'Object') {
Target [i] = (tostr.apply (item) .ToLowerCase () === Arrstr): []؟ {} ؛
DeepClone (البند ، الهدف [i]) ؛
}آخر{
DeepClone (البند ، الهدف [i]) ؛
}
}
}
الهدف الإرجاع ؛
} ؛
var تمديد = دالة (الوالد ، الطفل) {
طفل = طفل || وظيفة(){} ؛
إذا (الوالد === غير محدد)
إعادة الطفل
// استعارة مُنشئ فئة الوالدين
طفل = دالة () {
parent.apply (هذا ، حجة) ؛
} ؛
// يرث النموذج الأولي من الفئة الأصل من خلال نسخة عميقة
child.prototype = deepclone (parent.prototype) ؛
// إعادة تعيين سمة مُنشئ
child.prototype.constructor = الطفل ؛
} ؛
لخص
بعد قول الكثير ، في الواقع ، فإن تنفيذ الميراث في JavaScript مرن للغاية ومتنوع ، وليس هناك أفضل طريقة. تحتاج طرق الميراث المختلفة إلى تنفيذها وفقًا للاحتياجات المختلفة. الشيء الأكثر أهمية هو فهم مبدأ تنفيذ الميراث في جافا سكريبت ، أي مشكلة النماذج الأولية وسلاسل النموذج الأولي. طالما أنك تفهمها ، يمكنك بسهولة تنفيذ الميراث بنفسك.