Kode dalam JavaScript dengan cara yang cerdas dan modular

Beberapa orang masih tampak terkejut bahwa JavaScript dianggap sebagai bahasa pemrograman dewasa yang terhormat untuk aplikasi yang serius. Faktanya, pengembangan JavaScript telah matang dengan baik selama bertahun-tahun, dengan praktik terbaik untuk pengembangan modular sebagai contoh utama.

Manfaat menulis kode modular didokumentasikan dengan baik: kemudahan perawatan yang lebih baik, menghindari file monolitik, dan memisahkan kode menjadi beberapa unit yang dapat diuji dengan benar. Bagi Anda yang ingin mengetahui dengan cepat, berikut adalah praktik umum yang digunakan oleh pengembang JavaScript modern untuk menulis kode termodulasi.

Pola modul

Mari kita mulai dengan pola desain dasar yang disebut pola modul. Seperti yang mungkin Anda duga, ini memungkinkan kami untuk menulis kode dengan cara modular, memungkinkan kami melindungi konteks eksekusi dari modul yang diberikan dan untuk mengekspos secara global hanya apa yang ingin kami ungkapkan. Polanya terlihat seperti:

(function(){

//'private' variable

var orderId = 123;

// expose methods and variables by attaching them

// to the global object

window.orderModule = {

getOrderId: function(){

//brought to you by closures

return orderId;

}

};

})()

function Ekspresi anonim  , yang bertindak sebagai pabrik dalam kasus ini, ditulis dan dipanggil segera. Untuk kecepatan, Anda dapat secara eksplisit meneruskan variabel ke  function panggilan, secara efektif mengikat variabel tersebut ke cakupan lokal. Anda juga kadang-kadang akan melihat ini sebagai manuver defensif di perpustakaan yang mendukung browser lama di mana nilai-nilai tertentu (seperti undefined) adalah properti yang dapat ditulisi.

(function(global, undefined){

// code here can access the global object quickly,

// and 'undefined' is sure to be 'undefined'

// NOTE: In modern browsers 'undefined' isn't writeable,

// but it's worth keeping it in mind

// when writing code for old browsers.

})(this)

Ini hanyalah pola desain. Dengan teknik ini, untuk menulis JavaScript modular, Anda tidak perlu menyertakan pustaka tambahan. Kurangnya dependensi merupakan nilai tambah (atau mission critical) yang besar di beberapa pengaturan, terutama jika Anda menulis perpustakaan. Anda akan melihat bahwa sebagian besar pustaka populer akan menggunakan pola ini untuk merangkum fungsi dan variabel internal, hanya memperlihatkan apa yang diperlukan.

Namun, jika Anda menulis aplikasi, ada beberapa kelemahan dari pendekatan ini. Katakanlah Anda membuat modul yang menetapkan beberapa metode  window.orders. Jika Anda ingin menggunakan metode tersebut di bagian lain aplikasi Anda, Anda perlu memastikan modul disertakan sebelum Anda memanggilnya. Kemudian, di kode tempat Anda menelepon window.orders.getOrderId, Anda menulis kode dan berharap skrip lain telah dimuat.

Ini mungkin tidak tampak seperti akhir dunia, tetapi dapat dengan cepat lepas kendali pada proyek-proyek yang rumit - dan mengelola urutan penyertaan skrip menjadi merepotkan. Selain itu, semua file Anda harus dimuat secara sinkron, atau Anda akan mengundang kondisi balapan untuk memecahkan kode Anda. Jika saja ada cara untuk secara eksplisit mendeklarasikan modul yang ingin Anda gunakan untuk sedikit kode ....

AMD (definisi modul asinkron)

AMD lahir dari kebutuhan untuk menentukan dependensi eksplisit sambil menghindari pemuatan sinkron semua skrip. Ini mudah digunakan di browser tetapi tidak asli, jadi Anda perlu menyertakan pustaka yang memuat skrip, seperti RequireJS atau curl.js. Berikut ini tampilan mendefinisikan modul menggunakan AMD:

// libs/order-module.js

define(function(){

//'private' variable

var orderId = 123;

// expose methods and variables by returning them

return {

getOrderId: function(){

return orderId;

}

});

Kelihatannya mirip dengan apa yang kami hadapi sebelumnya, kecuali bahwa alih-alih langsung memanggil fungsi pabrik kami secara langsung, kami meneruskan sebagai argumen  define. Keajaiban nyata mulai terjadi ketika Anda ingin menggunakan modul nanti:

define( [ 'libs/order-module' ], function(orderModule) {

orderModule.getOrderId(); //evaluates to 123

});

Argumen pertama  define sekarang adalah larik dependensi, yang bisa panjang secara acak, dan fungsi pabrik mencantumkan parameter formal untuk dependensi tersebut untuk dilampirkan. Sekarang, beberapa dependensi yang Anda butuhkan mungkin memiliki dependensinya sendiri, tetapi dengan AMD, Anda tidak perlu tahu bahwa:

// src/utils.js

define( [ 'libs/underscore' ], function(_) {

return {

moduleId: 'foo',

_ : _

});

// src/myapp.js

define( [

'libs/jquery',

'libs/handlebars',

'src/utils'

], function($, Handlebars, Utils){

// Use each of the stated dependencies without

// worrying if they're there or not.

$('div').addClass('bar');

// Sub dependencies have also been taken care of

Utils._.keys(window);

});

Ini adalah cara yang bagus untuk mengembangkan JavaScript modular saat berhadapan dengan banyak bagian yang bergerak dan dependensi. Tanggung jawab untuk memesan dan menyertakan skrip sekarang berada di pundak pemuat skrip, sehingga Anda bebas menyatakan apa yang Anda butuhkan dan mulai menggunakannya.

Di sisi lain, ada beberapa potensi masalah. Pertama, Anda memiliki perpustakaan tambahan untuk disertakan dan dipelajari untuk digunakan. Saya tidak memiliki pengalaman dengan curl.js, tetapi RequireJS melibatkan pembelajaran cara mengatur konfigurasi untuk proyek Anda. Ini akan memakan waktu beberapa jam untuk membiasakan diri dengan pengaturan, setelah itu menulis konfigurasi awal hanya membutuhkan beberapa menit. Selain itu, definisi modul bisa bertambah panjang jika mengarah ke tumpukan dependensi. Berikut contohnya, diambil dari penjelasan masalah ini di dokumentasi RequireJS:

// From RequireJS documentation:

// //requirejs.org/docs/whyamd.html#sugar

define([ "require", "jquery", "blade/object", "blade/fn", "rdapi",

"oauth", "blade/jig", "blade/url", "dispatch", "accounts",

"storage", "services", "widgets/AccountPanel", "widgets/TabButton",

"widgets/AddAccount", "less", "osTheme", "jquery-ui-1.8.7.min",

"jquery.textOverflow"],

function (require, $, object, fn, rdapi,

oauth, jig, url, dispatch, accounts,

storage, services, AccountPanel, TabButton,

AddAccount, less, osTheme) {

});

Aduh! RequireJS menyediakan beberapa gula sintaksis untuk menangani ini, yang terlihat sangat mirip dengan API populer lainnya untuk pengembangan modular, CommonJS.

CJS (CommonJS)

Jika Anda pernah menulis JavaScript sisi server menggunakan Node.js, Anda telah menggunakan modul CommonJS. Setiap file yang Anda tulis tidak dibungkus dengan sesuatu yang mewah, tetapi memiliki akses ke variabel yang dipanggil  exports ke mana Anda dapat menetapkan apa pun yang Anda ingin diekspos oleh modul. Berikut tampilannya:

// a 'private' variable

var orderId = 123;

exports.getOrderId = function() {

return orderId;

};

Kemudian ketika Anda ingin menggunakan modul, Anda mendeklarasikannya secara inline:

// orderModule gets the value of 'exports'

var orderModule = require('./order-module');

orderModule.getOrderId(); // evaluates to 123

Secara sintaksis, ini selalu terlihat lebih baik bagi saya, sebagian besar karena ini tidak melibatkan indentasi yang sia-sia dalam opsi lain yang telah kita diskusikan. Di sisi lain, ini sangat berbeda dari yang lain, karena dirancang untuk pemuatan dependensi yang sinkron. Ini lebih masuk akal di server, tetapi tidak akan berhasil di front end. Pemuatan ketergantungan sinkron berarti waktu muat halaman lebih lama, yang tidak dapat diterima untuk Web. Sementara CJS sejauh ini adalah sintaks modul yang tampak favorit saya, saya dapat menggunakannya hanya di server (dan saat menulis aplikasi seluler dengan Titanium Studio Appcelerator).

Satu untuk mengatur semuanya

Draf ECMAScript edisi keenam (ES6) saat ini, spesifikasi tempat JavaScript diimplementasikan, menambahkan dukungan asli untuk modul. Spesifikasi ini masih dalam bentuk draf, tetapi ada baiknya melihat seperti apa masa depan pengembangan modular. Beberapa kata kunci baru digunakan dalam spesifikasi ES6 (juga dikenal sebagai Harmoni), beberapa di antaranya digunakan dengan modul:

// libs/order-module.js

var orderId = 123;

export var getOrderId = function(){

return orderId;

};

Anda dapat memanggilnya nanti dengan beberapa cara:

import { getOrderId } from "libs/order-module";

getOrderId();

Di atas, Anda memilih ekspor mana dalam modul yang ingin Anda kaitkan ke variabel lokal. Atau, Anda dapat mengimpor seluruh modul seolah-olah itu adalah objek (mirip dengan  exports objek dalam modul CJS):

import "libs/order-module" as orderModule;

orderModule.getOrderId();

Ada lebih banyak modul ES6 (lihat blog 2ality Dr. Axel Rauschmayer untuk lebih lanjut), tetapi contoh ini akan menunjukkan beberapa hal. Pertama, kami memiliki sintaks yang mirip dengan modul CJS, yang berarti lekukan tambahan tidak dapat ditemukan, meskipun tidak selalu demikian, seperti yang akan Anda temukan di tautan 2ality di atas. Apa yang tidak jelas dengan melihat contoh, terutama karena sangat mirip dengan CJS, adalah bahwa modul yang tercantum dalam pernyataan import dimuat secara asynchronous.

Hasil akhirnya adalah sintaks CJS yang mudah dibaca bercampur dengan sifat asinkron dari AMD. Sayangnya, perlu beberapa saat sebelum ini didukung sepenuhnya di semua browser yang umumnya ditargetkan. Artinya, "sementara" telah menjadi semakin pendek dan pendek karena vendor browser memperketat siklus rilis mereka.

Saat ini, kombinasi dari alat-alat ini adalah cara terbaik untuk mengembangkan JavaScript modular. Itu semua tergantung pada apa yang Anda lakukan. Jika Anda menulis perpustakaan, gunakan pola desain modul. Jika Anda membuat aplikasi untuk browser, gunakan modul AMD dengan pemuat skrip. Jika Anda berada di server, manfaatkan modul CJS. Akhirnya, tentu saja, ES6 akan didukung secara menyeluruh - pada titik mana Anda dapat melakukan berbagai hal dengan cara ES6 dan membuang sisanya!

Pertanyaan atau pemikiran? Silakan tinggalkan pesan di bawah di bagian komentar atau hubungi saya di Twitter, @ freethejazz.