跟我學(xué)習(xí)JScript的Bug與內(nèi)存管理
1、JScript的Bug
IE的ECMAScript實(shí)現(xiàn)JScript嚴(yán)重混淆了命名函數(shù)表達(dá)式,搞得現(xiàn)很多人都出來反對(duì)命名函數(shù)表達(dá)式,而且即便是現(xiàn)在還一直在用的一版(IE8中使用的5.8版)仍然存在下列問題。
下面我們就來看看IE在實(shí)現(xiàn)中究竟犯了那些錯(cuò)誤,俗話說知已知彼,才能百戰(zhàn)不殆。我們來看看如下幾個(gè)例子:
例1:函數(shù)表達(dá)式的標(biāo)示符泄露到外部作用域
var f = function g(){};
typeof g; // "function"
前面我們說過,命名函數(shù)表達(dá)式的標(biāo)示符在外部作用域是無效的,但JScript明顯是違反了這一規(guī)范,上面例子中的標(biāo)示符g被解析成函數(shù)對(duì)象,這就亂了套了,很多難以發(fā)現(xiàn)的bug都是因?yàn)檫@個(gè)原因?qū)е碌摹?/p>
注:IE9以后貌似已經(jīng)修復(fù)了這個(gè)問題
例2:將命名函數(shù)表達(dá)式同時(shí)當(dāng)作函數(shù)聲明和函數(shù)表達(dá)式
typeof g; // "function"
var f = function g(){};
特性環(huán)境下,函數(shù)聲明會(huì)優(yōu)先于任何表達(dá)式被解析,上面的例子展示的是JScript實(shí)際上是把命名函數(shù)表達(dá)式當(dāng)成函數(shù)聲明了,因?yàn)樗趯?shí)際聲明之前就解析了g。
這個(gè)例子引出了下一個(gè)例子。
例3:命名函數(shù)表達(dá)式會(huì)創(chuàng)建兩個(gè)截然不同的函數(shù)對(duì)象!
var f = function g(){};
f === g; // false
f.expando = 'foo';
g.expando; // undefined
看到這里,大家會(huì)覺得問題嚴(yán)重了,因?yàn)樾薷娜魏我粋€(gè)對(duì)象,另外一個(gè)沒有什么改變,這太惡了。通過這個(gè)例子可以發(fā)現(xiàn),創(chuàng)建2個(gè)不同的對(duì)象,也就是說如果你想修改f的屬性中保存某個(gè)信息,然后想當(dāng)然地通過引用相同對(duì)象的g的同名屬性來使用,那問題就大了,因?yàn)楦揪筒豢赡堋?/p>
再來看一個(gè)稍微復(fù)雜的例子:
例4:僅僅順序解析函數(shù)聲明而忽略條件語句塊
var f = function g() {
return 1;
};
if (false) {
f = function g(){
return 2;
};
}
g(); // 2
這個(gè)bug查找就難多了,但導(dǎo)致bug的原因卻非常簡(jiǎn)單。首先,g被當(dāng)作函數(shù)聲明解析,由于JScript中的函數(shù)聲明不受條件代碼塊約束,所以在這個(gè)很惡的if分支中,g被當(dāng)作另一個(gè)函數(shù)function g(){ return 2 },也就是又被聲明了一次。然后,所有“常規(guī)的”表達(dá)式被求值,而此時(shí)f被賦予了另一個(gè)新創(chuàng)建的對(duì)象的引用。由于在對(duì)表達(dá)式求值的時(shí)候,永遠(yuǎn)不會(huì)進(jìn)入“這個(gè)可惡if分支,因此f就會(huì)繼續(xù)引用第一個(gè)函數(shù)function g(){ return 1 }。分析到這里,問題就很清楚了:假如你不夠細(xì)心,在f中調(diào)用了g,那么將會(huì)調(diào)用一個(gè)毫不相干的g函數(shù)對(duì)象。
你可能會(huì)問,將不同的對(duì)象和arguments.callee相比較時(shí),有什么樣的區(qū)別呢?我們來看看:
var f = function g(){
return [
arguments.callee == f,
arguments.callee == g
];
};
f(); // [true, false]
g(); // [false, true]
可以看到,arguments.callee的引用一直是被調(diào)用的函數(shù),實(shí)際上這也是好事,稍后會(huì)解釋。
還有一個(gè)有趣的例子,那就是在不包含聲明的賦值語句中使用命名函數(shù)表達(dá)式:
(function(){
f = function f(){};
})();
按照代碼的分析,我們?cè)臼窍雱?chuàng)建一個(gè)全局屬性f(注意不要和一般的匿名函數(shù)混淆了,里面用的是帶名字的聲明),JScript在這里搗亂了一把,首先他把表達(dá)式當(dāng)成函數(shù)聲明解析了,所以左邊的f被聲明為局部變量了(和一般的匿名函數(shù)里的聲明一樣),然后在函數(shù)執(zhí)行的時(shí)候,f已經(jīng)是定義過的了,右邊的function f(){}則直接就賦值給局部變量f了,所以f根本就不是全局屬性。
了解了JScript這么變態(tài)以后,我們就要及時(shí)預(yù)防這些問題了,首先防范標(biāo)識(shí)符泄漏帶外部作用域,其次,應(yīng)該永遠(yuǎn)不引用被用作函數(shù)名稱的標(biāo)識(shí)符;還記得前面例子中那個(gè)討人厭的標(biāo)識(shí)符g嗎?——如果我們能夠當(dāng)g不存在,可以避免多少不必要的麻煩哪。因此,關(guān)鍵就在于始終要通過f或者arguments.callee來引用函數(shù)。如果你使用了命名函數(shù)表達(dá)式,那么應(yīng)該只在調(diào)試的時(shí)候利用那個(gè)名字。最后,還要記住一點(diǎn),一定要把命名函數(shù)表達(dá)式聲明期間錯(cuò)誤創(chuàng)建的函數(shù)清理干凈。
2、JScript的內(nèi)存管理
知道了這些不符合規(guī)范的代碼解析bug以后,我們?nèi)绻盟脑挘蜁?huì)發(fā)現(xiàn)內(nèi)存方面其實(shí)是有問題的,來看一個(gè)例子:
var f = (function(){
if (true) {
return function g(){};
}
return function g(){};
})();
我們知道,這個(gè)匿名函數(shù)調(diào)用返回的函數(shù)(帶有標(biāo)識(shí)符g的函數(shù)),然后賦值給了外部的f。我們也知道,命名函數(shù)表達(dá)式會(huì)導(dǎo)致產(chǎn)生多余的函數(shù)對(duì)象,而該對(duì)象與返回的函數(shù)對(duì)象不是一回事。所以這個(gè)多余的g函數(shù)就死在了返回函數(shù)的閉包中了,因此內(nèi)存問題就出現(xiàn)了。這是因?yàn)閕f語句內(nèi)部的函數(shù)與g是在同一個(gè)作用域中被聲明的。這種情況下 ,除非我們顯式斷開對(duì)g函數(shù)的引用,否則它一直占著內(nèi)存不放。
var f = (function(){
var f, g;
if (true) {
f = function g(){};
}
else {
f = function g(){};
}
// 設(shè)置g為null以后它就不會(huì)再占內(nèi)存了
g = null;
return f;
})();
通過設(shè)置g為null,垃圾回收器就把g引用的那個(gè)隱式函數(shù)給回收掉了,為了驗(yàn)證我們的代碼,我們來做一些測(cè)試,以確保我們的內(nèi)存被回收了。
測(cè)試
測(cè)試很簡(jiǎn)單,就是命名函數(shù)表達(dá)式創(chuàng)建10000個(gè)函數(shù),然后把它們保存在一個(gè)數(shù)組中。等一會(huì)兒以后再看這些函數(shù)到底占用了多少內(nèi)存。然后,再斷開這些引用并重復(fù)這一過程。下面是測(cè)試代碼:
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();
}
通過運(yùn)行在Windows XP SP2中的任務(wù)管理器可以看到如下結(jié)果:
IE7: without `null`: 7.6K -> 20.3K with `null`: 7.6K -> 18K IE8: without `null`: 14K -> 29.7K with `null`: 14K -> 27K
如我們所料,顯示斷開引用可以釋放內(nèi)存,但是釋放的內(nèi)存不是很多,10000個(gè)函數(shù)對(duì)象才釋放大約3M的內(nèi)存,這對(duì)一些小型腳本不算什么,但對(duì)于大型程序,或者長(zhǎng)時(shí)間運(yùn)行在低內(nèi)存的設(shè)備里的時(shí)候,這是非常有必要的。
以上就是關(guān)于JScript的Bug與內(nèi)存管理的全部介紹,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
JavaScript實(shí)現(xiàn)瀑布動(dòng)畫
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)瀑布動(dòng)畫,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
微信小程序與h5的相互跳轉(zhuǎn)場(chǎng)景分析
這篇文章主要介紹了微信小程序與h5的相互跳轉(zhuǎn)場(chǎng)景分析,在微信小程序內(nèi)編寫webview文件,通過webview去跳轉(zhuǎn)h5,loadUrl為h5的地址,非嵌套,h5需要跳轉(zhuǎn)打開某個(gè)微信小程序,需要的朋友可以參考下2023-10-10
如何利用js實(shí)時(shí)監(jiān)聽input輸入框值的變化
在做web開發(fā)時(shí)候很多時(shí)候都需要即時(shí)監(jiān)聽輸入框值的變化,以便作出即時(shí)動(dòng)作去引導(dǎo)瀏覽者增強(qiáng)網(wǎng)站的用戶體驗(yàn)感,這篇文章主要給大家介紹了關(guān)于如何利用js實(shí)時(shí)監(jiān)聽input輸入框值的變化,需要的朋友可以參考下2024-02-02

