Rahasia Fungsi JavaScript

Setiap programmer terbiasa dengan fungsi. Fungsi dalam JavaScript memiliki banyak kemungkinan, yang memungkinkan kita menyebutnya "fungsi tingkat tinggi". Tetapi bahkan jika Anda menggunakan fitur JavaScript sepanjang waktu, mereka mungkin memiliki sesuatu yang mengejutkan Anda. Dalam posting ini, saya akan membahas beberapa fitur lanjutan dari fungsi JavaScript. Saya harap apa yang Anda pelajari hari ini bermanfaat bagi Anda.











Fungsi murni



Fungsi yang memenuhi dua persyaratan berikut disebut murni:



  • Itu selalu, ketika dipanggil dengan argumen yang sama, mengembalikan hasil yang sama.
  • Tidak ada efek samping yang terjadi saat fungsi dijalankan.


Mari pertimbangkan sebuah contoh:



function circleArea(radius){
  return radius * radius * 3.14
}


Jika fungsi ini diberikan nilai yang sama radius, itu selalu mengembalikan hasil yang sama. Pada saat yang sama, selama menjalankan fungsi, tidak ada yang berubah di luarnya, yaitu tidak memiliki efek samping. Semua ini berarti bahwa ini adalah fungsi murni.



Berikut contoh lainnya:



let counter = (function(){
  let initValue = 0
  return function(){
    initValue++;
    return initValue
  }
})()


Mari coba fungsi ini di konsol browser.





Menguji fungsi di konsol browser



Seperti yang Anda lihat, fungsicounteryang mengimplementasikan penghitung mengembalikan hasil yang berbeda setiap kali dipanggil. Karena itu, tidak bisa disebut suci.



Dan inilah contoh lainnya:



let femaleCounter = 0;
let maleCounter = 0;
function isMale(user){
  if(user.sex = 'man'){
    maleCounter++;
    return true
  }
  return false
}


Yang ditampilkan di sini adalah fungsi isMaleyang, ketika diberikan argumen yang sama, selalu mengembalikan hasil yang sama. Tetapi itu memiliki efek samping. Yakni, kita berbicara tentang mengubah variabel global maleCounter. Akibatnya, fungsi ini tidak bisa disebut murni.



▍Mengapa fungsi murni dibutuhkan?



Mengapa kita menarik garis antara fungsi biasa dan fungsi murni? Intinya adalah fungsi murni memiliki banyak kekuatan. Penggunaannya dapat meningkatkan kualitas kode. Mari kita bicara tentang apa yang diberikan oleh fungsi murni kepada kita.



1. Kode fungsi murni lebih jelas daripada kode fungsi biasa, lebih mudah dibaca



Setiap fungsi murni ditujukan untuk tugas tertentu. Itu, dipanggil dengan input yang sama, selalu mengembalikan hasil yang sama. Ini sangat meningkatkan keterbacaan kode dan membuatnya lebih mudah untuk didokumentasikan.



2. Fungsi murni memungkinkan pengoptimalan yang lebih baik saat menyusun kodenya



Misalkan Anda memiliki potongan kode seperti ini:



for (int i = 0; i < 1000; i++){
    console.log(fun(10));
}


Jika fun- ini adalah fungsi yang tidak murni, maka selama eksekusi kode ini, fungsi ini harus dipanggil fun(10)1000 kali.



Dan jika itu funadalah fungsi murni, maka kompilator dapat mengoptimalkan kode tersebut. Ini mungkin terlihat seperti ini:



let result = fun(10)
for (int i = 0; i < 1000; i++){
    console.log(result);
}


3. Fungsi murni lebih mudah untuk diuji



Uji fungsi murni tidak boleh peka konteks. Saat menulis pengujian unit untuk fungsi murni, mereka hanya meneruskan beberapa nilai input ke fungsi tersebut dan memeriksa apa yang dikembalikan terhadap persyaratan tertentu.



Berikut contoh sederhananya. Fungsi murni mengambil larik angka sebagai argumen dan menambahkan 1 ke setiap elemen larik itu, mengembalikan larik baru. Berikut adalah representasi singkatnya:



const incrementNumbers = function(numbers){
  // ...
}


Untuk menguji fungsi seperti itu, cukup menulis pengujian unit yang menyerupai berikut ini:



let list = [1, 2, 3, 4, 5];
assert.equals(incrementNumbers(list), [2, 3, 4, 5, 6])


Jika suatu fungsi tidak bersih, maka untuk mengujinya, Anda perlu memperhitungkan banyak faktor eksternal yang dapat memengaruhi perilakunya. Akibatnya, menguji fungsi seperti itu akan lebih sulit daripada menguji yang murni.



Fungsi tingkat tinggi.



Fungsi tingkat tinggi adalah fungsi yang memiliki setidaknya salah satu dari kemampuan berikut:



  • Ia mampu menerima fungsi lain sebagai argumen.
  • Itu dapat mengembalikan fungsi sebagai hasil dari pekerjaannya.


Menggunakan fungsi tingkat tinggi memungkinkan Anda meningkatkan fleksibilitas kode Anda, membantu Anda menulis program yang lebih ringkas dan efisien.



Misalkan ada array bilangan bulat. Penting untuk membuat atas dasarnya sebuah larik baru dengan panjang yang sama, tetapi dengan demikian, setiap elemen yang akan mewakili hasil mengalikan dua elemen yang sesuai dari larik asli.



Jika Anda tidak menggunakan kemampuan fungsi tingkat tinggi, maka solusi untuk masalah ini mungkin terlihat seperti ini:



const arr1 = [1, 2, 3];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
    arr2.push(arr1[i] * 2);
}


Jika dipikir-pikir, ternyata objek tipe Arraydi JavaScript punya metode map(). Metode ini disebut sebagai map(callback). Ini membuat sebuah array baru yang diisi dengan elemen-elemen dari array yang dipanggilnya, diproses dengan fungsi yang diteruskan padanya callback.



Beginilah solusi untuk masalah ini menggunakan metode ini map():



const arr1 = [1, 2, 3];
const arr2 = arr1.map(function(item) {
  return item * 2;
});
console.log(arr2);


Metode map()adalah contoh dari fungsi orde tinggi.



Penggunaan yang benar dari fungsi tingkat tinggi membantu meningkatkan kualitas kode Anda. Pada bagian materi berikut ini, kita akan kembali ke fungsi seperti itu lebih dari sekali.



Hasil fungsi cache



Katakanlah Anda memiliki fungsi murni yang terlihat seperti ini:



function computed(str) {    
    // ,       
    console.log('2000s have passed')
      
    // ,     
    return 'a result'
}


Untuk meningkatkan kinerja kode, tidak ada salahnya kita menggunakan cache hasil kalkulasi yang dilakukan dalam fungsi tersebut. Saat Anda memanggil fungsi seperti itu dengan parameter yang sama dengan yang telah dipanggil, Anda tidak perlu melakukan perhitungan yang sama lagi. Sebagai gantinya, hasilnya, yang sebelumnya disimpan di cache, akan segera dikembalikan.



Bagaimana cara melengkapi suatu fungsi dengan cache? Untuk melakukan ini, Anda dapat menulis fungsi khusus yang dapat digunakan sebagai pembungkus untuk fungsi target. Kami akan memberi nama pada fungsi khusus ini cached. Fungsi ini mengambil fungsi objektif sebagai argumen dan mengembalikan fungsi baru. Dalam sebuah fungsi, cachedAnda bisa mengatur caching dari hasil panggilan ke fungsi yang membungkusnya menggunakan objek biasa ( Object) atau menggunakan objek yang merupakan struktur dataMap...



Seperti inilah tampilan kode fungsi cached:



function cached(fn){
  //     ,      fn.
  const cache = Object.create(null);

  //   fn,    .
  return function cachedFn (str) {

    //       -   fn
    if ( !cache[str] ) {
        let result = fn(str);

        // ,   fn,   
        cache[str] = result;
    }

    return cache[str]
  }
}


Berikut adalah hasil dari percobaan fitur ini pada konsol browser.





Bereksperimen dengan fungsi yang hasilnya disimpan dalam cache



Fungsi malas



Di badan fungsi, biasanya terdapat beberapa instruksi untuk memeriksa beberapa kondisi. Terkadang kondisi yang sesuai dengannya hanya perlu diperiksa sekali. Tidak ada gunanya memeriksanya setiap kali fungsi dipanggil.



Dalam keadaan seperti itu, Anda dapat meningkatkan kinerja fungsi dengan "menghapus" instruksi ini setelah dijalankan pertama kali. Akibatnya, ternyata fungsi tersebut, dengan panggilan berikutnya, tidak perlu melakukan pemeriksaan yang tidak lagi diperlukan. Ini akan menjadi fungsi "malas".



Misalkan kita perlu menulis fungsi fooyang selalu mengembalikan objek yang Datedibuat pertama kali fungsi itu dipanggil. Harap dicatat bahwa kita membutuhkan objek yang dibuat tepat saat fungsi tersebut dipanggil untuk pertama kalinya.



Kodenya mungkin terlihat seperti ini:



let fooFirstExecutedDate = null;
function foo() {
    if ( fooFirstExecutedDate != null) {
      return fooFirstExecutedDate;
    } else {
      fooFirstExecutedDate = new Date()
      return fooFirstExecutedDate;
    }
}


Setiap kali fungsi ini dipanggil, kondisinya harus diperiksa. Jika kondisi ini sangat sulit, maka pemanggilan fungsi tersebut akan mengakibatkan penurunan kinerja program. Di sinilah kita dapat menggunakan teknik membuat fungsi "malas" untuk mengoptimalkan kode.



Yakni, kita bisa menulis ulang fungsinya sebagai berikut:



var foo = function() {
    var t = new Date();
    foo = function() {
        return t;
    };
    return foo();
}


Setelah panggilan pertama ke fungsi tersebut, kami mengganti fungsi asli dengan yang baru. Fungsi baru ini mengembalikan nilai yang tdiwakili oleh objek yang Datedibuat saat pertama kali fungsi tersebut dipanggil. Akibatnya, tidak ada kondisi yang perlu diperiksa saat memanggil fungsi seperti itu. Pendekatan ini dapat meningkatkan kinerja kode Anda.



Ini adalah contoh bersyarat yang sangat sederhana. Sekarang mari kita lihat sesuatu yang lebih dekat dengan kenyataan.



Saat memasang event handler ke elemen DOM, Anda perlu melakukan pemeriksaan untuk memastikan solusinya kompatibel dengan browser modern dan IE:



function addEvent (type, el, fn) {
    if (window.addEventListener) {
        el.addEventListener(type, fn, false);
    }
    else if(window.attachEvent){
        el.attachEvent('on' + type, fn);
    }
}


Ternyata setiap kali kita memanggil suatu fungsi addEvent, suatu kondisi dicek di dalamnya, yang cukup untuk diperiksa hanya sekali, saat dipanggil pertama kali. Mari kita buat fungsi ini "malas":



function addEvent (type, el, fn) {
  if (window.addEventListener) {
      addEvent = function (type, el, fn) {
          el.addEventListener(type, fn, false);
      }
  } else if(window.attachEvent){
      addEvent = function (type, el, fn) {
          el.attachEvent('on' + type, fn);
      }
  }
  addEvent(type, el, fn)
}


Hasilnya, kita dapat mengatakan bahwa jika kondisi tertentu dicentang dalam suatu fungsi, yang harus dilakukan hanya sekali, maka dengan menerapkan teknik membuat fungsi "lazy", Anda dapat mengoptimalkan kode. Yaitu, pengoptimalan terdiri dari fakta bahwa setelah pemeriksaan kondisi pertama, fungsi asli diganti dengan yang baru, di mana tidak ada lagi pemeriksaan kondisi.



Fungsi kari



Currying adalah transformasi suatu fungsi, setelah menerapkan fungsi yang sebelumnya harus dipanggil dengan melewatkan beberapa argumen sekaligus berubah menjadi fungsi yang dapat dipanggil dengan meneruskan argumen yang diperlukan satu per satu.



Dengan kata lain, kita berbicara tentang fakta bahwa fungsi curried, yang membutuhkan beberapa argumen untuk bekerja dengan benar, mampu menerima argumen pertama dan mengembalikan fungsi yang mampu mengambil argumen kedua. Fungsi kedua ini, pada gilirannya, mengembalikan fungsi baru yang mengambil argumen ketiga dan mengembalikan fungsi baru. Ini akan berlanjut hingga jumlah argumen yang diperlukan diteruskan ke fungsi.



Apa gunanya ini?



  • Currying membantu menghindari situasi di mana suatu fungsi perlu dipanggil dengan meneruskan argumen yang sama berulang kali.
  • Teknik ini membantu menciptakan fungsi tingkat tinggi. Ini sangat berguna untuk menangani acara.
  • Berkat kari, Anda dapat mengatur persiapan awal fungsi untuk melakukan tindakan tertentu, dan kemudian dengan mudah menggunakan kembali fungsi tersebut dalam kode Anda.


Pertimbangkan fungsi sederhana yang menambahkan angka yang diteruskan padanya. Sebut saja add. Dibutuhkan tiga operan sebagai argumen dan mengembalikan jumlahnya:



function add(a,b,c){
 return a + b + c;
}


Fungsi seperti itu dapat dipanggil dengan memberikan argumen yang lebih sedikit daripada yang dibutuhkannya (meskipun ini akan mengarah pada fakta bahwa ia mengembalikan sesuatu yang sama sekali berbeda dari yang diharapkannya). Itu juga bisa dipanggil dengan lebih banyak argumen daripada yang disediakan saat dibuat. Dalam situasi seperti itu, argumen yang "tidak perlu" akan diabaikan begitu saja. Bereksperimen dengan fungsi serupa mungkin terlihat seperti ini:



add(1,2,3) --> 6 
add(1,2) --> NaN
add(1,2,3,4) --> 6 //  .


Bagaimana cara kari fungsi seperti itu?



Berikut adalah kode fungsi curryyang dimaksudkan untuk fungsi kari lainnya:



function curry(fn) {
    if (fn.length <= 1) return fn;
    const generator = (...args) => {
        if (fn.length === args.length) {

            return fn(...args)
        } else {
            return (...args2) => {

                return generator(...args, ...args2)
            }
        }
    }
    return generator
}


Berikut adalah hasil dari percobaan fitur ini pada konsol browser.





Bereksperimen dengan kari di konsol browser



Komposisi fungsi



Misalkan Anda perlu menulis fungsi yang, mengambil sebagai masukan, misalnya, string bitfish, mengembalikan string HELLO, BITFISH.



Seperti yang Anda lihat, fungsi ini memiliki dua tujuan:



  • Rangkaian string.
  • Mengonversi karakter dalam string yang dihasilkan menjadi huruf besar.


Seperti inilah kode untuk fungsi seperti itu:



let toUpperCase = function(x) { return x.toUpperCase(); };
let hello = function(x) { return 'HELLO, ' + x; };
let greet = function(x){
    return hello(toUpperCase(x));
};


Mari bereksperimen dengannya.





Menguji Fungsi di Browser Console



Tugas ini mencakup dua subtugas yang diatur sebagai fungsi terpisah. Hasilnya, kode fungsinyagreetcukup sederhana. Jika diperlukan untuk melakukan lebih banyak operasi pada string, maka fungsi tersebutgreetakan berisi konstruksi sepertifn3(fn2(fn1(fn0(x)))).



Mari sederhanakan solusi masalah dan tulis fungsi yang menyusun fungsi lain. Sebut sajacompose. Ini kodenya:



let compose = function(f,g) {
    return function(x) {
        return f(g(x));
    };
};


Sekarang fungsi tersebut greetdapat dibuat menggunakan fungsi compose:



let greet = compose(hello, toUpperCase);
greet('kevin');


Menggunakan fungsi composeuntuk membuat fungsi baru berdasarkan dua fungsi yang sudah ada berarti membuat fungsi yang memanggil fungsi tersebut dari kiri ke kanan. Hasilnya, kami mendapatkan kode ringkas yang mudah dibaca.



Sekarang fungsi kita composehanya membutuhkan dua parameter. Dan kami ingin dapat menerima sejumlah parameter.



Fungsi serupa, yang mampu menerima sejumlah parameter, tersedia di pustaka garis bawah open source yang terkenal .



function compose() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
        var i = start;
        var result = args[start].apply(this, arguments);
        while (i--) result = args[i].call(this, result);
        return result;
    };
};


Dengan menggunakan komposisi fungsi, Anda dapat membuat hubungan logis antar fungsi lebih mudah dipahami, meningkatkan keterbacaan kode Anda, dan meletakkan dasar untuk ekstensi dan pemfaktoran ulang di masa mendatang.



Apakah Anda menggunakan cara khusus untuk bekerja dengan fungsi dalam proyek JavaScript Anda?










All Articles