Eslint背后那些我们应该知道的为什么
前言
得益于git代码审查以及log规范化的解决方案中提到的任务,应用Eslint来检查公司的一个项目,在检查过程中就发现了很多与Eslint规范相悖的代码,整体下来错误达到一万多个。着实吓了宝宝一跳,接着使用ESlint提供的--fix
参数自动修复一些错误,把错误降到了2千多个,再修改一些规则,才把错误降到一千个以内。本篇博客想要讲的不是Eslint代码修改的艰辛,也不是说Eslint后面代码审查的原理实现,而是想从一些常见的错误中看到一些不一样的一面--那就是为什么Eslint会不建议这么写?Eslint的每一条规范都不会无缘无故设置的,在规范的后面肯定有其一定的风险,所以才会报错,那么根据目前项目检查的错误,我挑了下面几个常见的错误来解析后面我们应该知道的原理。
1、react/jsx-no-bind
该错误是Eslint插件eslint-plugin-react提出的。大致的意思就是在JSX Props中不要使用.bind()
或箭头函数,比如下面的例子:
<Button type="button" className="btn ant-btn-primary btn-xs" title="编辑" onClick={this._handleEditItem.bind(null,text)}>编辑</Button>
根据官方文档解释:在JSX的属性中使用bind
或者箭头函数将会造成每一次渲染的时候都会创建一个全新的函数来。这样做会导致性能降低,因为它会导致GC被调用得更加频繁。
那么问题来了,上面说的这段话原理是什么?我们不禁会这么问,不急,且听我细细道来。
1.1、为什么需要使用bind或者箭头函数
想必这个答案可以在call&apply&bind的学习这篇文章找到答案,这里再简单说一下:这是为了保证每次点击事件触发的时候this
指针永远有着正确的上下文环境。
那为什么上面是使用bind(null, text)
呢?因为这段代码截取自React.creatClass函数中,大家都知道该方法是会自动帮你绑定this
到组件的方法中的,所以传递null
即可,否则会有此警告:
You are binding a component method to the component. React does this for you automatically in a high-performance way, so you can safely remove this call
又因为在_handleEditItem
函数中需要传递参数,所以最后会使用bind(null, text)
,如果你的事件处理函数不需要参数,那么就没必要使用bind来传递参数(这种只在React.creatClass使用的情况下)
1.2、为什么不能在jsx不能使用bind或者箭头函数
在call&apply&bind的学习这篇文章中提到bind的实现原理是通过创建新函数封装旧函数,所以当你每次需要渲染该组件的时候,都会创建一个新函数,然后在每次Unmount组件的时候,该组件就得由GC回收。
1.3、 如何修改
我们以最新的React创建class方式说明如何修改。
在ES2015学习之class这篇文章的末尾提到class的自动绑定问题可以作为这里的解决方案,那就是在constructor中手动绑定方法。我们知道class上定义的方法都是原型方法,这些方法从一开始只会创建一次,然后我们在构造函数中使用一个新的变量this._onClick
指向该原型方法,之后无论渲染该组件多少次,在原型对象上的_onClick
只会创建一次新函数,而不断新建的无非就只有构造函数上的_onClick
这个变量而已。所以既节约了内存又不会不断重复地创建出多个函数对象。
比如:
class Foo extends React.Component {
constructor() {
super();
this._onClick = this._onClick.bind(this);
}
render() {
return (
<div onClick={this._onClick}>
Hello!
</div>
);
}
_onClick() {
// Do whatever you like, referencing "this" as appropriate
}
}
更多信息可以参考React v0.13.0 Beta 1
2、no-string-refs
该错误也是Eslint插件eslint-plugin-react提出的。大致的意思是禁止使用字符串引用。举个例子就明白了:
第一种做法:
var Hello = React.createClass({
render: function() {
return <div ref="hello">Hello, world.</div>;
}
});
var Hello = React.createClass({
componentDidMount: function() {
var component = this.refs.hello;
// ...do something with component
},
render: function() {
return <div ref="hello">Hello, world.</div>;
}
});
第二种做法:
var Hello = React.createClass({
componentDidMount: function() {
var component = this.hello;
// ...do something with component
},
render() {
return <div ref={(c) => { this.hello = c; }}>Hello, world.</div>;
}
});
根据官网的解释:
目前React支持使用两种方式来引用组件。第一种官方文档中比较传统的做法:使用一个字符串来标识,然后再需要使用到的地方时这么使用:this.refs.xxx;另外一种做法便是通过使用回调将值设置为`this`的一个属性,然后再需要使用的地方直接:this.xxx,然后推荐使用第二种方法。
那么为什么会推荐使用第二种做法呢?
谷歌查找了一下,有一些线索,不过貌似仍然没看懂。童鞋们你们看懂了吗?
- http://stackoverflow.com/questions/37468913/why-ref-string-is-legacy
- https://news.ycombinator.com/edit?id=12093234
- https://www.bountysource.com/issues/32479066-does-jsx-no-bind-contradicts-with-no-string-refs
注意上面的链接3,刚开始我也是很疑惑这条规则和刚才提到的第一条规则相冲突的,于是人家给出了一些答案以及解决办法的,细节可以链接进去查看。
3、no-prototype-builtins
该错误也是Eslint规范中提到的no-prototype-builtins提出的。大致的意思是不允许直接使用内建的Object.prototypes,比如下面的做法就是不推荐:
var hasBarProperty = foo.hasOwnProperty("bar");
而应该这样使用:
var hasBarProperty = {}.hasOwnProperty.call(foo, "bar");
官方是这样解释的:
在ECMAScript 5.1中,增加了Object.create这个函数,用来使能对象的创建时可以带有一个指定的[[Prototype]]。Object.create(null)通常用来创建一个可以作为Map的对象。这就可能导致错误当它假设对象从Object.prototype中有属性。
3.1、hasOwnProperty
首先我们需要知道hasOwnProperty这个方法是做啥的?
hasOwnProperty是为了检测对象是否有指定的属性,如果有返回true,否则false。
该方法可以用来检测指定的属性是否是对象的直属属性而不是原型链上的,也就是说它不会去检查原型链上的所有属性。
3.2、为什么需要这条规则
其实是因为在ES5.1中增加了Onject.create这个创建对象的新方法。假如你如果使用Object.create(null)创建新对象的话,那么有:
let foo = Object.create(null);
foo.bar = 'test';
var hasBarProperty = foo.hasOwnProperty("bar");
// 'Uncaught TypeError: obj.hasOwnProperty is not a function'
此时会报错,因为foo的原型对象是null
,所以无法找到hasOwnProperty
这个方法(因为该方法是Object构造函数上的方法),因为这样的创建方法导致二者断开了联系,然后你再使用这种方法去判断一个属性是否是一个对象的直属属性,那就只能报错了。
你这时也许会问,哪个二货会用这种方法去创建呢?
有的,那就是使用Object创建Map对象,可以参考MDN-Map
那么我们可以保证无论用什么方法肯定不会报错的检查方法是什么呢?
var hasBarProperty = {}.hasOwnProperty.call(foo, "bar");
或者
var hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar");
4、prefer-spread
该错误也是Eslint规范中提到的prefer-spread提出的。大致的意思是推荐使用spread操作符来替代.apply()
,比如下面的做法就是不推荐:
var args = [1, 2, 3, 4];
Math.max.apply(Math, args);
而应该这样使用:
var args = [1, 2, 3, 4];
Math.max(...args);
官方是这样解释的:
在ES2015之前,如果调用可变参数的函数一般会去使用apply方法,因为apply方法的参数是数组传递的,其数组的长度是可以扩展的,参考[call&apply&bind的学习](https://blog.5udou.cn/blog/detail/callapplybindDe-Xue-Xi-61)。
如今有了ES2015,我们就建议使用延展符号...
那么我们就要弄懂延展符号了。
4.1、spread operator
spread语法允许你在多个参数(函数调用的时候)、多元素(数组迭代的时候)、多变量(解构赋值的时候)如我们期望的那样展开所有的参数。
比如在函数调用的时候,如果使用以前的方法:
function concat () {
return Array.prototype.slice.call(arguments).join(' ');
}
var result = concat('this', 'was', 'call', 'syntax');
console.log(result);
如果使用spread语法的时候那么很简洁:
function concat (...words) {
return words.join(' ');
}
var result = concat('this', 'is', 'spread', 'syntax');
console.log(result);
还有很多例子,可以参考MDN-Spread_operator
4.2、为什么优先使用spread?
个人觉得应该是可以有更加简洁的代码以及更好地扩展性考虑的,当然新语法的出现必定是有更多的优越性,所以让我更多地使用新语法,拥抱ES2015以及未来吧。
参考
公众号关注一波~
网站源码:linxiaowu66 · 豆米的博客
Follow:linxiaowu66 · Github
关于评论和留言
如果对本文 Eslint背后那些我们应该知道的为什么 的内容有疑问,请在下面的评论系统中留言,谢谢。