Rumah > Artikel > hujung hadapan web > Pelajari Pepijat JScript dan Pengurusan Memori daripada kemahiran me_javascript
1. Pepijat JScript
Pelaksanaan ECMAScript JScript IE secara serius mengelirukan ungkapan fungsi bernama, menyebabkan ramai orang menentang ungkapan fungsi yang dinamakan, malah versi yang masih digunakan (versi 5.8 digunakan dalam IE8) masih wujud Soalan berikut.
Mari kita lihat apakah kesilapan yang dilakukan oleh IE dalam pelaksanaannya. Mari kita lihat contoh berikut:
Contoh 1: Pengecam ungkapan fungsi bocor ke skop luar
var f = function g(){}; typeof g; // "function"
Kami berkata sebelum ini bahawa pengecam ungkapan fungsi yang dinamakan tidak sah dalam skop luaran, tetapi JScript jelas melanggar spesifikasi ini Pengecam g dalam contoh di atas dihuraikan ke dalam objek fungsi, yang berantakan -untuk-mencari pepijat disebabkan oleh sebab ini.
Nota: Masalah ini nampaknya telah dibetulkan dalam IE9
Contoh 2: Anggap ungkapan fungsi bernama sebagai pengisytiharan fungsi dan ungkapan fungsi
typeof g; // "function" var f = function g(){};
Dalam persekitaran ciri, pengisytiharan fungsi akan dihuraikan sebelum sebarang ungkapan Contoh di atas menunjukkan bahawa JScript sebenarnya menganggap ungkapan fungsi yang dinamakan sebagai pengisytiharan fungsi kerana ia menghuraikan g sebelum pengisytiharan sebenar.
Contoh ini membawa kepada contoh seterusnya.
Contoh 3: Ungkapan fungsi bernama mencipta dua objek fungsi yang berbeza sama sekali!
var f = function g(){}; f === g; // false f.expando = 'foo'; g.expando; // undefined
Melihat ini, semua orang akan menganggap bahawa masalahnya adalah serius, kerana mengubah suai mana-mana objek tidak akan mengubah yang lain Ini terlalu jahat. Melalui contoh ini, kita boleh mendapati bahawa mencipta dua objek yang berbeza, iaitu, jika anda ingin mengubah suai atribut f untuk menyimpan maklumat tertentu, dan kemudian menggunakannya sebagai satu perkara dengan merujuk atribut nama yang sama bagi g daripada objek yang sama, maka akan ada masalah besar kerana ia adalah mustahil.
Mari kita lihat contoh yang lebih rumit:
Contoh 4: Hanya menghuraikan pengisytiharan fungsi secara berurutan dan mengabaikan blok pernyataan bersyarat
var f = function g() { return 1; }; if (false) { f = function g(){ return 2; }; } g(); // 2
Pepijat ini jauh lebih sukar ditemui, tetapi punca pepijat adalah sangat mudah. Pertama, g dihuraikan sebagai pengisytiharan fungsi Memandangkan pengisytiharan fungsi dalam JScript tidak tertakluk kepada blok kod bersyarat, dalam cabang jika jahat ini, g dianggap sebagai fungsi lain g(){ return 2 }, juga Ia baru sahaja diisytiharkan semula. . Kemudian, semua ungkapan "biasa" dinilai, dan f diberi rujukan kepada objek lain yang baru dibuat. Memandangkan cabang jika keji "" tidak akan dimasukkan apabila ungkapan dinilai, f akan terus merujuk kepada fungsi fungsi pertama g(){ return 1 }. Selepas menganalisis ini, masalahnya adalah sangat jelas: jika anda tidak cukup berhati-hati dan panggil g dalam f, objek fungsi g yang tidak berkaitan akan dipanggil
.Anda mungkin bertanya, apakah perbezaan apabila membandingkan objek berbeza dengan arguments.callee? Mari lihat:
var f = function g(){ return [ arguments.callee == f, arguments.callee == g ]; }; f(); // [true, false] g(); // [false, true]
Seperti yang anda lihat, rujukan arguments.callee sentiasa dipanggil fungsi Malah, ini juga merupakan perkara yang baik, seperti yang akan dijelaskan kemudian.
Satu lagi contoh menarik ialah menggunakan ungkapan fungsi bernama dalam pernyataan tugasan yang tidak mengandungi pengisytiharan:
(function(){ f = function f(){}; })();
Menurut analisis kod, kami pada asalnya ingin mencipta atribut global f (berhati-hati untuk tidak mengelirukannya dengan fungsi tanpa nama umum, yang menggunakan pengisytiharan bernama JScript yang membuat kucar-kacir di sini Ungkapan dihuraikan sebagai pengisytiharan fungsi, jadi f di sebelah kiri diisytiharkan sebagai pembolehubah tempatan (sama seperti pengisytiharan dalam fungsi tanpa nama umum Kemudian apabila fungsi itu dilaksanakan, f sudah ditakrifkan, dan fungsi f(). ) di sebelah kanan {} ditugaskan terus kepada pembolehubah tempatan f, jadi f bukan atribut global sama sekali.
Setelah memahami betapa tidak normalnya JScript, kita mesti menghalang masalah ini tepat pada masanya, pertama, mengelakkan pengecam daripada bocor ke dalam skop luaran. Kedua, pengecam yang digunakan sebagai nama fungsi tidak boleh disebut dalam contoh yang menjengkelkan g? ——Jika kita boleh berpura-pura bahawa g tidak wujud, berapa banyak masalah yang tidak perlu boleh dielakkan. Oleh itu, kuncinya ialah sentiasa merujuk kepada fungsi melalui f atau arguments.callee. Jika anda menggunakan ungkapan fungsi bernama, anda hanya perlu menggunakan nama itu semasa menyahpepijat. Akhir sekali, ingat untuk membersihkan fungsi yang telah dibuat secara tidak betul semasa pengisytiharan ungkapan fungsi bernama.
2. Pengurusan memori JScript
Setelah mengetahui pepijat penghuraian kod bukan standard ini, jika kita menggunakannya, kita akan mendapati bahawa sebenarnya terdapat masalah dengan ingatan Mari kita lihat contoh:
var f = (function(){ if (true) { return function g(){}; } return function g(){}; })();
我们知道,这个匿名函数调用返回的函数(带有标识符g的函数),然后赋值给了外部的f。我们也知道,命名函数表达式会导致产生多余的函数对象,而该对象与返回的函数对象不是一回事。所以这个多余的g函数就死在了返回函数的闭包中了,因此内存问题就出现了。这是因为if语句内部的函数与g是在同一个作用域中被声明的。这种情况下 ,除非我们显式断开对g函数的引用,否则它一直占着内存不放。
var f = (function(){ var f, g; if (true) { f = function g(){}; } else { f = function g(){}; } // 设置g为null以后它就不会再占内存了 g = null; return f; })();
通过设置g为null,垃圾回收器就把g引用的那个隐式函数给回收掉了,为了验证我们的代码,我们来做一些测试,以确保我们的内存被回收了。
测试
测试很简单,就是命名函数表达式创建10000个函数,然后把它们保存在一个数组中。等一会儿以后再看这些函数到底占用了多少内存。然后,再断开这些引用并重复这一过程。下面是测试代码:
function createFn(){ return (function(){ var f; if (true) { f = function F(){ return 'standard'; }; } else if (false) { f = function F(){ return 'alternative'; }; } else { f = function F(){ return 'fallback'; }; } // var F = null; return f; })(); } var arr = [ ]; for (var i=0; i < 10000; i++) { arr[i] = createFn(); }
通过运行在Windows XP SP2中的任务管理器可以看到如下结果:
IE7: without `null`: 7.6K -> 20.3K with `null`: 7.6K -> 18K IE8: without `null`: 14K -> 29.7K with `null`: 14K -> 27K
如我们所料,显示断开引用可以释放内存,但是释放的内存不是很多,10000个函数对象才释放大约3M的内存,这对一些小型脚本不算什么,但对于大型程序,或者长时间运行在低内存的设备里的时候,这是非常有必要的。
以上就是关于JScript的Bug与内存管理的全部介绍,希望对大家的学习有所帮助。