一直以为检查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的代码,收获应该不小。

本文作者:admin 转载请注明来自:携程设计委员会