عاجلاً أم آجلاً ، تحتاج إلى استخدام النتائج المجردة للمطورين الآخرين - أي أنك تعتمد على رمز الآخرين. أحب الاعتماد على وحدات مجانية (خالية من التبعية) ، لكن من الصعب تحقيق ذلك. حتى مكونات الصندوق الأسود الجميل الذي تنشئه ستعتمد على شيء أكثر أو أقل. هذا هو بالضبط ما يجعل الحقن التبعية رائعا. القدرة على إدارة التبعيات بفعالية ضرورية الآن. يلخص هذا المقال استكشاف المشكلة وبعض الحلول.
1. الهدف
تخيل أن لدينا وحدتان. الأول هو المسؤول عن خدمة طلب 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. قائمة مثالية ، دعنا نحققها أدناه.
3
ربما تكون قد سمعت عن requirejs ، وهو خيار جيد لحل حقن التبعية.
نسخة الكود كما يلي:
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 بوضوح. طريقة.
لنبدأ وحدة الحاقن الخاصة بنا ، وهو نمط مفرز رائع ، لذلك يعمل بشكل جيد في أجزاء مختلفة من برنامجنا.
نسخة الكود كما يلي:
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) مطلوبة لتحويل متغيرات الوسائط إلى صفائف حقيقية. حتى الآن ليس سيئًا. لقد مرت اختبارنا. المشكلة في هذا التنفيذ هي أننا نحتاج إلى كتابة الأجزاء المطلوبة مرتين ولا يمكننا الخلط بين ترتيبها. معلمات مخصصة إضافية هي دائما وراء التبعية.
4. طريقة الانعكاس
وفقًا لتعريف ويكيبيديا ، يشير التفكير إلى قدرة البرنامج على فحص وتعديل بنية وسلوك كائن في وقت التشغيل. ببساطة ، في سياق JavaScript ، يشير هذا على وجه التحديد إلى الكود المصدري لكائن أو وظيفة يتم قراءتها وتحليلها. دعنا نكتمل وظيفة dosomething المذكورة في بداية المقالة. إذا قمت بإخراج dosomething.toString () في وحدة التحكم. ستحصل على السلسلة التالية:
نسخة الكود كما يلي:
"وظيفة (الخدمة ، جهاز التوجيه ، آخر) {
var s = service () ؛
var r = router () ؛
} "
تمنحنا السلسلة التي يتم إرجاعها بهذه الطريقة القدرة على اجتياز المعلمات ، والأهم من ذلك ، الحصول على أسمائها. هذه هي في الواقع طريقة Angular لتنفيذ حقن التبعية. كنت كسولًا بعض الشيء واعترضت بشكل مباشر التعبير العادي الذي يحصل على المعلمات في الكود الزاوي.
نسخة الكود كما يلي:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
يمكننا تعديل رمز حل مثل هذا:
نسخة الكود كما يلي:
حل: وظيفة () {
var func ، deps ، نطاق ، args = [] ، الذات = هذا ؛
func = الحجج [0] ؛
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/m) [1] .replace (//g ، '').ينقسم('،')؛
SCOPE = الوسيطات [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') ؛
}) ؛
شيء ("آخر") ؛
ليست هناك حاجة لإعادة كتابة التبعيات ويمكن تعطيل ترتيبها. لا يزال يعمل ، وقمنا بنجاح بنسخ سحر Angular.
ومع ذلك ، فإن هذه الممارسة ليست مثالية ، وهي مشكلة كبيرة جدًا مع حقن نوع Reflex. سيؤدي الضغط إلى تدمير منطقنا لأنه يغير اسم المعلمة ولن نتمكن من الحفاظ على علاقة التعيين الصحيحة. على سبيل المثال ، قد يبدو Dosometing () هكذا بعد الضغط:
نسخة الكود كما يلي:
var dosomething = function (e ، t ، n) {var r = e () ؛ var i = t ()}
يشبه الحل الذي اقترحه الفريق الزاوي:
var dosomething = enjector.Resolve (['Service' ، 'Router' ، function (service ، router) {
}]) ؛
هذا يشبه إلى حد كبير الحل الذي بدأناه. لم أتمكن من العثور على حل أفضل ، لذلك قررت الجمع بين كليهما. هنا هي النسخة النهائية من الحاقن.
نسخة الكود كما يلي:
var enjector = {
التبعيات: {} ،
السجل: الدالة (المفتاح ، القيمة) {
this.dependencies [key] = value ؛
} ،
حل: وظيفة () {
var func ، deps ، نطاق ، args = [] ، الذات = هذا ؛
if (typeof mations [0] === 'String') {
func = الحجج [1] ؛
DEPS = الوسيطات [0] .replace ( / / g ، '') .split ('،') ؛
SCOPE = الوسيطات [2] || {} ؛
} آخر {
func = الحجج [0] ؛
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/m) [1] .replace (//g ، '').ينقسم('،')؛
SCOPE = الوسيطات [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 dosomething = enjector.Resolve ('Router ،، service' ، function (a ، b ، c) {
توقع (a (). الاسم). to.be ('Router') ؛
توقع (ب). إلى.
توقع (c (). الاسم). to.be ('service') ؛
}) ؛
شيء ("آخر") ؛
قد تلاحظ أن هناك فاصلة بعد المعلمة الأولى - لاحظ أن هذا ليس خطأ مطبعي. تمثل القيمة الفارغة في الواقع المعلمة "الأخرى" (صاحب نائب). هذا يوضح كيف نتحكم في ترتيب المعلمات.
5. الحقن المباشر للنطاق
أحيانًا أستخدم متغير الحقن الثالث ، والذي يتضمن نطاق وظيفة التشغيل (بمعنى آخر ، هذا الكائن). لذلك ، هذا المتغير ليس ضروريًا في كثير من الحالات.
نسخة الكود كما يلي:
var enjector = {
التبعيات: {} ،
السجل: الدالة (المفتاح ، القيمة) {
this.dependencies [key] = value ؛
} ،
حل: وظيفة (DEPS ، FUNC ، نطاق) {
var args = [] ؛
Scope = Scope || {} ؛
لـ (var i = 0 ؛ i <deps.length ، d = deps [i] ؛ i ++) {
if (this.dependencies [d]) {
النطاق [d] = this.dependencies [d] ؛
} آخر {
رمي خطأ جديد ("لا يمكن/حل" + د) ؛
}
}
وظيفة الإرجاع () {
funC.Apply (Scope || {} ، array.prototype.slice.call (الوسيطات ، 0)) ؛
}
}
}
كل ما نفعله هو في الواقع إضافة تبعيات إلى النطاق. ميزة ذلك هي أن المطورين لم يعد عليهم كتابة معلمات التبعية ؛
نسخة الكود كما يلي:
var dosomething = enjector.Resolve (['Service' ، 'Router'] ، function (other) {
توقع (this.service (). name) .to.be ('service') ؛
توقع (this.router (). name) .to.be ('Router') ؛
توقع (آخر). to.be ('other') ؛
}) ؛
شيء ("آخر") ؛
6. الخلاصة
في الواقع ، استخدم معظمنا حقن التبعية ، لكننا لا ندرك ذلك. حتى لو كنت لا تعرف المصطلح ، فقد تكون قد استخدمته في الكود الخاص بك مليون مرة. آمل أن تعمق هذا المقال فهمك لها.