
Dalam artikel ini, metode <em>eval</em>
KOTOR, tidak aman, tidak stabil, dan menakutkan akan dijelaskan. Jadi, jika Anda tidak nyaman dengan itu, berhentilah membaca sekarang.
Pertama, beberapa masalah dengan kenyamanan tetap tidak terselesaikan: dalam kode yang dikirim ke pekerja web web, penutupan tidak dapat digunakan.
Kita semua menyukai teknologi baru, dan kita semua menyukai teknologi baru agar nyaman digunakan. Namun, tidak demikian halnya dengan pekerja web. pekerja web menerima file atau tautan ke file, yang tidak nyaman. Akan lebih baik untuk dapat menempatkan tugas apa pun ke pekerja web, bukan hanya kode yang direncanakan secara khusus.
Apa yang kita butuhkan untuk membuat pekerja web lebih nyaman untuk beroperasi? Saya percaya, berikut ini:
- Kemungkinan untuk meluncurkan kode apa pun pada pekerja web kapan saja
- Kemungkinan untuk mengirim data rumit kepada pekerja web (instance kelas, fungsi)
- Kemungkinan untuk menerima janji dengan balasan dari pekerja web.
Mari kita coba menulisnya. Sebagai permulaan, kita akan memerlukan protokol komunikasi antara pekerja web dan jendela utama. Secara umum, protokol hanyalah struktur dan jenis data yang digunakan untuk komunikasi antara jendela browser dan pekerja web. Cukup mudah. Anda dapat menggunakan ini atau menulis versi Anda sendiri. Setiap pesan akan memiliki ID dan data tipikal dari tipe pesan tertentu. Awalnya, kami akan memiliki dua jenis pesan untuk pekerja web:
- Menambahkan perpustakaan / file ke pekerja web
- Luncurkan.
File yang akan berada di dalam pekerja web
Sebelum menulis pekerja web, kita perlu menjelaskan file yang akan ada di dalamnya, mendukung protokol yang dijelaskan di atas. Saya suka pemrograman berorientasi objek (OOP), jadi ini akan menjadi kelas bernama workerBody. Kelas ini harus berlangganan ke suatu acara dari jendela induk.
self.onmessage = (message) => { this.onMessage(message.data); };
Sekarang kita dapat mendengarkan acara dari jendela induk. Kami memiliki dua jenis acara: peristiwa yang menyiratkan balasan dan yang lainnya. Mari kita lakukan acara: \
Perpustakaan dan file ditambahkan ke pekerja web menggunakan API imporScripts .
Dan sekarang bagian yang paling menakutkan: untuk meluncurkan fungsi acak, kita akan menggunakan eval .
... onMessage(message) { switch (message.type) { case MESSAGE_TYPE.ADD_LIBS: this.addLibs(message.libs); break; case MESSAGE_TYPE.WORK: this.doWork(message); break; } } doWork(message) { try { const processor = eval(message.job); const params = this._parser.parse(message.params); const result = processor(params); if (result && result.then && typeof result.then === 'function') { result.then((data) => { this.send({ id: message.id, state: true, body: data }); }, (error) => { if (error instanceof Error) { error = String(error); } this.send({ id: message.id, state: false, body: error }); }); } else { this.send({ id: message.id, state: true, body: result }); } } catch (e) { this.send({ id: message.id, state: false, body: String(e) }); } } send(data) { data.body = this._serializer.serialize(data.body); try { self.postMessage(data); } catch (e) { const toSet = { id: data.id, state: false, body: String(e) }; self.postMessage(toSet); } }
Metode onMessage bertanggung jawab untuk menerima pesan dan memilih penangan, doWork meluncurkan fungsi terkirim dan mengirim mengirim balasan ke jendela induk.
Parser dan serializer
Sekarang kita memiliki konten pekerja web, kita perlu belajar membuat cerita bersambung dan menguraikan data apa pun, sehingga data tersebut dapat dikirim ke pekerja web. Mari kita mulai dengan serializer. Kami ingin dapat mengirim data apa pun kepada pekerja web, termasuk instance kelas, kelas, dan fungsi, sementara kapasitas asli pekerja web memungkinkan pengiriman hanya data seperti JSON. Untuk mengatasinya, kita perlu _ eval _. Kami akan membungkus semua data yang JSON tidak dapat terima ke dalam struktur sengatan yang sesuai dan meluncurkannya di sisi lain. Untuk menjaga keabadian, data yang diterima akan dikloning dengan cepat, menggantikan apa pun yang tidak dapat diserialisasi dengan metode biasa dengan objek layanan, yang akan diganti kembali di sisi lain oleh parser. Pada pandangan pertama, tugas ini tidak sulit, tetapi ada banyak jebakan. Keterbatasan paling menakutkan dari pendekatan ini adalah ketidakmampuan untuk menggunakan penutupan, yang mengarah ke gaya penulisan kode yang sedikit berbeda. Mari kita mulai dengan bagian yang paling mudah, fungsinya. Pertama, kita perlu belajar membedakan fungsi dari konstruktor kelas. Ayo lakukan itu:
static isFunction(Factory){ if (!Factory.prototype) {
Pertama, kami akan memeriksa apakah fungsi tersebut memiliki prototipe. Jika tidak, ini jelas sebuah fungsi. Kemudian, kita melihat jumlah fitur prototipe. Jika hanya memiliki konstruktor dan fungsinya bukan penerus kelas lain, kami menganggapnya sebagai fungsi.
Ketika kami menemukan suatu fungsi, kami hanya menggantinya dengan objek layanan dengan bidang __type = "serialized-function" dan templat bersesuaian dengan templat fungsi ini (func.toString ()).
Untuk saat ini, kami akan melewati kelas dan melihat instance kelas. Kemudian, kita perlu membedakan antara objek reguler dan instance kelas.
static isInstance(some) { const constructor = some.constructor; if (!constructor) { return false; } return !Serializer.isNative(constructor); } static isNative(data) { return /function .*?\(\) \{ \[native code\] \}/.test(data.toString()); }
Kami percaya bahwa suatu objek adalah teratur jika tidak memiliki konstruktor atau konstruktornya adalah fungsi asli. Setelah kami menemukan instance kelas, kami akan menggantinya dengan objek layanan dengan bidang berikut:
- __type: 'serialized-instance'
- data adalah data yang terkandung dalam instance
- indeks adalah indeks kelas dari instance ini pada daftar kelas layanan.
Untuk mengirim data, kita harus membuat bidang tambahan, di mana kita akan menyimpan daftar kelas unik yang kita kirim. Namun, ada tantangan: menemukan kelas, kita tidak hanya perlu mengambil templatnya, tetapi juga templat dari semua kelas induk dan menyimpannya sebagai kelas independen, sehingga setiap kelas induk hanya dikirim satu kali, juga menyimpan instanceof bukti. Menemukan kelas itu mudah: ini adalah fungsi yang gagal pada bukti Serializer.isFungsi kami. Saat menambahkan kelas, kami memeriksa keberadaan kelas itu pada daftar data berseri dan hanya menambahkan kelas unik. Kode yang merakit kelas menjadi templat cukup besar dan tersedia di sini .
Di parser, kami awalnya memeriksa semua kelas yang dikirimkan kepada kami dan mengkompilasinya jika belum dikirim. Kemudian, kami secara rekursif memeriksa setiap bidang data dan mengganti objek layanan dengan data yang dikompilasi. Bagian yang paling menarik adalah instance kelas. Kami memiliki kelas dan data yang ada dalam instance-nya, tetapi kami tidak bisa hanya membuat instance karena permintaan konstruktor mungkin memerlukan parameter yang tidak kami miliki. Kami mendapatkannya dari metode Object.create yang hampir terlupakan, yang menciptakan objek dengan prototipe yang ditetapkan. Dengan cara ini, kami menghindari meminta konstruktor, mendapatkan instance kelas dan hanya menyalin properti ke instance.
Menciptakan pekerja web
Agar pekerja web dapat beroperasi dengan sukses, kami membutuhkan parser dan serializer di dalam pekerja web dan di luar. Jadi kami mengambil serializer dan mengubahnya, parser dan badan pekerja web menjadi templat. Dari templat, kami membuat gumpalan dan membuat tautan unduhan melalui URL.createObjectURL (metode ini mungkin tidak berfungsi untuk beberapa โKebijakan Keamanan Kontenโ). Metode ini juga baik untuk meluncurkan kode acak dari sebuah string.
_createworker(customworker) { const template = `var Myworker = ${this._createTemplate(customworker)};`; const blob = new Blob([template], { type: 'application/javascript' }); return new worker(URL.createObjectURL(blob)); } _createTemplate(workerBody) { const Name = Serializer.getFnName(workerBody); if (!Name) { throw new Error('Unnamed worker Body class! Please add name to worker Body class!'); } return [ '(function () {', this._getFullClassTemplate(Serializer, 'Serializer'), this._getFullClassTemplate(Parser, 'Parser'), this._getFullClassTemplate(workerBody, 'workerBody'), `return new workerBody(Serializer, Parser)})();` ].join('\n'); }
Hasil
Jadi, kami memiliki pustaka sederhana yang dapat digunakan yang dapat mengirim kode apa pun ke pekerja web. Ini mendukung kelas-kelas TypeScript, misalnya:
const wrapper = workerWrapper.create(); wrapper.process((params) => {
Perkembangan masa depan
Sayangnya, perpustakaan ini jauh dari ideal. Kita perlu menambahkan dukungan setter dan getter untuk kelas, objek, prototipe, dan fitur statis. Juga, kita perlu menambahkan caching, peluncuran skrip alternatif tanpa eval , menggunakan URL.createObjectURL
sebagai gantinya. Akhirnya, file dengan konten pekerja web perlu ditambahkan ke majelis (jika penciptaan on-the-fly tidak tersedia) dll. Kunjungi repositori !