背景
--------------------------------------------------------------------------------
jQuery想必各個web工程師都再熟悉不過了,不過現如今很多網站還采用了很古老的jQuery版本。其實如果早期版本使用不當,可能會有DOMXSS漏洞,非常建議升級到jQuery 1.9.x或以上版本。前段時間我就主導了這件事情,把公司里我們組負責的項目jQuery版本從1.4.2升級到了jQuery 1.11.3。jQuery官方也為類似升級工作提供了jQuery Migrate插件。
言歸正傳。
坑從何處來
--------------------------------------------------------------------------------
jQuery 1.11.3是1.x時代的最后一個版本(作者更新:2016年1月8日,jQuery 1.12.0上線,jQuery 1.11.3不再是1.x時代最后一個版本了),由於我的部門項目已經有一定年頭了,當時還是采用的jQuery 1.4.2,這次升級步子邁得算是比較大。早期時候jQuery的很多寫法,在新版本中已經被廢棄,亦或者有些不規范的寫法,當時版本還能支持,但是現在已經不支持。更糟糕的情況是,新版本還支持,但是功能已經和以前不一樣了……這種情況連個錯都不會報,需要深入到代碼邏輯里面去看。
jQuery官方推薦了jQuery Migrate 庫來解決jQuery升級問題。不過一直采用這個庫終究不是長久之計,開發中建議使用jQuery Migrate的開發版,可以在瀏覽器控制台上打印出來不兼容的地方詳細信息。需要注意的是開發中一定要使用jQuery Migrate的開發版,因為壓縮版的是不會在控制台給出警告的……把jQuery Migrate的庫緊跟在jQuery庫后面引用即可:
1
2
|
<script src=
"<path>/<to>/jquery-1.11.3.js"
></script>
<script src=
"<path>/<to>/jquery-migrate-1.2.1.js"
></script>
|
等升級完畢,確定沒問題了之后,再將jQuery Migrate庫去掉就可以了。根據個人經驗,下面我把坑分成 常見坑,少見坑兩類來論述。
常見坑
--------------------------------------------------------------------------------
1. 使用了被廢棄的jQuery.fn.live方法
jQuery Migrate庫對此錯誤也在控制台有相應的警告:
JQMIGRATE: jQuery.fn.live() is deprecated
live方法原本的作用是設置事件代理,該方法在jQuery 1.7之后就不推薦使用了,取代之的是jQuery.fn.on函數。他們的接口分別是:
1
2
|
$(selector).live(
'click'
,
function
(){
/* some code */
});
$(selector).on(
'click'
, [selector,]
function
(){
/* some code */
});
|
乍一看,中括號里面的參數可以省略掉,倆函數不是一模一樣么?於是天真地把函數名live直接替換成on,大部分時候,這么做好像沒有引起任何異常。但是如果在你調用on函數的時候,前面的$(selector)在當前的網頁上根本不匹配任何元素(該元素可能是后面的代碼才加到DOM里的),那是不會綁定成功的。事實上,live函數將$(selector)代理到了document元素上,這個元素是肯定存在的,所以不會出現類似情況。正確的替換方法應該是:
1
2
|
$(selector).live(
'click'
,
function
(){
/* some code */
}); 替換為
$(document).on(
'click'
, selector,
function
(){
/* some code */
});
|
2. 使用了被廢棄的jQuery.fn.die方法
jQuery Migrate對此錯誤的警告是:
JQMIGRATE: jQuery.fn.die() is deprecated
這個方法和前面的live剛好反過來,取消事件處理函數的綁定。新版本中應該使用off函數代替之,替換方式類似。
3. 使用了被廢棄的jQuery.fn.toggle函數
jQuery Migrate對此錯誤的警告是:
JQMIGRATE: jQuery.fn.toggle(handler, handler...) is deprecated
早期jQuery中名字叫toggle的函數有兩個,一個是用於控制元素的顯示和隱藏,這個用途的函數目前jQuery中依舊存在;另一個就是上面提到的被廢棄的toggle函數,它用於綁定至少兩個函數到同一個元素,點擊該元素的時候兩個函數交替着執行。這兩個同名函數功能相差甚遠,為了不引起誤導,在jQuery 1.8中就不再建議使用了。替換的方式是把兩個函數合並成一個函數的if-else兩個區段,然后自己設置一個boolean變量,控制每次點擊時應該執行哪個區段即可。
4. 使用了被廢棄的jQuery.browser屬性
jQuery Migrate對此錯誤的警告是:
JQMIGRATE: jQuery.browser is deprecated
在前端開發中我們經常要根據不同的瀏覽器版本做出不同的處理,jQuery.browser本來是通過瀏覽器的userAgent字段來提取瀏覽器相關信息的。新版本中已經將其廢棄,而是建議使用特征檢測的方法去判斷,並且給了一個Modernizr庫作為推薦。不過,改成這個庫可能改動成本有點大,如果你還是想沿用jQuery.browser的思路的話,可以自己去實現一下它。例如,判斷是不是IE瀏覽器,可以用
/msie/.test(navigator.userAgent.toLowerCase());
即自己手動獲取userAgent字段,並且做一個正則表達式匹配。其他瀏覽器思路類似,都是對navigator.userAgent做一個正則匹配。
5. $(html)格式書寫錯誤
在jQuery Migrate中,出現以下三種警告中的任何一種,都是屬於這個錯誤:
JQMIGRATE: $(html) HTML strings must start with '<' character
JQMIGRATE: $(html) HTML text after last tag is ignored
JQMIGRATE: HTML string cannot start with a '#' character
這個錯誤還是蠻值得注意的,因為我們文章開頭所說的jQuery低版本有XSS漏洞,其實就是和這個錯誤有關系。在javascript中我們經常會直接將一段html格式的字符串寫在jQuery引用里面,比如$('<div></div>')。按照新版本的jQuery要求,這段html格式的字符串必須是以左尖括號(小於號)開頭,其他字符都不可以。以下幾種寫法,都是錯誤的:
$(" <div></div>"); //錯誤,字符串最開頭有一個空格,不是以小於號'<'開頭的
$("<div></div>test"); //不標准,html標簽結束后后面還有多余的"test",它會被忽略
$("#<div></div>); //錯誤,以井號開頭並且后面並不是一個css選擇器
這一點在書寫的時候注意一下就可以了,其實還是很容易避免的。其中第三種錯誤其實就不僅僅是警告了,jQuery會直接拋出一個錯誤,停止javascript代碼的繼續執行。一般情況以井號開頭,例如$("#test"),其實就是一個普通的選擇器,但是上面例子中后面又夾雜着html字符串,這會被jQuery判斷為潛在的XSS攻擊。
6. jQuery.fn.attr方法的錯誤使用(這是個非常易犯的錯誤!)
jQuery Migrate中,關於attr方法的警告有以下這些:
JQMIGRATE: jQuery.fn.attr('value', val) no longer sets properties
JQMIGRATE: jQuery.fn.attr('value') no longer gets properties
JQMIGRATE: jQuery.fn.attr('checked') may use property instead of attribute
JQMIGRATE: jQuery.fn.attr( props, pass ) is deprecated
實踐中我發現,早期寫的代碼里面,獲取一個input輸入表單的值時,是怎么獲取的呢?$('input').attr('value');又是怎么設置的呢?$('input').attr('value', 'helloworld')。這在新版本中都是不正確的!正確的做法應該是
$('input').val(); //獲取input表單現在所輸入的值
$('input').val('helloworld'); //設置input表單輸入的值
到底是獲取還是設置,只取決於調用val方法時有沒有帶着參數。
如果你想手動設置單選框(例如<input type="radio" >)被選中,應該怎么設置呢?老的代碼里面可能會看到這樣 $('input').attr('checked', true)或者$('input').attr('checked', 'checked')。這些現在也都是不正確的!正確的做法應該是
$('input').prop('checked', true); //把單選框設為選中狀態
$('input').prop('checked'); //獲取單選框是不是被選中了,返回true或false
這是從jQuery 1.6版本開始使用的寫法。如果設置disabled和selected屬性,也是使用prop方法。那到底什么時候使用attr方法呢?兩者的區別是:prop設置的是某元素固有的屬性,而attr設置的是寫在html標簽上的自定義屬性。舉個例子:
1
2
3
4
5
|
<input type=
"checkbox"
checked=
"checked"
haha=
"hello"
>
var
v1 = $(
'input'
).prop(
"checked"
);
//返回true/false,是否被選中,隨狀態改變而改變
var
v2 = $(
'input'
).attr(
"checked"
);
//返回"checked",這是你設置在標簽上的,不會變
var
v3 = $(
'input'
).attr(
"haha"
);
//返回"hello",自定義屬性
var
v4 = $(
'input'
).prop(
"haha"
);
//返回undefined,根本沒有這個固有屬性
|
上面提到的第四個錯誤,jQuery.fn.attr(props, pass) is deprecated這個警告在真實項目中從未見到過,看了一下源碼,觸發該警告的jQuery寫法很少見,可忽略。
7. 向$.parseJSON傳入了非法的參數
在jQuery Migrate中,該錯誤產生如下警告
JQMIGRATE: jQuery.parseJSON requires a valid JSON string
jQuery之所以改這個接口,是為了和瀏覽器自帶的JSON.parse接口對齊,從jQuery 1.9開始生效。這個問題常見於AJAX接收服務端返回值的時候。服務端可能返回一個空字符串,這時候調用該接口會產生錯誤。必須向$.parseJSON傳入合法的JSON字符串。修正方法如下:
1
2
|
var
v1 = $.parseJSON(str); 替換為
var
v1 = $.parseJSON( str ? str :
"null"
);
|
8. 使用了被廢棄的'hover'事件字符串
在jQuery Migrate中該錯誤產生如下警告
JQMIGRATE: 'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'
在注冊事件處理函數時,'hover'以前可以看作是'mouseenter mouseleave'兩個事件的別稱。目前已經將該別稱去掉了,所以代碼中請用'mouseenter mouseleave'替換之。
9. jQuery.fn.andSelf已經被替換,不能再使用
jQuery Migrate中是這樣的警告:
JQMIGRATE: jQuery.fn.andSelf() replaced by jQuery.fn.addBack()
兩個函數功能是完全一樣的,可以直接替換。
以上,就是在jQuery升級中常見的問題,當然,本着精益求精的精神,我們還是需要研究一下不常見的問題是什么樣子的。需要指出的是:下面的問題在我的實際項目中從來沒有碰到過,比較少見,但也無法保證一定不會出現在你的項目中,僅供感興趣的程序員們參考吧。
少見坑
--------------------------------------------------------------------------------
1. jQuery不兼容瀏覽器的怪異模式
這個錯誤的觸發方式非常簡單,直接把html頁面最頂端的<!DOCTYPE html>標簽刪掉就可以了。瀏覽器怪異模式是為了兼容老古董網頁而設計的,詳情可參考這篇文章:鏈接。我想現在的WEB程序員應該不會傻到不寫DOCTYPE,也很少使用這種模式下的瀏覽器吧。
jQuery Migrate展示的錯誤警告如下:
2. AJAX全局事件必須綁定到document節點上
jQuery Migrate中的警告如下:
JQMIGRATE: AJAX events should be attached to document: ajaxStart
jQuery中AJAX全局事件包括如下接口ajaxStart, ajaxStop, ajaxSend, ajaxComplete, ajaxError, ajaxSuccess。因為這些事件使用的比較少,所以也歸在少見坑當中。從jQuery 1.9開始,這些事件只能綁定到$(document)上。改正方法如下(摘自jQuery官網):
1
2
|
$(
"#status"
).ajaxStart(
function
(){ $(
this
).text(
"Ajax started"
); }); 修改為
$(document).ajaxStart(
function
(){ $(
"#status"
).text(
"Ajax started"
); });
|
3. IE6/7/8瀏覽器不支持修改input表單的type屬性
在jQuery Migrate中是這樣的警告:
JQMIGRATE: Can't change the 'type' of an input or button in IE 6/7/8
改變input的表單的type屬性,你可以直接把文本框改成單選框,改成多選框等等。雖然我感覺這是一種並不算優雅的行為,但是很多瀏覽器都是支持這么做的,除了IE6/7/8。建議在實際中也是少用這個功能為好。
4. 使用了被移除的$.clean, $.event.handle, $.attrFn, $.fn.data('events'), jQuery.event.trigger屬性與方法
在jQuery Migrate中是這樣的警告:
JQMIGRATE: jQuery.clean() is deprecated
JQMIGRATE: jQuery.event.handle is undocumented and deprecated
JQMIGRATE: jQuery.attrFn is deprecated
JQMIGRATE: Use of jQuery.fn.data('events') is deprecated
JQMIGRATE: Global events are undocumented and deprecated
如果你在自己的代碼中使用過這五個接口,那確實是仔細研究過jQuery源代碼的高人啊。因為這五個接口從來沒有出現在jQuery的官方文檔中,並且有些在后續版本中已經刪除,可謂來無影去無蹤。看源代碼的話在早期版本有機會找到他們的存在,但是並不建議使用。建議采用其他方法實現相應的功能。什么?你不知道這五個函數是什么功能?那最好了,你現在也不需要知道了……
5. 使用了過時的$.sub()方法
jQuery Migrate中對本問題的警告如下:
JQMIGRATE: jQuery.sub() is deprecated
這個接口非常簡單,不接受任何參數。它用來創建一個jQuery的副本。該方法在jQuery 1.7版本開始就已經不再使用。
6. 使用了過時的jQuery.fn.error方法
jQuery Migrate中對本問題的警告如下:
JQMIGRATE: jQuery.fn.error() is deprecated
在jQuery中,error也是和click一樣的事件。注冊該事件的處理函數,以前是$(selector).error(function(){}),現在已經被廢棄,可以使用$(selector).on('error', function(){})來替代。
示例代碼
--------------------------------------------------------------------------------
本文既然自稱為“XX大全”,那就應該盡量的全面一些。為了搞明白這些坑是怎么踩進去的,我們最后來寫一段js代碼,要求是用最少的代碼,把jQuery Migration庫中所有的坑都踩一遍……也就是讓jQuery Migration庫打印出來它能打印的所有代碼。最終的代碼如下所示(博客園竟然沒有辦法上傳附件,只能貼代碼了),非常簡單易懂。打開index.html文件,然后再按F12鍵打開控制台,你就可以看到壯觀宏偉的控制台警告了^_^
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
<!-- filename : index.html --><!--<!DOCTYPE html>-->
//keng0 怪異模式
<html>
<head>
<meta charset=
"utf-8"
/>
<title>jQuery升級踩坑大全</title>
</head>
<body>
<div class=
"test"
id=
"a"
>a</div>
<input type=
"radio"
id=
"b"
value=
"b"
/>
<input type=
"radio"
id=
"c"
value=
"c"
/>
<div id=
"d"
value=
"d"
>test</div>
<script type=
"text/javascript"
>
//開始踩坑
//使用被廢棄分$.attrFn方法
var
keng1 = $.attrFn || {};
//該函數在jQuery內部調用,真實項目中從未見過,可忽略,這里只是為了觸發一下錯誤警告
var
keng2 = $.attr($(
"#a"
),
"class"
,
"xxx"
,
true
);
//IE6、7、8中不支持改變輸入框的類型
var
keng3 = $(
"input#b"
).attr(
"type"
,
"text"
);
//在該使用prop的地方使用了attr
var
keng4 = $(
"input#c"
).attr(
"checked"
,
true
);
//使用attr獲取property的值,正確的是應該使用 .val()
var
keng5 = $(
"div#d"
).attr(
"value"
);
//使用attr設置property的值,正確的是應該使用 .val('somevalue')
var
keng6 = $(
"div#d"
).attr(
"value"
,
"abcd"
);
//html字符串必須以'<'開頭(下面這個是以空格開頭)
var
keng7 = $(
" <div></div>"
);
//最后一個tag后面還有多余字符串
var
keng8 = $(
"<div></div>abc"
);
//html字符串不可以以井號‘#'開頭
try
{
var
keng9 = $(
"#<div></div>"
);
}
catch
(e){
console.error(e);
}
//$.parseJSON的參數必須是合法的JSON字符串
var
keng10 = $.parseJSON(undefined);
//使用被廢棄的$.browser
var
keng11 = $.browser;
//使用被廢棄的$.sub
var
keng12 = $.sub();
$(
"#c"
).on(
"click"
,
function
(){});
var
keng13 = $(
"#c"
).data(
"events"
);
//調用了已經不再使用的函數andSelf,該函數已經被addBack替代
var
keng14 = $(
"#c"
).nextAll().andSelf();
//使用被廢棄的$.clean方法
try
{
var
keng15 = $.clean();
}
catch
(e){
console.error(e);
}
//"hover"字符串注冊事件已經被拆成"mouseenter"和"mouseleave"兩個
var
keng16 = $(
"#d"
).on(
"hover"
,
function
(){
/*some code*/
});
//jQuery.event.handle並沒有收錄到官方的API中,新版本已經被移除
var
keng17 =
function
(){
$.event.handle.apply(
this
, arguments);
};
//全局AJAX事件處理必須綁定到document對象上
var
keng18 = $(
"#c"
).ajaxStart(
function
(){});
//使用了被廢棄的error方法
var
keng19 = $(
"#c"
).error(
function
(){});
//使用了被廢棄的toggle方法
var
keng20 = $(
"#d"
).toggle(
function
(){
/*some code*/
},
function
(){
/*some code*/
});
//使用了被廢棄的live方法,應該使用on方法替代之
var
keng21 = $(
"#a"
).live(
"click"
,
function
(){
/*some code*/
});
//使用了被廢棄的die方法,應該使用off方法替代之
var
keng22 = $(
"#a"
).die(
"click"
);
//使用了全局事件函數,目前全局事件只支持AJAX那幾個,其他全局事件都不支持
var
keng23 = $.event.trigger(
"click"
);
</script>
</body>
</html>
|