Panduan Pasukan Lucee MY

Closure dan arrow function dalam CFML

Kenali perbezaan closure dan arrow function dalam CFML: tangkapan skop variables lawan arguments, bila arrow tidak sesuai dan bila gelung biasa lebih jelas.

Closure dan arrow function dalam CFML
0
Kandungan

Closure dan arrow function dalam CFML

Closure ialah fungsi tanpa nama yang menangkap pemboleh ubah daripada skop tempat ia ditulis, dan arrow function ialah cara ringkas untuk menulis closure itu. Kedua-duanya melakukan kerja yang sama, tetapi cara mereka melihat pemboleh ubah sekeliling, terutamanya arguments, ialah punca paling kerap pepijat yang sukar dikesan. Artikel ini menjelaskan perbezaannya dengan contoh yang dijalankan pada pelayan Lucee sebenar, bukan dibaca daripada dokumentasi sahaja.

Apa itu closure

Closure, atau fungsi tanpa nama, ialah fungsi yang tidak diberi nama dan biasanya disimpan ke dalam pemboleh ubah atau dihantar terus sebagai argumen. Bentuk paling asas kelihatan begini:

<cfscript>
    f = function(numeric x) { return arguments.x * 2; };
    writeOutput("f(3)=" & f(3));
</cfscript>

Contoh di atas memulangkan f(3)=6. Fungsi f menerima satu parameter berjenis numeric, dan di dalam badannya arguments.x merujuk kepada parameter itu. Anda boleh menghantar closure ini ke fungsi lain seperti arrayMap, menyimpannya dalam struct, atau memulangkannya daripada fungsi lain. Asas menulis fungsi dan komponen dibincang dalam panduan fungsi dan komponen CFC.

Arrow function: sintaks ringkas dengan return tersirat

Arrow function membuang kata kunci function, pendakap, dan return untuk badan yang hanya satu ungkapan. Tulis parameter, anak panah =>, kemudian ungkapan yang nilainya dipulangkan secara automatik:

<cfscript>
    g = (x) => x + 1;
    writeOutput("g(41)=" & g(41));
</cfscript>

Ini memulangkan g(41)=42. Ungkapan x + 1 dipulangkan tanpa perlu menulis return. Anda juga boleh menulis jenis parameter seperti closure biasa:

<cfscript>
    tambah = (numeric x, numeric y) => x + y;
    writeOutput("tambah(2,3)=" & tambah(2, 3));
</cfscript>

Apabila badan memerlukan lebih daripada satu ungkapan, anda mesti membuka pendakap { } dan menulis return secara eksplisit, kerana arrow function tidak boleh meneka bila hendak memulangkan nilai:

<cfscript>
    h = (x) => { return x * x; };
    writeOutput("h(4)=" & h(4));
</cfscript>

Contoh terakhir memulangkan h(4)=16. Tanpa return, badan berpendakap itu memulangkan nilai null secara senyap.

Perbezaan closure dan arrow

Pada dasarnya arrow function ialah singkatan sintaks untuk closure, bukan jenis fungsi yang berbeza. Kedua-duanya dapat melihat pemboleh ubah luar, kedua-duanya mempunyai skop arguments sendiri, dan kedua-duanya boleh dihantar ke arrayMap dan rakan-rakannya. Beza yang ketara hanya pada cara menulis dan cara nilai dipulangkan:

AspekClosure function() {}Arrow function () =>
Kata kunciPerlu functionTiada, terus =>
Badan satu ungkapanPerlu returnPulang automatik
Badan berbilang barisPerlu returnPerlu pendakap dan return
Jenis parameterfunction(numeric x)(numeric x) =>
Skop argumentsSendiriSendiri

Kedua-dua bentuk ini paling kerap muncul bersama fungsi array. arrayMap menukar setiap elemen, arrayReduce melipat array menjadi satu nilai, dan arrayEach melintasi untuk kesan sampingan:

<cfscript>
    senarai = [1, 2, 3];
    kuasa2 = arrayMap(senarai, (x) => x * x);
    jumlah = arrayReduce(senarai, (a, b) => a + b, 0);
    jumlahEach = 0;
    senarai.each(function(x) { jumlahEach += x; });
    writeOutput("map=" & serializeJSON(kuasa2) & " | reduce=" & jumlah & " | each=" & jumlahEach);
</cfscript>

Outputnya map=[1,4,9] | reduce=6 | each=6. Rujukan penuh bagi fungsi ini ada dalam panduan fungsi array CFML, dan corak yang sama tersedia untuk koleksi pasangan nama dan nilai dalam fungsi struct CFML. Lucee juga menyokong rantaian gaya kaedah, jadi anda boleh menapis dahulu kemudian menukar dalam satu baris:

<cfscript>
    harga = [10, 20, 30, 40];
    hasil = harga.filter((n) => n >= 20).map((n) => n * 0.9);
    writeOutput(serializeJSON(hasil));
</cfscript>

Ini memulangkan [18,27,36]: elemen di bawah 20 dibuang, selebihnya didarab 0.9.

Penangkapan skop: variables lawan arguments

Inilah bahagian yang membezakan closure daripada fungsi biasa, dan tempat pembangun paling kerap tersilap. Closure menangkap pemboleh ubah local dan variables daripada skop tempat ia ditulis, walaupun fungsi yang menciptanya sudah selesai. Ini membolehkan corak seperti pembilang:

<cfscript>
    function kaunter() {
        var c = 0;
        return function() { c = c + 1; return c; };
    }
    k = kaunter();
    writeOutput("k1=" & k() & " k2=" & k() & " k3=" & k());
</cfscript>

Outputnya k1=1 k2=2 k3=3. Pemboleh ubah c hidup di dalam kaunter, tetapi closure yang dipulangkan masih boleh membaca dan menulisnya kerana c ditangkap oleh rujukan. Ini dipanggil penangkapan skop, dan itulah sebab utama closure wujud. Penjelasan penuh tentang bagaimana skop local, variables dan arguments berkelakuan ada dalam panduan memahami scope CFML.

Yang tidak ditangkap ialah arguments. Di dalam closure, arguments sentiasa merujuk kepada argumen closure itu sendiri, bukan argumen fungsi yang membungkusnya. Tulis function() { return arguments.nilai; } di dalam fungsi luar, dan anda akan mendapat ralat The key [NILAI] doesn't exist in the arguments scope. The existing keys are [] kerana closure itu dipanggil tanpa argumen. Arrow function berkelakuan sama, arguments di dalamnya juga milik arrow itu sendiri.

Cara betul ialah menangkap nilai itu ke pemboleh ubah tempatan terlebih dahulu, kemudian closure membacanya daripada penangkapan itu:

<cfscript>
    function bungkus(nilai) {
        var tangkap = nilai;
        return function() { return tangkap; };
    }
    f = bungkus(42);
    writeOutput(f());
</cfscript>

Ini memulangkan 42. Peraturan mudahnya: jika closure perlu membaca nilai daripada fungsi luar, salin ke var dahulu, jangan harapkan arguments luar tersedia di dalamnya.

Bila arrow function tidak sesuai

Arrow function bagus untuk ungkapan pendek, tetapi ada keadaan ia menjadi beban dan bukan bantuan.

Pertama, apabila badan memerlukan banyak langkah. Arrow function dengan pendakap masih berfungsi, tetapi kelebihannya, iaitu pulangan tersirat, hilang dan anda menulis return juga. Untuk logik berbilang langkah, closure bernama lebih jelas.

Kedua, apabila fungsi perlu memanggil dirinya sendiri secara rekursif. Arrow function tidak mempunyai nama, jadi rekursi mesti melalui nama pemboleh ubah yang ditetapkan kemudian, dan itu rapuh jika pemboleh ubah itu berubah.

Ketiga, apabila anda perlu menulis dokumentasi atau anotasi jenis yang panjang. Closure bernama memberi tempat yang jelas untuk komen, manakala arrow satu baris cenderung dipadatkan sehingga sukar dibaca selepas sebulan.

Akhirnya, apabila fungsi itu ialah kaedah komponen. Kaedah yang diisytiharkan dengan function jelas kelihatan sebagai sebahagian daripada komponen, manakala closure yang disimpan dalam variables atau this mudah terlepas pandang sebagai sebahagian daripada antara muka komponen itu.

Prestasi relatif

Arrow function dan closure dikompil menjadi bentuk yang hampir sama, jadi jangan pilih satu berbanding yang lain atas alasan kelajuan. Dalam ujian pada pelayan Lucee 7.1 yang memproses 200,000 elemen, gelung for, arrayMap dengan closure, dan arrayMap dengan arrow semuanya selesai dalam lingkungan yang sama, kira-kira 60 hingga 90 milisaat selepas pemanasan JIT, tanpa pemenang yang konsisten.

Beza prestasi sebenar muncul bukan antara closure dan arrow, tetapi antara gelung biasa dan fungsi array apabila badannya sangat ringkas. Gelung for mengelakkan satu panggilan fungsi setiap elemen, jadi ia boleh lebih pantas sedikit pada data besar, tetapi pada saiz data biasa perbezaan itu tidak ketara. Utamakan kebolehbacaan, bukan mikropengoptimuman pada paksi ini.

Bila gelung biasa lebih jelas

Fungsi array dengan closure sangat bagus untuk transformasi tulen, tetapi ia tidak selalunya alat yang tepat. Pilih gelung biasa dalam keadaan ini.

Apabila anda perlu berhenti awal. arrayEach tidak menyokong break, jadi gelung for dengan break ialah cara terus untuk berhenti sebaik sahaja syarat dipenuhi:

<cfscript>
    senarai = [3, 1, 4, 1, 5, 9];
    dijumpai = 0;
    for (i = 1; i <= arrayLen(senarai); i++) {
        if (senarai[i] == 4) { dijumpai = i; break; }
    }
    writeOutput("4 dijumpai pada indeks " & dijumpai);
</cfscript>

Ini memulangkan 4 dijumpai pada indeks 3. Gelung berhenti serta-merta dan tidak perlu melintasi baki array. Pelbagai bentuk gelung dibincang dalam panduan gelung dan kondisional.

Apabila anda memerlukan indeks semasa untuk logik, arrayEach memberi nilai dan indeks, tetapi membaca kod for (i = 1; i <= arrayLen(x); i++) selalunya lebih jujur kepada niat. Apabila anda perlu menyunting array yang sama semasa melintasi, atau apabila badan gelung mengubah pemboleh ubah luar secara banyak, gelung biasa menjadikan kesan sampingan itu kelihatan dan bukannya tersembunyi di dalam penutup.

Apabila transformasi itu melibatkan keadaan merentas elemen yang rumit, menulisnya sebagai gelung dengan nama pemboleh ubah yang jelas lebih mudah disemak daripada rangkaian map dan filter yang panjang. Closure ialah alat yang kuat, tetapi ayat yang sama boleh ditulis semula dengan gelung dan kadangkala gelung itulah yang patut pembaca lihat.

Soalan Lazim

Apakah perbezaan closure dan arrow function dalam CFML?

Arrow function ialah singkatan sintaks untuk closure. Closure memerlukan kata kunci function dan return, manakala arrow function dengan badan satu ungkapan memulangkan nilainya secara automatik. Dari segi tingkah laku skop, kedua-duanya sama.

Adakah arrow function menangkap arguments fungsi luar?

Tidak. Di dalam closure atau arrow function, arguments merujuk kepada argumen fungsi itu sendiri. Untuk membaca nilai fungsi luar, salin nilai itu ke var dahulu dan biarkan closure menangkap pemboleh ubah tempatan itu.

Bolehkah arrow function mempunyai badan berbilang baris?

Boleh, tetapi anda mesti membuka pendakap { } dan menulis return secara eksplisit. Tanpa return, badan berpendakap memulangkan null secara senyap.

Adakah arrow function lebih pantas daripada closure?

Tidak secara ketara. Kedua-duanya dikompil hampir sama. Dalam ujian 200,000 elemen, closure dan arrow selesai dalam masa yang hampir sama dengan gelung for selepas pemanasan JIT.

Bila patut saya guna gelung biasa dan bukan closure?

Guna gelung biasa apabila anda perlu break awal, memerlukan indeks semasa, menyunting array semasa melintasi, atau apabila kesan sampingan lebih jelas ditulis sebagai gelung daripada disembunyikan dalam penutup.

Sumber

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk