عالم برمجة الكمبيوتر هو في الواقع عملية لاستخلاص أجزاء بسيطة باستمرار وتنظيم هذه التجريدات. JavaScript ليس استثناء. عندما نستخدم JavaScript لكتابة التطبيقات ، هل نستخدم التعليمات البرمجية التي كتبها الآخرين ، مثل بعض المكتبات أو الأطر الشهيرة مفتوحة المصدر. مع نمو مشاريعنا ، المزيد والمزيد من الوحدات التي نحتاج إلى الاعتماد عليها. في هذا الوقت ، أصبحت كيفية تنظيم هذه الوحدات بشكل فعال مشكلة مهمة للغاية. حقن التبعية يحل مشكلة كيفية تنظيم وحدات تبعية الكود بفعالية. ربما تكون قد سمعت عن مصطلح "حقن التبعية" في بعض الأطر أو المكتبات ، مثل AngularJs الإطار الأمامي الشهير. حقن التبعية هي واحدة من الميزات المهمة للغاية. ومع ذلك ، فإن حقن التبعية ليس بالأمر الجديد على الإطلاق ، فقد كان موجودًا لفترة طويلة بلغات برمجة أخرى مثل PHP. في الوقت نفسه ، لا يكون حقن التبعية معقدًا كما يتخيل. في هذه المقالة ، سوف نتعلم مفهوم حقن التبعية في JavaScript ونشرح بطريقة سهلة الفهم كيفية كتابة رمز "نمط حقن التبعية".
الإعداد الهدف
لنفترض أن لدينا وحدتان الآن. تتمثل وظيفة الوحدة النمطية الأولى في إرسال طلبات AJAX ، في حين أن وظيفة الوحدة الثانية تتمثل في استخدامها كترويش.
نسخة الكود كما يلي:
var service = function () {
إرجاع {name: 'service'} ؛
}
var Router = function () {
return {name: 'Router'} ؛
}
في هذا الوقت ، كتبنا وظيفة تحتاج إلى استخدام الوحدتين المذكورة أعلاه:
نسخة الكود كما يلي:
var dosomething = function (other) {
var s = service () ؛
var r = router () ؛
} ؛
هنا ، لجعل رمزنا أكثر إثارة للاهتمام ، تحتاج هذه المعلمة إلى تلقي العديد من المعلمات الأخرى. بالطبع ، يمكننا استخدام الكود أعلاه ، ولكن بغض النظر عن أي جانب ، يبدو الرمز أعلاه أقل مرونة. ماذا يجب أن نفعل إذا أصبح اسم الوحدة النمطية التي نحتاج إلى استخدامها servicexml أو serviceJson؟ أو ماذا لو أردنا استخدام بعض الوحدات المزيفة لأغراض الاختبار؟ في هذا الوقت ، لا يمكننا فقط تعديل الوظيفة نفسها. لذلك ، فإن أول ما يتعين علينا القيام به هو تمرير الوحدة التابعة كمعلمات للوظيفة ، فإن الكود كما يلي:
نسخة الكود كما يلي:
var dosomething = function (service ، router ، other) {
var s = service () ؛
var r = router () ؛
} ؛
في الكود أعلاه ، نمرر الوحدات التي نحتاجها تمامًا. لكن هذا يثير مشكلة جديدة. لنفترض أننا نسمي طريقة dosomething في الجزء الأخ من الكود. في هذا الوقت ، ماذا يجب أن نفعل إذا كنا بحاجة إلى تبعية ثالثة. في هذا الوقت ، ليست طريقة حكيمة لتحرير جميع رمز استدعاء الوظيفة. لذلك نحن بحاجة إلى قطعة من الكود لمساعدتنا على القيام بذلك. هذه هي المشكلة التي يحاول حاقن التبعية حلها. الآن يمكننا تحديد أهدافنا:
1. يجب أن نكون قادرين على تسجيل التبعيات
2. يجب أن يتلقى حاقن التبعية وظيفة ثم إرجاع وظيفة يمكنها الحصول على الموارد المطلوبة
3. يجب ألا يكون الكود معقدًا ، ولكن يجب أن يكون بسيطًا وودودًا
4. يجب أن يحافظ حاقن التبعية على نطاق الوظيفة الذي تم تمريره
5. يجب أن تكون الوظيفة التي تم تمريرها قادرة على تلقي معلمات مخصصة ، وليس فقط التبعيات الموصوفة
متطلبات/طريقة AMD
ربما تكون قد سمعت عن المتطلبات الشهيرة ، وهي مكتبة يمكنها حل مشكلة حقن التبعية بشكل جيد:
نسخة الكود كما يلي:
DEFINE (['Service' ، 'Router'] ، function (service ، trouter) {
// ...
}) ؛
فكرة المتطلبات هي أنه يجب علينا أولاً وصف الوحدات المطلوبة ثم كتابة وظائفك الخاصة. من بينها ، ترتيب المعلمات مهم جدا. لنفترض أننا بحاجة إلى كتابة وحدة تسمى الحاقن يمكنها تنفيذ بناء جملة مماثل.
نسخة الكود كما يلي:
var dosomething = enjector.Resolve (['Service' ، 'Router'] ، function (service ، router ، other) {
توقع (service (). name) .to.be ('service') ؛
توقع (Router (). الاسم). to.be ('Router') ؛
توقع (آخر). to.be ('other') ؛
}) ؛
شيء ("آخر") ؛
قبل المتابعة ، هناك شيء واحد يجب ملاحظته هو أنه في جسم وظيفة dosomething ، نستخدم مكتبة التأكيد التي تتوقع. js لضمان صحة الكود. هنا يشبه بعض الشيء TDD (التطوير الذي يحركه الاختبار).
الآن نبدأ رسميًا في كتابة وحدة الحاقن الخاصة بنا. أولاً ، يجب أن يكون مونومرًا بحيث يمكن أن يكون له نفس الوظيفة في كل جزء من تطبيقنا.
نسخة الكود كما يلي:
var enjector = {
التبعيات: {} ،
السجل: الدالة (المفتاح ، القيمة) {
this.dependencies [key] = value ؛
} ،
حل: وظيفة (DEPS ، FUNC ، نطاق) {
}
}
هذا الكائن بسيط للغاية ، مع وظيفتين فقط ومتغير لأغراض التخزين. ما يتعين علينا القيام به هو التحقق من صفيف DEPS ثم ابحث عن الإجابة في متغيرات التبعيات. الباقي هو استخدام طريقة .apply للاتصال بمتغير FUNC الذي مررنا به:
نسخة الكود كما يلي:
حل: وظيفة (DEPS ، FUNC ، نطاق) {
var args = [] ؛
لـ (var i = 0 ؛ i <deps.length ، d = deps [i] ؛ i ++) {
if (this.dependencies [d]) {
args.push (this.dependencies [d]) ؛
} آخر {
رمي خطأ جديد ("لا يمكن/حل" + د) ؛
}
}
وظيفة الإرجاع () {
funC.Apply (Scope || {} ، args.concat (Array.Prototype.slice.call (الوسيطات ، 0))) ؛
}
}
إذا كنت بحاجة إلى تحديد نطاق ، فسيتم تشغيل الكود أعلاه بشكل طبيعي.
في الكود أعلاه ، فإن دور Array.Prototype.slice.call (الوسيطات ، 0) هو تحويل متغير الوسائط إلى صفيف حقيقي. حتى الآن ، مرت الكود لدينا الاختبار تمامًا. لكن المشكلة هنا هي أنه يتعين علينا كتابة الوحدات المطلوبة مرتين ، ولا يمكننا ترتيبها بشكل تعسفي. تتبع المعلمات الإضافية دائمًا جميع التبعيات.
طريقة الانعكاس
وفقًا للتفسير في ويكيبيديا ، يشير التفكير إلى حقيقة أن الكائن يمكنه تعديل بنية وسلوكه أثناء التشغيل. في JavaScript ، ببساطة وضعها هي القدرة على قراءة الكود المصدر لكائن وتحليل رمز المصدر. أو عد إلى طريقة dosomething الخاصة بنا ، إذا قمت بالاتصال بالطريقة dosomething.toString () ، يمكنك الحصول على السلسلة التالية:
نسخة الكود كما يلي:
"وظيفة (الخدمة ، جهاز التوجيه ، آخر) {
var s = service () ؛
var r = router () ؛
} "
وبهذه الطريقة ، طالما استخدمنا هذه الطريقة ، يمكننا بسهولة الحصول على المعلمات التي نريدها ، والأهم من ذلك ، أسمائها. هذه هي أيضًا الطريقة التي تستخدمها AngularJS لتنفيذ حقن التبعية. في رمز AngularJS ، يمكننا أن نرى التعبير العادي التالي:
نسخة الكود كما يلي:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
يمكننا تعديل طريقة حل إلى الكود التالي:
نسخة الكود كما يلي:
حل: وظيفة () {
var func ، deps ، نطاق ، args = [] ، الذات = هذا ؛
func = الحجج [0] ؛
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .replace (//g ، '') .Split ('،') ؛
النطاق = الوسائط [1] || {} ؛
وظيفة الإرجاع () {
var a = array.prototype.slice.call (الوسائط ، 0) ؛
لـ (var i = 0 ؛ i <deps.length ؛ i ++) {
var d = deps [i] ؛
args.push (self.dependencies [d] && d! = ''؟ self.dependencies [d]: a.shift ()) ؛
}
func.apply (Scope || {} ، args) ؛
}
}
نستخدم التعبير العادي أعلاه لمطابقة الوظيفة التي حددناها ، ويمكننا الحصول على النتيجة التالية:
نسخة الكود كما يلي:
["الوظيفة (الخدمة ، جهاز التوجيه ، آخر)" ، "الخدمة ، جهاز التوجيه ، آخر"]
في هذه المرحلة ، نحتاج فقط إلى العنصر الثاني. ولكن بمجرد إزالة المساحات الإضافية ونقطع السلسلة ، نحصل على صفيف DEPS. الرمز التالي هو الجزء الذي قمنا بتعديله:
نسخة الكود كما يلي:
var a = array.prototype.slice.call (الوسائط ، 0) ؛
...
args.push (self.dependencies [d] && d! = ''؟ self.dependencies [d]: a.shift ()) ؛
في الكود أعلاه ، نعبر مشروع التبعية ، إذا كانت هناك عناصر مفقودة فيه ، إذا كانت هناك أجزاء مفقودة في مشروع التبعية ، فإننا نحصل عليها من كائن الوسائط. إذا كانت الصفيف عبارة عن صفيف فارغ ، فإن استخدام طريقة التحول سيعود فقط غير محدد دون إلقاء خطأ. حتى الآن ، يبدو الإصدار الجديد من الحاقن هذا:
نسخة الكود كما يلي:
var dosomething = enjector.Resolve (وظيفة (الخدمة ، أخرى ، جهاز توجيه) {
توقع (service (). name) .to.be ('service') ؛
توقع (Router (). الاسم). to.be ('Router') ؛
توقع (آخر). to.be ('other') ؛
}) ؛
شيء ("آخر") ؛
في الكود أعلاه ، يمكننا الخلط بين ترتيب التبعيات حسب الرغبة.
لكن لا شيء مثالي. هناك مشكلة خطيرة للغاية في حقن التبعية لطرق الانعكاس. يحدث خطأ عند تبسيط الرمز. هذا لأنه أثناء تبسيط الكود ، يتغير اسم المعلمة ، مما سيؤدي إلى حل التبعيات. على سبيل المثال:
نسخة الكود كما يلي:
var dosomething = function (e ، t ، n) {var r = e () ؛ var i = t ()}
لذلك نحن بحاجة إلى الحل التالي ، كما في AngularJS:
نسخة الكود كما يلي:
var dosomething = enjector.Resolve (['Service' ، 'Router' ، function (service ، router) {
}]) ؛
هذا مشابه جدًا لحل AMD الذي رأيته في البداية ، حتى نتمكن من دمج الطريقتين أعلاه ، والرمز النهائي هو كما يلي:
نسخة الكود كما يلي:
var enjector = {
التبعيات: {} ،
السجل: الدالة (المفتاح ، القيمة) {
this.dependencies [key] = value ؛
} ،
حل: وظيفة () {
var func ، deps ، نطاق ، args = [] ، الذات = هذا ؛
if (typeof mations [0] === 'String') {
func = الحجج [1] ؛
DEPS = الوسيطات [0] .replace ( / / g ، '') .split ('،') ؛
النطاق = الوسائط [2] || {} ؛
} آخر {
func = الحجج [0] ؛
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .replace (//g ، '') .Split ('،') ؛
النطاق = الوسائط [1] || {} ؛
}
وظيفة الإرجاع () {
var a = array.prototype.slice.call (الوسائط ، 0) ؛
لـ (var i = 0 ؛ i <deps.length ؛ i ++) {
var d = deps [i] ؛
args.push (self.dependencies [d] && d! = ''؟ self.dependencies [d]: a.shift ()) ؛
}
func.apply (Scope || {} ، args) ؛
}
}
}
يمكن أن يقبل هذا الإصدار من طريقة حل اثنين أو ثلاثة معلمتين. هنا رمز الاختبار:
نسخة الكود كما يلي:
var dosomething = enjector.Resolve ('Router ،، service' ، function (a ، b ، c) {
توقع (a (). الاسم). to.be ('Router') ؛
توقع (ب). إلى.
توقع (c (). الاسم). to.be ('service') ؛
}) ؛
شيء ("آخر") ؛
ربما لاحظت أنه لا يوجد شيء بين فواصل ، وهذا ليس خطأ. يتم ترك هذا الشواغر للمعلمة الأخرى. هذه هي الطريقة التي نتحكم بها في ترتيب المعلمات.
خاتمة
في المحتوى أعلاه ، قدمنا عدة طرق لحقن التبعية في جافا سكريبت. آمل أن تساعدك هذه المقالة في البدء في استخدام تقنية حقن التبعية وكتابة رمز نمط حقن التبعية.