Rumah >hujung hadapan web >tutorial js >Data yang tidak berubah dan javascript berfungsi dengan mori
Pengaturcaraan fungsional dan data yang tidak berubah adalah tumpuan semasa bagi banyak pemaju JavaScript kerana mereka cuba mencari cara untuk menjadikan kod mereka lebih mudah dan mudah untuk alasan.
Walaupun JavaScript sentiasa menyokong beberapa teknik pengaturcaraan berfungsi, mereka hanya menjadi popular dalam beberapa tahun kebelakangan dan secara tradisinya tidak ada sokongan asli untuk data yang tidak berubah sama ada. JavaScript masih banyak belajar tentang kedua -dua dan idea -idea terbaik yang datang dari bahasa yang telah mencuba dan menguji teknik -teknik ini.
Di sudut lain dunia pengaturcaraan, Clojure adalah bahasa pengaturcaraan berfungsi yang didedikasikan untuk kesederhanaan yang tulen, terutama di mana struktur data berkenaan. Mori adalah perpustakaan yang membolehkan kita menggunakan struktur data berterusan Clojure secara langsung dari JavaScript.
Artikel ini akan meneroka rasional di sebalik reka bentuk struktur data ini dan memeriksa beberapa corak untuk menggunakannya untuk memperbaiki aplikasi kami. Kami juga boleh memikirkan ini sebagai batu loncatan pertama untuk pemaju JavaScript yang berminat dalam pengaturcaraan dengan Clojure atau Clojurescript.
Apakah data yang berterusan?
yang tidak dapat diubah dan nilai -nilai yang mempunyai jangka hayat temporal antara mutasi. Percubaan untuk mengubahsuai struktur data yang berterusan mengelakkan mutasi data yang mendasari dengan mengembalikan struktur baru dengan perubahan yang digunakan. Ia dapat membantu melihat apa perbezaan ini akan dilihat dalam bahasa pengaturcaraan teoritis.
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
kita dapat melihat bahawa senarai sementara telah bermutasi apabila kita menolak nilai ke atasnya. Kedua -dua A dan B menunjuk kepada nilai yang sama. Sebaliknya, panggilan menolak pada senarai berterusan mengembalikan nilai baru dan kita dapat melihat bahawa C dan D menunjuk kepada senarai diskret yang berbeza.
Struktur data yang berterusan ini tidak boleh dimutasi, yang bermaksud bahawa sebaik sahaja kita merujuk kepada nilai, kita juga mempunyai jaminan bahawa ia tidak akan pernah diubah. Jaminan ini umumnya membantu kami menulis kod yang lebih selamat dan mudah. Sebagai contoh, fungsi yang mengambil struktur data yang berterusan sebagai argumen tidak dapat bermutasi dan oleh itu jika fungsi itu ingin menyampaikan perubahan yang bermakna, ia mesti datang dari nilai pulangan. Ini membawa kepada penulisan fungsi yang telus, tulen, yang lebih mudah untuk menguji dan mengoptimumkan.
Lebih mudah, data yang tidak berubah memaksa kita untuk menulis lebih banyak kod berfungsi.
Mori menggunakan pengkompil Clojurescript untuk menyusun pelaksanaan untuk struktur data dalam perpustakaan standard Clojure ke JavaScript. Pengkompil mengeluarkan kod yang dioptimumkan, yang bermaksud bahawa tanpa pertimbangan tambahan, ia tidak mudah untuk berkomunikasi dengan clojure yang disusun dari JavaScript. Mori adalah lapisan pertimbangan tambahan.
Sama seperti Clojure, fungsi Mori dipisahkan dari struktur data yang mereka beroperasi, yang bertentangan dengan kecenderungan berorientasikan objek JavaScript. Kami akan mendapati bahawa perbezaan ini mengubah arah yang kami tulis kod.
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
Mori juga menggunakan perkongsian struktur untuk membuat perubahan yang cekap kepada data dengan berkongsi sebanyak mungkin struktur asal. Ini membolehkan struktur data yang berterusan hampir sama cekap seperti yang biasa. Pelaksanaan untuk konsep -konsep ini diliputi lebih terperinci dalam video ini.
Untuk memulakan, mari kita bayangkan kita cuba mengesan bug dalam asas JavaScript yang kita warisi. Kami membaca kod yang cuba mencari tahu mengapa kami telah berakhir dengan nilai yang salah untuk persekutuan.
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
Apakah nilai persekutuan apabila ia dilog masuk ke konsol?
tanpa menjalankan kod, atau membaca definisi untuk DeletePerson () tidak ada cara untuk mengetahui. Ia boleh menjadi array kosong. Ia boleh mempunyai tiga sifat baru. Kami berharap ia adalah array dengan elemen kedua yang dikeluarkan, tetapi kerana kami lulus dalam struktur data yang boleh berubah, tidak ada jaminan.
lebih buruk lagi, fungsi itu dapat menahan rujukan dan bermutasi secara tidak segerak pada masa akan datang. Semua rujukan kepada persekutuan dari sini dan seterusnya akan bekerja dengan nilai yang tidak dapat diramalkan.
Bandingkan ini dengan alternatif dengan Mori.
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
Tanpa mengira pelaksanaan DeletePerson (), kita tahu bahawa vektor asal akan dilog masuk, semata -mata kerana terdapat jaminan bahawa ia tidak boleh dimutasi. Sekiranya kita mahu fungsi itu berguna, maka ia harus mengembalikan vektor baru dengan item yang ditentukan dikeluarkan.
Memahami aliran melalui fungsi yang berfungsi pada data yang tidak berubah adalah mudah, kerana kita tahu bahawa satu -satunya kesan mereka adalah untuk memperoleh dan mengembalikan nilai yang tidak berubah yang berbeza.
Lebih mudah, data yang tidak berubah menguatkuasakan budaya ramalan.
dalam amalan
Kami akan menganggap anda sama ada mengikuti codepen, atau anda bekerja dalam persekitaran ES2015 dengan MORI dan HTML berikut.
Persediaan & Utiliti
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>mari kita mulakan dengan merosakkan fungsi yang kita perlukan dari ruang nama Mori.
Ini kebanyakannya pilihan gaya. Anda juga boleh menggunakan mana -mana fungsi dari Mori dengan mengaksesnya secara langsung pada objek Mori (mis. Mori.list ()).
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>Perkara pertama yang akan kami lakukan ialah menubuhkan fungsi pembantu untuk melihat struktur data berterusan kami. Perwakilan dalaman Mori tidak masuk akal dalam konsol, jadi kami akan menggunakan fungsi TOJS () untuk mengubahnya menjadi format yang dapat difahami.
kita boleh menggunakan fungsi ini sebagai alternatif kepada konsol.log () apabila kita perlu memeriksa struktur data Mori.
<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>Seterusnya kami akan menyediakan beberapa nilai konfigurasi dan fungsi utiliti.
Mudah -mudahan anda perasan bahawa fungsi TO2D () kami mengembalikan vektor. Vektor agak seperti susunan JavaScript dan menyokong akses rawak yang cekap.
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>Penstrukturan data
Kami akan menggunakan fungsi TO2D () kami untuk membuat urutan koordinat yang akan mewakili semua piksel pada kanvas.
kami menggunakan fungsi julat () untuk menghasilkan urutan nombor antara 0 dan ketinggian * lebar (dalam kes kami 100) dan kami menggunakan peta () untuk mengubahnya menjadi senarai koordinat 2D dengan TO2D kami () Fungsi pembantu.
<span>const { </span> list<span>, vector, peek, pop, conj, map, assoc, zipmap, </span> range<span>, repeat, each, count, intoArray, toJs </span><span>} = mori; </span>Ia mungkin membantu untuk memvisualisasikan struktur koordinasi.
Ia adalah urutan satu dimensi vektor koordinat.
<span>const log = (<span>...args</span>) => { </span> <span>console.log(...args.map(toJs)) </span><span>}; </span>bersama setiap koordinat, kami juga ingin menyimpan nilai warna.
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
Kami menggunakan fungsi ulangan () untuk membuat urutan tak terhingga rentetan '#FFF'. Kami tidak perlu bimbang tentang ingatan ini dan merosakkan penyemak imbas kami, kerana urutan Mori menyokong penilaian malas . Kami hanya akan mengira nilai -nilai item dalam urutan apabila kami meminta mereka kemudian.
Akhirnya kami ingin menggabungkan koordinat kami dengan warna kami dalam bentuk peta hash.
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
Kami menggunakan fungsi ZipMap () untuk membuat peta hash, dengan koordinasi sebagai kunci dan warna sebagai nilai. Sekali lagi, ia mungkin membantu memvisualisasikan struktur data kami.
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
Tidak seperti objek JavaScript, peta hash Mori boleh mengambil apa -apa jenis data sebagai kunci.
Untuk menukar warna piksel kita akan mengaitkan salah satu koordinat dalam peta hash kami dengan rentetan baru. Mari kita tulis fungsi tulen yang mewarnai piksel tunggal.
<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
Kami menggunakan koordinat X dan Y untuk membuat vektor koordinat yang boleh kita gunakan sebagai kunci, maka kami menggunakan Assoc () untuk mengaitkan kunci itu dengan warna baru. Ingatlah bahawa kerana struktur data berterusan, fungsi Assoc () akan mengembalikan peta hash baru, dan bukannya bermutasi yang lama.
Sekarang kita mempunyai segala yang kita perlukan untuk menarik imej mudah ke kanvas. Mari buat fungsi yang mengambil peta hash koordinat terhadap piksel dan menariknya ke RenderingContext2D.
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>
mari kita ambil satu minit untuk memahami apa yang berlaku di sini.
Kami menggunakan setiap () untuk melangkah ke atas peta hash piksel kami. Ia melepasi setiap kunci dan nilai (bersama -sama sebagai urutan) ke dalam fungsi panggil balik sebagai p. Kemudian kami menggunakan fungsi keArray () untuk mengubahnya menjadi tatasusunan yang boleh dimusnahkan, jadi kami dapat memilih nilai yang kami inginkan.
<span>const { </span> list<span>, vector, peek, pop, conj, map, assoc, zipmap, </span> range<span>, repeat, each, count, intoArray, toJs </span><span>} = mori; </span>
Akhirnya kita menggunakan kaedah kanvas untuk melukis segi empat tepat berwarna ke konteks itu sendiri.
<span>const log = (<span>...args</span>) => { </span> <span>console.log(...args.map(toJs)) </span><span>}; </span>
Sekarang kita perlu melakukan sedikit paip hanya untuk mendapatkan semua bahagian ini bersama -sama dan bekerja.
<span>// the dimensions of the canvas </span><span>const [height, width] = [20, 20]; </span> <span>// the size of each canvas pixel </span><span>const pixelSize = 10; </span> <span>// converts an integer to a 2d coordinate vector </span><span>const to2D = (i) => vector( </span> i <span>% width, </span> <span>Math.floor(i / width) </span><span>); </span>
Kami akan memegang kanvas dan menggunakannya untuk membuat konteks untuk menjadikan imej kami. Kami juga akan mengubah saiznya dengan tepat untuk mencerminkan dimensi kami.
Akhirnya kami akan lulus konteks kami dengan piksel kami untuk ditarik oleh kaedah cat. Dengan apa -apa nasib, kanvas anda harus diberikan sebagai piksel putih. Bukan yang paling menarik mendedahkan, tetapi kami semakin dekat.
kami ingin mendengar acara klik dan menggunakannya untuk menukar warna piksel tertentu dengan fungsi cabutan kami () dari sebelumnya.
<span>const coords = map(to2D, range(height * width)); </span>
Kami melampirkan pendengar klik ke kanvas kami dan menggunakan koordinat acara untuk menentukan pixel mana yang hendak dilukis. Kami menggunakan maklumat ini untuk membuat peta hash piksel baru dengan fungsi cabutan kami (). Kemudian kami melukisnya ke dalam konteks kami dan menimpa bingkai terakhir yang kami tarik.
Pada ketika ini kita dapat menarik piksel hitam ke dalam kanvas dan setiap bingkai akan berdasarkan pada sebelumnya, mewujudkan imej komposit.
Untuk melaksanakan Undo, kami ingin menyimpan setiap semakan bersejarah ke peta hash piksel, jadi kami dapat mengambilnya lagi pada masa akan datang.
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
Kami menggunakan senarai untuk menyimpan "bingkai" yang berbeza yang kami tarik. Senarai tambahan sokongan yang cekap di kepala dan o (1) cari item pertama, yang menjadikannya ideal untuk mewakili susunan.
Kami perlu mengubah suai pendengar klik kami untuk bekerja dengan timbunan bingkai kami.
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
Kami menggunakan fungsi mengintip () untuk mendapatkan bingkai di bahagian atas timbunan. Kemudian kami menggunakannya untuk membuat bingkai baru dengan fungsi Draw (). Akhirnya kami menggunakan conj () untuk conjoin bingkai baru ke bahagian atas timbunan bingkai.
Walaupun kita menukar keadaan tempatan (bingkai = conj (bingkai, newframe)) kita sebenarnya tidak bermutasi apa -apa data.
Akhirnya kita perlu melaksanakan butang Undo untuk muncul bingkai atas dari timbunan kami.
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
Apabila butang Undo diklik, kami periksa sama ada terdapat sebarang bingkai untuk dibatalkan, kemudian gunakan fungsi pop () untuk menggantikan bingkai dengan senarai baru yang tidak lagi termasuk bingkai atas.
Akhirnya kita lulus bingkai atas pada timbunan baru ke fungsi cat () kami untuk mencerminkan perubahan. Pada ketika ini, anda harus dapat menarik dan membatalkan perubahan pada kanvas.
inilah yang kita berakhir dengan:
lihat piksel pena mori oleh sitepoint (@sitePoint) pada codepen.
Berikut adalah senarai idea untuk cara anda dapat memperbaiki aplikasi ini:
Kami telah melihat vektor, senarai, julat dan peta hash, tetapi Mori juga dilengkapi dengan set, set dan beratur yang disusun dan setiap struktur data ini dilengkapi dengan fungsi polimorfik untuk bekerja dengan mereka.
Kami hampir tidak menggaru permukaan apa yang mungkin, tetapi diharapkan anda akan melihat cukup untuk menghargai kepentingan berpasangan data yang berterusan dengan satu set fungsi mudah yang kuat.Ketidakhadiran dalam JavaScript merujuk kepada keadaan objek yang tidak dapat diubah suai selepas ia dibuat. Ini bermakna sekali pembolehubah diberikan nilai, ia tidak boleh diubah. Konsep ini sangat penting dalam pengaturcaraan berfungsi kerana ia membantu mengelakkan kesan sampingan dan menjadikan kod anda lebih diramalkan dan lebih mudah difahami. Ia juga meningkatkan prestasi aplikasi anda dengan membenarkan penggunaan semula data dan penggunaan memori yang cekap. Struktur data berterusan ke dalam JavaScript. Struktur data ini tidak berubah, bermakna mereka tidak dapat diubah sebaik sahaja ia dicipta. Ini membantu mengekalkan integriti data dan mengelakkan pengubahsuaian yang tidak disengajakan. MORI juga menyediakan satu set utiliti pengaturcaraan berfungsi yang kaya yang menjadikannya lebih mudah untuk memanipulasi struktur data ini. Adakah menyediakan kaedah untuk mengendalikan data yang tidak berubah, Mori menawarkan cara yang lebih cekap dan teguh untuk melakukannya. Struktur data berterusan Mori lebih cepat dan memakan memori yang kurang daripada kaedah JavaScript asli. Selain itu, Mori menyediakan pelbagai utiliti pengaturcaraan berfungsi yang tidak terdapat dalam JavaScript. . Oleh kerana objek yang tidak berubah tidak dapat diubah sebaik sahaja dibuat, mereka boleh digunakan semula dengan selamat merentasi pelbagai fungsi tanpa risiko diubah suai. Ini membawa kepada penggunaan memori yang cekap dan pengambilan data yang lebih cepat, dengan itu meningkatkan prestasi keseluruhan aplikasi. diubah selepas mereka dicipta. Sebaliknya, struktur data yang tidak berubah tidak boleh diubahsuai sebaik sahaja ia dicipta. Mana -mana operasi pada struktur data yang tidak berubah menghasilkan struktur data baru.
Atas ialah kandungan terperinci Data yang tidak berubah dan javascript berfungsi dengan mori. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!