一、简介
eslint是一个开源的JavaScript代码检查工具,其作者是大名鼎鼎的“红宝书”《JavaScript高级程序设计》作者 Nicholas C. Zakas。Nicholas C. Zakas 在他的多部著作中都有涉及到JavaScript的代码风格问题,而eslint正是用来统一JavaScript代码风格的工具。
二、使用的目的和原因
1. 统一代码风格,减少review成本和低级错误的出现
eslint可以配置在开发环境中,帮助我们找出项目中不符合规则的代码并给出提示。在我们的开发环境中,开发者每次修改代码,都会先用eslint检查代码,再进行babel和react的编译,一旦eslint发现error级别的错误,那么报错文件不会进行后续的编译。这样可以让eslint随时提醒开发者代码是否符合规范,从而减少了我们花费在review代码风格上的时间,降低了低级bug的出现。
2. 支持ES6
同类型的代码检查工具还有jslint和jshint,和它们相比,eslint对ES6语法支持更好,这是我们选择使用eslint的主要原因之一,可以通过eslint在团队内快速统一ES6的语法,精简产品代码,提高开发效率。
3. 插件丰富
eslint除了上面说的支持ES6语法之外,还支持各种插件,可以让我们添加自己需要的语法规则。比如项目引入了react,那么最好使用jsx和react 这两个插件来检查代码,使基于react的代码更符合规范。
三、使用方式
eslint的插件是多种多样的,可以使用npm全局安装eslint,通过命令行的方式来进行代码检查;如果构建工具是gulp,建议使用gulp-eslint;由于我们项目采用webpack作为构建工具,这里重点介绍在webpack中使用eslint的方式。
基础配置
首先引入eslint基础包,再加入webpack中对应的插件eslint-loader,然后在项目根目录下创建.eslintrc文件后进行eslint的配置。
通过.eslintrc文件,可以指定代码运行环境是node还是browser;有哪些全局变量,例如jQuery之类;支持的ECMAScript版本号等。其中,最重要的是规则配置,告诉eslint工具需要检查哪些语法规则,配置方式如下:
"rules": [
"rule-name1": "0",
"rule-name2": "1",
"rule-name3": "2"
]
规则格式是"<规则名称>: <告警级别>",告警级别分为三种:
- "0"表示忽略问题,等同于"off";
- "1"表示给出警告,等同于"warn";
- "2"表示直接报错,等同于"error"。
配置4个空格一个缩进,不符合配置时报错:
"indent": ["error", 4]
或者,switch下的case缩进4个空格,不符合配置时警告:
"indent": ["warn", 4, {
"SwitchCase": 1
}]
引入并配置好eslint和eslint-loader后,就可以开始添加webpack的相关配置了:
preLoaders: [
{
test: /\.js$/, // 检测所有的js文件
loader: "eslint-loader", // 使用eslint插件
exclude: [ // 排除第三方文件
/node_modules/,
/app\/lib/
]
}
]
让webpack在打包文件之前,对除第三方外的js文件用eslint进行检查。
完成上述配置后,webpack在构建时就能自动对js代码用eslint进行检查了。
注:由于webpack在默认配置下遇到error并不会抛出错误终止代码打包,需要在webpack命令上添加bail参数让webpack抛出错误:
webpack --bail --progress --colors --config webpack.config.js
添加插件
如果需要react和jsx的语法检查,可以引入eslint-plugin-jsx-a11y和eslint-plugin-react这两个插件并在.eslintrc文件中添加入plugins的配置:
"plugins": [
"react",
"jsx-a11y"
]
规则格式是"<插件名称>/<规则名称>: <告警级别>":
{
"rules": {
"react/jsx-uses-react": "error",
"jsx-a11y/no-static-element-interactions": "warn"
}
}
添加扩展
除了支持插件外,eslint还支持通过扩展来快速的引入开源的JavaScript代码规则,减少了我们选择规则的时间,例如eslint官方推荐的规则:
"extends": "eslint:recommended"
或者是前端圈内很流行的airbnb 的规则:
"extends": "airbnb"
前提是要先引入airbnb 的插件。airbnb的规则不仅包含了官方推荐的大部分规则,还加入了jsx、react和import的相关规则,能帮助我们一键完成react应用的代码规则配置。
如果扩展引入的有些规则不符合所在团队的开发习惯,可在.eslintrc的rules中用自己的配置覆盖掉扩展中的默认值。
引入扩展的目的是减少我们挑选规则的时间,但这些规则不一定切合团队和项目的具体情况。如果一味地让团队去遵守别人制定的规则,很可能造成对现存代码的大范围修改,反而降低了开发效率。因此,建议先依据团队现有的良好的风格挑选出最符合现有开发习惯的规则,保证已有的好习惯不被破坏的基础上,再添加一些希望在团队中推广的规则。
四、规则节选
JavaScript是一种很灵活的语言,一些笔误或者多余的写法并不会引起代码编译打包报错,却会留下风格不良或实现错误的代码。我们选取了下面两条规则来自动定位这些问题代码:
消除不必要的类型转换no-extra-boolean-cast
JavaScript的判断语句会自动将一个任意类型的值转成Boolean类型,但我们有时会看到如下代码:
if (!!str) { // if (str)
}
var bar = Boolean(str) ? 'yes' : 'no'; // str ? 'yes' : 'no'
这种类型转换都是多余的,因为判断本身就会帮你做这步操作,所以我们选取了这个规则,并设置为error级别:
"no-extra-boolean-cast": "error"
在条件判断中不能出现赋值语句no-cond-assign
上面我们说过JavaScript在条件判断时,会把任何类型的值都转成Boolean,所以如果我们代码中出现了如下笔误:
if (a = 1) { // a === 1
...
}
这是一个bug, 但JavaScript引擎却不会报错,所以我们把这条规则也设置为error级别:
"no-cond-assign": "error"
代码可读性是团队开发中极为重要的一环,可读性高的代码能减少沟通成本并让代码更容易重构。我们选取了下面两条规则来提高代码可读性:
消除魔幻数字no-magic-numbers
magic number特指我们代码中出现的含义不明确的数字,如下面这段代码中的199,0.8和0.79:
let finalPrice = originalPrice;
if (originalPrice > 199) {
finalPrice = 199 + (originalPrice - 199) * 0.8;
}
if (isVip) {
finalPrice *= 0.79
}
上面代码的意思是,对于购物金额超过199的部分享8折优惠,vip用户可以对最终金额享受7.9折优惠。
但是这种写法的代码可读性差,在不了解业务需求的情况下很难理解其中涵义,我们为这种代码选择了直接报错的配置:
"no-magic-numbers": "error"
上面的代码需要改为下面的版本才能通过检查
const BASE_PRICE = 199;
const BASE_DISCOUNT = 0.8;
const VIP_DISCOUNT = 0.79;
let finalPrice = price;
if (originalPrice > BASE_PRICE) {
finalPrice = BASE_PRICE + (price - BASE_PRICE) * BASE_DISCOUNT;
}
if (isVip) {
finalPrice *= VIP_DISCOUNT;
}
将意义不明的数字声明为意义明确的常量,常量名称能让代码自注释,从而提高了代码的可读性,并且由于数字仅出现一次,修改也更容易。
但是,我们发现引入这条规则之后,多处代码报错no-magic-number:
var arr = ['a', 'b', 'c'];
var second = arr[1]; // 这里取下标1会报错
var last = arr[arr.length - 1]; // 这里的1没有赋值给一个变量也会报错
这明显是不符合我们期望的,但eslint中支持配置哪些数字不被视为magic number并忽略对数组下标的检查:
"no-magic-numbers": ["error", {
"ignoreArrayIndexes": true,
"ignore": [0, 1]
}]
0,1两个数字以及数组下标都不会被当成magic number。
最后,我们还规定这类数字必须使用ES6的const声明为常量:
"no-magic-numbers": ["error", {
"enforceConst": true
}]
限制函数的最大参数个数max-params
在实际开发中,代码会随着需求的变化而变化,比如一个显示弹出框的方法:
function showDialog (title, content) {
...
}
showDialog('标题', '内容');
最初只需要控制弹出框的标题和内容,showDialog()方法也就只有两个参数,简单明了。后来需要控制弹出框的位置、大小以及遮罩的显示,代码变成了这样:
function showDialog (title, content, position, size, showShadow) {
...
}
showDialog('标题', '内容', [200, 400], [500, 500], true);
这时代码变得难以阅读,而且在调用的时候需要记住每个参数的位置,很不方便。通过max-params规则:
"max-params": ["error", 4]
要求创建的方法中参数不能超过4个后,上面的代码必须改写为下面的版本才能通过检查:
function showDialog (options) {
...
}
showDialog({
title: '标题',
content: '内容',
position: [200, 400],
size: [500, 300],
showShadow: false
});
将所有的参数配置包含在一个object类型的参数中,增强了代码的灵活性和可读性。
尽管我们的项目中很早就引入了ES6,但是代码中依然存在大量ES5和ES6的语法混用。我们选取了下面几条规则来推广ES6的使用:
禁用var关键字no-var
ES6的let关键字解决了var关键字无法实现块作用域的问题;const关键字解决了var关键字无法定义常量的问题。通过规则:
"no-var": "error"
禁用var关键字后,代码必须根据不同场合选用let或const替代var。
箭头回调函数优先prefer-arrow-callback
JavaScript编程离不开回调函数,但回调函数常常会遇到this引用的指向问题。在ES5中,虽然可通过重新给this赋值:
var that = this;
$.get('a.php', function (res) {
that.success(res.data);
});
或改变this绑定对象:
setTimeout(function () {
this.tip('success')
}.bind(this), 500);
这两种方式来解决这个问题,但代码却显得不够简洁美观,而在ES6中则提供了箭头回调函数这一更简洁的方式:
$.get('a.php', (res) => {
this.success(res.data);
});
...
setTimeout( () => {
this.tip('success')
}, 500);
ES6的箭头回调函数标记法"()=>{}"等同于ES5中的"function (){}.bind(this)"。因为项目的已有代码中这种用法过多,大范围修改工作量较大,对于这个用法我们只设置了警告规则提醒开发者注意,并不要求强制修改:
"prefer-arrow-callback": "warn"
模版优先prefer-template
早期的JavaScript项目中拼接html字符串的做法十分常见:
var person = {
name: '小明',
...
};
var htmlStr =
'<div>' +
'<label>姓名:</label><span>' + person.name + '</span> +
...
'</div>';
所以很多前端库都会提供模版方法,而ES6的语言特性内置了对模版的支持:
var person = {
name: '小明',
...
};
var htmlStr = `
<div>
<label>姓名:</label><span>${ person.name }</span>
...
</div>`;
通过规则:
"prefer-template": "error"
将字符串的拼接操作统一为ES6的模板操作。