前端javascript代碼編寫中,有一個不錯的工具叫JSLint,可以檢查代碼規范化,壓縮JS,CSS等,但是他的語法規范檢查個人覺得太“苛刻”了,會提示各種各樣的問題修改建議,有時候提示的信息我們看的莫名其妙,這里,先轉載一下攜程UED的一個技術文章(原文鏈接http://ued.ctrip.com/blog/?p=2733),看看JSLint的錯誤提示都是什么意思:
一直以為檢查JS語法錯誤非jslint不可,不過使用起來總是覺得太重量級了一點點。
后來無意中發現了一個叫jshint的東東。
首先介紹一下,jshint和jslint的差別在哪里。
摘自官網的一段內容
JSHint is a fork of Douglas Crockford’s JSLint that is designed to be more flexible than the original. Our goal is to make a tool that helps you to find errors in your JavaScript code and to enforce your favorite coding style.
We realize that people use different styles and conventions, and we want our tool to adjust to them. JSHint will never enforce one particular convention.
大概的意思就是,JSHint比起JSLint而言,會更加輕量級一些。它能夠找出代碼中的語法錯誤,並且建議更好的一種編碼風格。當然,它也不是強制性的非要你根據它規定的編碼風格來做。因為它提供了一系列的配置,你可以隨時關掉某些你覺得不必要的錯誤提示。這個我后面會介紹到。
那么如何使用jsHint檢查錯誤呢?用起來非常簡單哦~
var result = JSHINT(source, options);
先解釋一下參數和返回值:
第一個參數source : 必選項。表示需要檢查的代碼,js或者json,可以傳一個字符串或者一個數組。如果傳字符串,需要用’\r’或者’\n’來分隔一行一行的代碼;如果傳數組,則每一個數組元素表示一行的代碼。
第二個參數option : 可選項。表示代碼檢查的配置項。大部分的都是bool類型的,也有一部分,例如predef,可以是一個array,含有全局變量或者全局方法;或者是一個object,key是全局變量或者方法,value是一個bool值,表示是否被定義過。
返回值:如果代碼沒有問題,JSHINT會返回一個true;否則返回false。
詳細的說明
1. 關於第一個參數
由於只能傳入一個字符串或者數組。但是如果需要根據一個js的鏈接來檢查此文件呢?
我嘗試使用ajax方法獲取js的內容(雖然是可以跨域的),但是如果頁面是gb2312的取回來就會亂碼,那么用jshint頁面檢查的話,讀到中文部分就會報錯”unsafe charater”。嘗試使用二進制的responseBody進行轉碼,但是沒有找到是適合的js轉碼方法。
后來,我寫了一個php的中轉頁面,用file_get_contents的方法讀取文件,使用mb_detect_encoding檢測頁面編碼
<?php mb_detect_order("GB2312,GBK,UTF-8,ASCII"); $url = $_REQUEST["url"]; $str = file_get_contents($url); if(!isset($url) || !$str){ echo ""; }else{ $getcontent = iconv(mb_detect_encoding($str), "utf-8", $str); echo $getcontent; } ?>
當然,也有同學提到可以將js文件獲取下來,存到本地。讀取的時候判斷編碼來讀取,也是一種方法。不過我沒有嘗試過。
2. 關於第二個參數
我覺得這個才是JSHINT的精髓,因為每一個配置項都可以定義你要check的深度和廣度。
下面就把這一系列的配置項列出來(偶自己翻譯的)。以下的這些是官網上面的option。
prop | description |
asi | 是否使用自動插入分號 |
bitwise | 如果是true,則禁止使用位運算符 |
boss | 如果是true,則允許在if/for/while的條件中使用=做賦值操作 |
curly | 如果是true,則要求在if/while的模塊時使用TAB結構 |
debug | 如果是true,則允許使用debugger的語句 |
eqeqeq | 如果是true,則要求在所有的比較時使用===和!== |
eqnull | 如果是true,則允許使用== null |
evil | 如果是true,則允許使用eval方法 |
forin | 如果是true,則不允許for in在沒有hasOwnProperty時使用 |
immed | 如果是true,則要求“立即調用”(immediate invocations)必須使用括號包起來 |
laxbreak | 如果是true,則不檢查換行,那么自動插入分號的選項必須開啟。 |
maxerr | 默認是50。 表示多少錯誤時,jshint停止分析代碼 |
newcap | 如果是true,則構造函數必須大寫 |
noarg | 如果是true,則不允許使用arguments.caller和arguments.callee |
noempty | 如果是true,則不允許使用空函數 |
nonew | 如果是true,則不允許不做賦值的構造函數,例如new UIWindow(); |
nomen | 如果是true,則不允許在名稱首部和尾部加下划線 |
onevar | 如果是true,則在一個函數中只能出現一次var |
passfail | 如果是true,則在遇到第一個錯誤的時候就終止 |
plusplus | 如果是true,則不允許使用++或者- -的操作 |
regexp | 如果是true,則正則中不允許使用.或者[^…] |
undef | 如果是ture,則所有的局部變量必須先聲明之后才能使用 |
sub | 如果是true,則允許使用各種寫法獲取屬性(一般使用.來獲取一個對象的屬性值) |
strict | 如果是true,則需要使用strict的用法, 詳見http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ |
white | 如果是true,則需要嚴格使用空格用法。 |
當然,千萬別以為JSHINT就只有這些配置項,在我使用的過程中,發現很多配置項就需要去讀它的源代碼才能發現。
比如,當我發現他會報錯我的某個自定義函數$animate沒有定義的時候,我嘗試用/*global $animate*/來聲明,但是沒有效果。
於是我跟蹤代碼,發現了如下的代碼
function assume() { if (option.couch) combine(predefined, couch); if (option.rhino) combine(predefined, rhino); if (option.prototypejs) combine(predefined, prototypejs); if (option.node) combine(predefined, node); if (option.devel) combine(predefined, devel); if (option.dojo) combine(predefined, dojo); if (option.browser) combine(predefined, browser); if (option.jquery) combine(predefined, jquery); if (option.mootools) combine(predefined, mootools); if (option.wsh) combine(predefined, wsh); if (option.globalstrict && option.strict !== false) option.strict = true; }
當時我在這個后面加了一段代碼,准備擴展一個predef的項。
后來才發現,原來JSHINT本身就有一個predef的配置,就像上面說的一個數組或一個object即可。
if(option.predef){ for(var i=0,l=option.predef.length; i<l; i++){ predefined[option.predef[i]] = true; } }
當然,細心的同學一定發現了,里面也可以聲明代碼運行的環境。例如jquery/dojo等。
- Browser (browser)
- Development: console, alert, etc. (devel)
- jQuery (jquery)
- CouchDB (couch)
- ES5 (es5)
- Node.js (node)
- Rhino (rhino)
- Prototype.js (prototypejs)
- MooTools (mootools)
又比如JSHINT檢查的時候,針對某些字符會報”unsafe character”的錯誤,但是如果有些字符恰巧就是我們需要的怎么辦呢?
跟蹤代碼,發現檢查unsafe的正則如下:
cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/
我們可以自定義一下cx
3. 返回值
當返回false的時候,我們如何知道代碼哪里出問題了呢?
有以下幾個方法:
1. JSHINT.errors
JSHINT.errors是一個object,有以下值:
{
line: 錯誤的行數,
charater: 錯誤的字符數,
reason: 問題詳細描述信息,
evidence: 出錯的代碼,
raw: 原本的描述信息,
a: the first detail,
b: the second detail,
c: the third detail,
d: the fourth detail
}
2. JSHINT.report(limited)
參數limited如果為true,則表示report僅僅輸出錯誤(errors)
返回一個類似report的最終結果。可以被放置在html中。
3. JSHINT.data()
返回一個object格式的數據結果, 有以下值:
{ errors:[ { line: 錯誤的行數[number], charater: 錯誤的字符數[number], reason: 問題詳細描述信息[string], evidence: 出錯的代碼[string] } ], functions: [ name: 函數名稱[STRING], line: 錯誤的行數[NUMBER], last: NUMBER, param: [ 參數[STRING] ], closure: [ 閉包[STRING] ], var: [ STRING ], exception: [ STRING ], outer: [ STRING ], unused: [ STRING ], global: [ STRING ], label: [ STRING ] ], globals: [ STRING ], member: { STRING: NUMBER }, unuseds: [ { name: STRING, line: NUMBER } ], implieds: [ { name: STRING, line: NUMBER } ], urls: [ STRING ], json: 是否是json的數據[BOOLEAN]
使用下來,jshint對代碼的檢查非常不錯的。
但是覺得jshint有些地方可以改進,例如所有的報錯信息都是分散在四面八方。這里一句warning(“xx”),那里一句warning(“yy”)。不像wikipedia有一個統一管理message的地方,而且有語言版本的選擇。
無奈之下,我只能增加了一下message的翻譯,並且小改了一下warning的函數
globalMsg = { “Missing semicolon.” : “缺少分號.”, “Use the function form of \”use strict\”.” : “使用標准化定義function.”, “Unexpected space after ‘-’.” : “在’-'后面不應出現空格.”, “Expected a JSON value.” : “請傳入一個json的值.”, “Mixed spaces and tabs.”: “空格和TAB重復.”, “Unsafe character.” : “不安全的字符.”, “Line too long.”: “本行中的字符超過設定的最大長度.”, “Trailing whitespace.”: “本行末尾有過多無用空格.”, “Script URL.” : “腳本URL.”, “Unexpected {a} in ‘{b}’.” : “在 ‘{b}’ 中不該出現 {a}.”, “Unexpected ‘{a}’.” : “不該在此出現’{a}’.”, “Strings must use doublequote.” : “字符串需要用雙引號”, “Unnecessary escapement.” : “不需要轉義”, “Control character in string: {a}.” : “在字符串中出現了Control的字符”, “Avoid \\’.” : “避免 \\”, “Avoid \\v.” : “避免 \\v”, “Avoid \\x-.” : “避免 \\x-”, “Bad escapement.” : “錯誤的轉義字符”, “Bad number ‘{a}’.” : “錯誤的數字 ‘{a}’”, “Missing space after ‘{a}’.” : “在’{a}’之后缺少空格”, “Don’t use extra leading zeros ‘{a}’.” : “不要再’{a}’的前面用多余的0″, “Avoid 0x-. ‘{a}’.” : “避免使用 0x-. ‘{a}’.”, “A trailing decimal point can be confused with a dot ‘{a}’.” : “在’{a}’中使用點尾隨小數點”, “Unexpected comment.” : “不該在此處出現注釋”, “Unescaped ‘{a}’.” : “沒有轉義 ‘{a}’”, “Unexpected control character in regular expression.” : “在正則表達式中出現了control字符”, “Unexpected escaped character ‘{a}’ in regular expression.” : “在正則表達式中出現了沒有轉義的字符 ‘{a}’”, “Expected ‘{a}’ and instead saw ‘{b}’.” : “應該用 ‘{a}’代替’{b}’”, “Spaces are hard to count. Use {{a}}.” : “空格難以統計,請使用 {{a}}”, “Insecure ‘{a}’.” : “不安全的 ‘{a}’”, “Empty class.” : “空的class”, “Expected a number and instead saw ‘{a}’.”:“應該用數字代替’{a}’”, “‘{a}’ should not be greater than ‘{b}’.”:“‘{a}’不應該比’{b}’大”, “‘hasOwnProperty’ is a really bad name.”: “‘hasOwnProperty’是關鍵字”, “‘{a}’ was used before it was defined.”:“‘{a}’未定義就已經使用了.”, “‘{a}’ is already defined.”:“‘{a}’被重復定義”, “A dot following a number can be confused with a decimal point.”:“數字后面的一個點會被誤認為是十進制的小數點”, “Confusing minusses” : “容易混淆的負數表達-”, “Confusing plusses.” : “容易混淆的正數表達+”, “Unmatched ‘{a}’.” : “無法匹配的’{a}’”, “Expected ‘{a}’ to match ‘{b}’ from line {c} and instead saw ‘{d}’.”:“在行{c}中需要用’{a}’和’{b}’匹配,用來代替’{d}’”, “Unexpected early end of program.”:“程序不可預期的提前終止”, “A leading decimal point can be confused with a dot: ‘.{a}’.”:“‘{a}’前的點容易混淆成小數點”, “Use the array literal notation [].”:“使用數組的符號 []“, “Expected an operator and instead saw ‘{a}’.”:“需要用一個符號來代替’{a}’”, “Unexpected space after ‘{a}’.”:“在’{a}’之后不能出現空格”, “Unexpected space before ‘{a}’.”:“在’{a}’之前不能出現空格”, “Bad line breaking before ‘{a}’.”:“在’{a}’之前錯誤的換行”, “Expected ‘{a}’ to have an indentation at {b} instead at {c}.”:“‘{a}’需要在{c}而不是{b}處縮進”, “Line breaking error ‘{a}’.”:“換行錯誤 ‘{a}’”, “Unexpected use of ‘{a}’.”:“此處不能用’{a}’”, “Bad operand.”:“錯誤的操作數”, “Use the isNaN function to compare with NaN.”:“使用isNaN來與NaN比較”, “Confusing use of ‘{a}’.”:“容易混淆的’{a}’的使用”, “Read only.”:“只讀的屬性”, “‘{a}’ is a function.”:“‘{a}’是一個函數”, ‘Bad assignment.’:“錯誤的賦值”, “Do not assign to the exception parameter.”:“不要給額外的參數賦值”, “Expected an identifier in an assignment and instead saw a function invocation.”:“在賦值的語句中需要有一個標識符,而不是一個方法的調用”, “Expected an identifier and instead saw ‘{a}’ (a reserved word).”:“需要有一個標識符,而不是’{a}’(保留字符)”, “Missing name in function declaration.”:“在方法聲明中缺少名稱”, “Expected an identifier and instead saw ‘{a}’.”:“需要有一個標識符,而不是’{a}’”, “Inner functions should be listed at the top of the outer function.”:“內部函數的聲明應該放在此函數的頂部。”, “Unreachable ‘{a}’ after ‘{b}’.”:“在’{b}’之后無法獲取’{a}’”, “Unnecessary semicolon.”:“不必要的分號”, “Label ‘{a}’ on {b} statement.”:“將’{a}’放在{b}的聲明中”, “Label ‘{a}’ looks like a javascript url.”:“‘{a}’看上去像一個js的鏈接”, “Expected an assignment or function call and instead saw an expression”:“需要一個賦值或者一個函數調用,而不是一個表達式.”, “Do not use ‘new’ for side effects.”:“不要用’new’語句.”, “Unnecessary \”use strict\”.”:“不必要的\”use strict\”.”, “Missing \”use strict\” statement.”:“缺少\”use strict\”的聲明”, “Empty block.”:“空的模塊”, “Unexpected /*member ‘{a}’.”:“不應出現 /*元素 ‘{a}’.”, “‘{a}’ is a statement label.”:“‘{a}’是一個聲明”, “‘{a}’ used out of scope.”:“‘{a}’使用超出范圍”, “‘{a}’ is not allowed.”:“不允許使用’{a}’”, “‘{a}’ is not defined.”:“‘{a}’沒有被定義”, “Use ‘{a}’ to compare with ‘{b}’.”:“使用’{a}’與’{b}’相比”, “Variables should not be deleted.”:“變量需要被刪除”, “Use the object literal notation {}.”:“使用對象的文字符號 {}”, “Do not use {a} as a constructor.”:“不要使用{a}作為一個構造對象”, “The Function constructor is eval.”:“The Function constructor is eval.”, “A constructor name should start with an uppercase letter.”:“一個構造對象的名稱必須用大寫字母開頭.”, “Bad constructor.”:“錯誤的構造對象”, “Weird construction. Delete ‘new’.”:“構造對象有誤,請刪除’new’”, “Missing ‘()’ invoking a constructor.”:“缺少括號()”, “Avoid arguments.{a}.”:“避免參數.{a}.”, “document.write can be a form of eval.”:“document.write是eval的一種形式”, ‘eval is evil.’:“盡量不要使用eval”, “Math is not a function.”:“Math不是一個函數”, “Missing ‘new’ prefix when invoking a constructor.”:“此處缺少了’new’”, “Missing radix parameter.”:“缺少參數”, “Implied eval is evil. Pass a function instead of a string.”:“傳遞一個函數,而不是一個字符串”, “Bad invocation.”:“錯誤的調用”, “['{a}'] is better written in dot notation.”:“['{a}']最好用點.的方式”, “Extra comma.”:“多余的逗號”, “Don’t make functions within a loop.”:“不要用循環的方式創建函數”, “Unexpected parameter ‘{a}’ in get {b} function.”:“在{b}方法中不該用到參數’{a}’”, “Duplicate member ‘{a}’.”:“重復的’{a}’”, “Expected to see a statement and instead saw a block.”:“此處應該是語句聲明.”, “Too many var statements.”:“過多var的聲明”, “Redefinition of ‘{a}’.”:“‘{a}’被重復定義”, “It is not necessary to initialize ‘{a}’ to ‘undefined’.”:“無需將’{a}’初始化為’undefined’”, “Expected a conditional expression and instead saw an assignment.”:“此處需要一個表達式,而不是賦值語句”, “Expected a ‘break’ statement before ‘case’.”:“在’case’之前需要有’break’.”, “Expected a ‘break’ statement before ‘default’.”:“在’default’之前需要有’break’.”, “This ‘switch’ should be an ‘if’.”:“此處’switch’應該是’if’.”, “All ‘debugger’ statements should be removed.”:“請刪除’debugger’的語句”, “‘{a}’ is not a statement label.”:“‘{a}’不是一個聲明標簽.”, “Expected an assignment or function call and instead saw an expression.”:“需要一個語句或者一個函數調用,而不是一個表達式”, “Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.”:“函數的聲明不能放在類似if的塊中,需要放在外部函數的頂部.” },
warning函數增加了一個m = globalMsg[m] || m;
function warning(m, t, a, b, c, d) { var ch, l, w; t = t || nexttoken; if (t.id === ‘(end)’) { // `~ t = token; } l = t.line || 0; ch = t.from || 0; m = globalMsg[m] || m; w = { id: ‘(error)’, raw: m, evidence: lines[l - 1] || ”, line: l, character: ch, a: a, b: b, c: c, d: d }; w.reason = m.supplant(w); JSHINT.errors.push(w); if (option.passfail) { quit(‘Stopping. ‘, l, ch); } warnings += 1; if (warnings >= option.maxerr) { quit(“Too many errors.”, l, ch); } return w; }
如果你需要一個輕量級的語法檢查工具,那么jshint還是一個蠻得心應手的工具。如果能夠更深入的讀一下JSHINT的代碼,收獲應該不小。
針對自己項目中遇到的一些提示,做一些舉例說明:
1 [W099]:Mixed spaces and tabs
這個錯誤是最常見的,意思是在同一行中,空格和Tab縮進混合使用了,修改很簡單,一般是刪除Tab縮進,全部改為空格。為了方便,我們可以把編輯器的Tab縮進設置成2個或4個空格,來代替原有的縮進。
2 [W030]:Expected an assignment or function call and instead saw an expression
這個錯誤提示的很詭異,我是用如下代碼提示的這個錯誤 index-1 <0 ? index = 0:index = index - 1; 這是一個逗號表達式,但是JSLInt認為這里不應該用表達式,而必須是一個函數,所以,如果非常在乎這個錯誤,就改為if else 語句吧
3 [W041]:Use '===' to compare with ...
這個錯誤是說,我們要是用全等來代替等於,如果表達式兩邊的數據類型是一致的話,建議使用全等來判斷
4 [W033]:Missing semicolon
缺少分號;這個一般都是自己忘記寫了吧,但是有一個需要注意的是,對於只有一句的結構,后面也需要寫分號。例如:if(index<0) {index=tcount-1} 這句代碼,正確寫法是if(index<0) {index=tcount-1;},我是經常忘記這里寫分號,汗...
其他還有一些錯誤提示就對照一下改吧,要培養自己良好的代碼風格和書寫習慣。