Cepat atau lambat Anda perlu menggunakan hasil abstrak dari pengembang lain - yaitu, Anda mengandalkan kode orang lain. Saya suka mengandalkan modul gratis (bebas ketergantungan), tetapi itu sulit dicapai. Bahkan komponen kotak hitam yang indah yang Anda buat akan bergantung pada sesuatu yang lebih atau kurang. Inilah yang membuat injeksi ketergantungan hebat. Kemampuan untuk mengelola dependensi secara efektif sekarang benar -benar diperlukan. Artikel ini merangkum eksplorasi masalah saya dan beberapa solusi.
1. Sasaran
Bayangkan kita memiliki dua modul. Yang pertama bertanggung jawab atas layanan permintaan AJAX, dan yang kedua adalah router.
Salinan kode adalah sebagai berikut:
var service = function () {
return {name: 'service'};
}
var router = function () {
return {name: 'router'};
}
Kami memiliki fungsi lain yang perlu menggunakan dua modul ini.
Salinan kode adalah sebagai berikut:
var dosomething = function (lainnya) {
var s = service ();
var r = router ();
};
Untuk membuatnya lebih menarik, fungsi ini menerima argumen. Tentu saja, kita dapat menggunakan kode di atas sepenuhnya, tetapi ini jelas tidak cukup fleksibel. Bagaimana jika kita ingin menggunakan ServiceXML atau ServiceJson, atau bagaimana jika kita membutuhkan beberapa modul tes. Kami tidak dapat menyelesaikan masalah dengan mengedit tubuh fungsi saja. Pertama, kita dapat menyelesaikan ketergantungan melalui parameter fungsi. Sekarang:
Salinan kode adalah sebagai berikut:
var dosomething = fungsi (layanan, router, lainnya) {
var s = service ();
var r = router ();
};
Kami mengimplementasikan fungsionalitas yang kami inginkan dengan melewati parameter tambahan, namun, ini membawa masalah baru. Bayangkan jika metode dosomething kami tersebar dalam kode kami. Jika kita perlu mengubah kondisi ketergantungan, tidak mungkin bagi kita untuk mengubah semua file yang memanggil fungsi.
Kami membutuhkan alat yang dapat membantu kami menyelesaikan hal -hal ini. Ini adalah masalah yang coba diselesaikan oleh injeksi ketergantungan. Mari kita tuliskan beberapa tujuan yang harus dicapai oleh solusi injeksi ketergantungan kita:
Kita harus dapat mendaftarkan dependensi
1. Injeksi harus menerima fungsi dan mengembalikan fungsi yang kita butuhkan
2. Kita tidak bisa menulis terlalu banyak - kita perlu merampingkan tata bahasa yang indah
3. Injeksi harus mempertahankan ruang lingkup fungsi yang ditransfer
4. Fungsi yang disahkan harus dapat menerima parameter khusus, tidak hanya bergantung pada deskripsi
5. Daftar yang sempurna, mari kita capai di bawah ini.
3. Metode Persyaratan/AMD
Anda mungkin pernah mendengar tentang kebutuhan, yang merupakan pilihan yang baik untuk menyelesaikan injeksi ketergantungan.
Salinan kode adalah sebagai berikut:
define (['service', 'router'], function (service, router) {
// ...
});
Idenya adalah untuk menggambarkan dependensi yang diperlukan terlebih dahulu, dan kemudian tulis fungsi Anda. Urutan parameter di sini sangat penting. Seperti disebutkan di atas, mari kita tulis modul yang disebut injektor yang dapat menerima sintaks yang sama.
Salinan kode adalah sebagai berikut:
var dosomething = injector.resolve (['service', 'router'], function (service, router, lainnya) {
harapkan (service (). name) .to.be ('service');
harapkan (router (). Nama) .to.be ('router');
harapkan (lainnya) .to.be ('lain');
});
dosomething ("lain");
Sebelum melanjutkan, saya harus menjelaskan konten dari badan fungsi dosomething dengan jelas. metode.
Mari kita mulai modul injektor kita, yang merupakan pola singleton yang hebat, jadi berfungsi dengan baik di berbagai bagian program kami.
Salinan kode adalah sebagai berikut:
var injector = {
dependensi: {},
register: function (tombol, value) {
this.dependencies [key] = value;
},
Resolve: function (deps, func, scope) {
}
}
Ini adalah objek yang sangat sederhana, dengan dua metode, satu untuk menyimpan properti. Yang ingin kami lakukan adalah memeriksa array DEP dan mencari jawaban dalam variabel dependensi. Yang tersisa hanyalah memanggil metode .Apply dan lulus parameter dari metode FUNC sebelumnya.
Salinan kode adalah sebagai berikut:
Resolve: function (deps, func, scope) {
var args = [];
untuk (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.dependencies [d]) {
args.push (this.dependencies [d]);
} kalau tidak {
melempar kesalahan baru ('can/' t resolve ' + d);
}
}
return function () {
func.Apply (scope || {}, args.concat (array.prototype.slice.call (argumen, 0)));
}
}
Lingkup adalah opsional, array.prototype.slice.call (argumen, 0) diperlukan untuk mengubah variabel argumen menjadi array nyata. Sejauh ini tidak buruk. Tes kami lulus. Masalah dengan implementasi ini adalah bahwa kita perlu menulis bagian yang diperlukan dua kali dan kita tidak dapat membingungkan pesanan mereka. Parameter khusus tambahan selalu di belakang ketergantungan.
4. Metode refleksi
Menurut definisi Wikipedia, refleksi mengacu pada kemampuan suatu program untuk memeriksa dan memodifikasi struktur dan perilaku objek saat runtime. Sederhananya, dalam konteks JavaScript, ini secara khusus mengacu pada kode sumber suatu objek atau fungsi yang dibaca dan dianalisis. Mari selesaikan fungsi dosomething yang disebutkan di awal artikel. Jika Anda mengeluarkan dosomething.tostring () di konsol. Anda akan mendapatkan string berikut:
Salinan kode adalah sebagai berikut:
"fungsi (layanan, router, lainnya) {
var s = service ();
var r = router ();
} "
String yang dikembalikan dengan metode ini memberi kita kemampuan untuk melintasi parameter, dan yang lebih penting, untuk mendapatkan nama mereka. Ini sebenarnya adalah metode Angular untuk mengimplementasikan injeksi ketergantungannya. Saya sedikit malas dan langsung mencegat ekspresi reguler yang mendapatkan parameter dalam kode sudut.
Salinan kode adalah sebagai berikut:
/^function/s*[^/(]*/(/s*([^/)]*)/)/m
Kami dapat memodifikasi kode resol seperti ini:
Salinan kode adalah sebagai berikut:
Resolve: function () {
var func, deps, ruang lingkup, args = [], self = this;
func = argumen [0];
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .replace (//g, '').membelah(',');
scope = argumen [1] || {};
return function () {
var a = array.prototype.slice.call (argumen, 0);
untuk (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);
}
}
Hasil pelaksanaan ekspresi reguler kami adalah sebagai berikut:
Salinan kode adalah sebagai berikut:
["Fungsi (Layanan, Router, Lainnya)", "Layanan, Router, Lainnya"]
Tampaknya kita hanya membutuhkan item kedua. Setelah kami menghapus spasi dan membagi string, kami mendapatkan array DEP. Hanya ada satu perubahan besar:
Salinan kode adalah sebagai berikut:
var a = array.prototype.slice.call (argumen, 0);
...
args.push (self.dependencies [d] && d! = ''? self.dependencies [d]: a.shift ());
Kami mengulangi array dependensi dan mencoba mendapatkannya dari objek argumen jika kami menemukan item yang hilang. Untungnya, ketika array kosong, metode shift hanya kembali tidak terdefinisi alih -alih melempar kesalahan (ini berkat ide web). Versi baru injektor dapat digunakan seperti berikut:
Salinan kode adalah sebagai berikut:
var dosomething = injector.resolve (function (layanan, lainnya, router) {
harapkan (service (). name) .to.be ('service');
harapkan (router (). Nama) .to.be ('router');
harapkan (lainnya) .to.be ('lain');
});
dosomething ("lain");
Tidak perlu menulis ulang dependensi dan pesanan mereka dapat terganggu. Itu masih berfungsi, dan kami berhasil menyalin sihir Angular.
Namun, praktik ini tidak sempurna, yang merupakan masalah yang sangat besar dengan injeksi tipe refleks. Kompresi akan menghancurkan logika kami karena mengubah nama parameter dan kami tidak akan dapat mempertahankan hubungan pemetaan yang benar. Misalnya, dosometing () mungkin terlihat seperti ini setelah kompresi:
Salinan kode adalah sebagai berikut:
var dosomething = fungsi (e, t, n) {var r = e (); var i = t ()}
Solusi yang diusulkan oleh tim sudut terlihat seperti:
var dosomething = injector.resolve (['service', 'router', function (service, router) {
}]);
Ini sangat mirip dengan solusi yang kami mulai. Saya tidak dapat menemukan solusi yang lebih baik, jadi saya memutuskan untuk menggabungkan keduanya. Berikut adalah versi terakhir dari injektor.
Salinan kode adalah sebagai berikut:
var injector = {
dependensi: {},
register: function (tombol, value) {
this.dependencies [key] = value;
},
Resolve: function () {
var func, deps, ruang lingkup, args = [], self = this;
if (typeof argumen [0] === 'string') {
func = argumen [1];
deps = argumen [0] .replace ( / / g, '') .split (',');
scope = argumen [2] || {};
} kalau tidak {
func = argumen [0];
deps = func.toString (). match (/^function/s*[^/(]*/(/s*([^/)]*)/)/m) [1] .replace (//g, '').membelah(',');
scope = argumen [1] || {};
}
return function () {
var a = array.prototype.slice.call (argumen, 0);
untuk (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);
}
}
}
Selesaikan pengunjung menerima dua atau tiga parameter, jika ada dua parameter, sebenarnya sama dengan apa yang tertulis dalam artikel sebelumnya. Namun, jika ada tiga parameter, itu mengonversi parameter pertama dan mengisi array DEP, berikut adalah contoh tes:
Salinan kode adalah sebagai berikut:
var dosomething = injector.resolve ('router ,, service', function (a, b, c) {
harapkan (a (). Nama) .to.be ('router');
harapkan (b) .to.be ('lain');
harapkan (c (). Nama) .to.be ('layanan');
});
dosomething ("lain");
Anda mungkin memperhatikan bahwa ada dua koma setelah parameter pertama - perhatikan bahwa ini bukan kesalahan ketik. Nilai nol sebenarnya mewakili parameter "lain" (placeholder). Ini menunjukkan bagaimana kami mengontrol urutan parameter.
5. Suntikan lingkup langsung
Kadang -kadang saya menggunakan variabel injeksi ketiga, yang melibatkan ruang lingkup fungsi operasi (dengan kata lain, objek ini). Oleh karena itu, variabel ini tidak diperlukan dalam banyak kasus.
Salinan kode adalah sebagai berikut:
var injector = {
dependensi: {},
register: function (tombol, value) {
this.dependencies [key] = value;
},
Resolve: function (deps, func, scope) {
var args = [];
scope = scope || {};
untuk (var i = 0; i <deps.length, d = deps [i]; i ++) {
if (this.dependencies [d]) {
SCOPE [D] = This.dependencies [D];
} kalau tidak {
melempar kesalahan baru ('can/' t resolve ' + d);
}
}
return function () {
func.Apply (scope || {}, array.prototype.slice.call (argumen, 0));
}
}
}
Yang kami lakukan adalah menambahkan ketergantungan ke dalam lingkup. Keuntungan dari ini adalah bahwa pengembang tidak lagi harus menulis parameter ketergantungan;
Salinan kode adalah sebagai berikut:
var dosomething = injector.resolve (['service', 'router'], function (lainnya) {
harapkan (this.service (). name) .to.be ('service');
harapkan (this.router (). name) .to.be ('router');
harapkan (lainnya) .to.be ('lain');
});
dosomething ("lain");
6. Kesimpulan
Bahkan, kebanyakan dari kita telah menggunakan injeksi ketergantungan, tetapi kami tidak menyadarinya. Bahkan jika Anda tidak tahu istilahnya, Anda mungkin telah menggunakannya dalam kode Anda jutaan kali. Saya harap artikel ini akan memperdalam pemahaman Anda tentang hal itu.