翻译:Jessie
原文连接:连接
javascript
每当阅读 JavaScript 代码的时候,你是否有如下感觉:html
上面这些就是典型的不良的编码习惯。java
在这篇文章里,我会描述在 JavaScript 中 5 个常见 不良的编码习惯,最重要的是我会推荐个人可行性建议来去摆脱托这些习惯。git
JavaScript 是弱类型语言, 若是使用正确,这是一个好处,由于它给你提供了灵活性github
大多数操做符+ - * / ==
(但不是===
)在处理不一样类型的操做数时使用类型的隐式转换数组
语法 if (condition) {...}
while(condition) {...}
隐式的将条件转换为布尔值。安全
下面的例子依赖隐式类型转换,我打赌你会感受到困惑:bash
console.log("2" + "1"); // => "21"
console.log("2" - "1"); // => 1
console.log('' == 0); // => true
console.log(true == []); // -> false
console.log(true == ![]); // -> false
复制代码
过度依赖隐式类型转换是一种坏习惯, 首先,它让你的代码在边界状况下面不那么稳定,其次,你增长了引入难以复制和修复的 bug 的机会。ide
咱们实现一个传入对象类型参数的函数,若是属性不存在,这个函数返回一个默认值:函数
function getProp(object, propertyName, defaultValue) {
if (!object[propertyName]) {
return defaultValue;
}
return object[propertyName];
}
const hero = {
name: 'Batman',
isVillian: false
};
console.log(getProp(hero, 'name', 'Unknown')); // => 'Batman'
复制代码
getProp
获取 name
属性的值是 Batman
。
尝试去访问 isVillian
属性会怎么样呢:
console.log(getProp(hero, 'isVillian', true)); // => true
复制代码
这是个错误, 即便 hero
的 isVillian
属性值是 false
, getProp()
函数返回不正确的 true
。
之因此会发生这种状况,是由于属性存在校验依赖于 if (!object[propertyName]) {...}
隐式转换为布尔值。
这种类型的错误很难发现,去修复这个函数,须要明确验证值的类型:
function getPropFixed(object, propertyName, defaultValue) {
if (object[propertyName] === undefined) {
return defaultValue;
}
return object[propertyName];
}
const hero = {
name: 'Batman',
isVillian: false
};
console.log(getPropFixed(hero, 'isVillian', true)); // => false
复制代码
object[propertyName] === undefined
明确的验证属性访问器是否为 undefined
。
你知道其余方法去校验属性是否在对象里面?若是是这样,欢迎在下面留言!
边注: 第四节建议避免直接使用 undefined
。因此能够用 in
操做符改进上述解决方案。
function getPropFixedBetter(object, propertyName, defaultValue) {
if (!(propertyName in object)) {
return defaultValue;
}
return object[propertyName];
}
复制代码
下面是个人建议:只要有可能,不要使用隐式类型转换。 相反,确认变量和函数参数始终拥有相同的类型。必要时使用显式类型转换。
最佳实践列表:
===
进行比较==
operand1 + operand2
: 两边的操做数应该是数值或者字符串类型- * / % **
:两边操做数应该是数值类型if (condition) {...}, while (condition) {...}
等语法:条件应该是布尔类型你可能会说这种方法须要编写更多的代码...是的!可是使用显式方法,你控制了你代码的行为。再者,显式提升了可读性。
JavaScript 的有趣之处在于它的做者没料到这种语言会如此流行。
基于 JavaScript 构建应用程序的复杂性比语言发展的速度还要快。这种状况致使开发者使用 JavaScript 技巧和变通方法,只是为了功能实现。
一个典型例子是查询一个数组是否包含一个项,我历来不喜欢使用 array.indexOf(item) !== -1
去验证某一项是否存在。
ECMAScript 2015 及更高版本更强大,你可使用新语法特性来安全重构许多技巧。
重构 array.indexOf(item) !== -1
以支持新的 ES2015 方法 array.includes(item)
。
按照 my compiled list of refactorings 去移除你 JavaScript 代码中老的技巧。
在 ES2015 以前,JavaScript 变量是函数做用域的。所以,您可能养成了将全部变量声明为函数做用域的坏习惯。
咱们来看一个例子:
function someFunc(array) {
var index, item, length = array.length;
/*
* Lots of code
*/
for (index = 0; index < length; index++) {
item = array[index];
// Use `item`
}
return someResult;
}
复制代码
变量 index
item
length
都是函数做用域,可是这些变量污染了函数做用域由于他们只是在 for()
代码块内使用。
根据块级做用域变量 let
和 const
的介绍,你应该尽可能限制变量的生命周期。
让咱们清理函数做用域:
function someFunc(array) {
/*
* Lots of code
*/
const length = array.length;
for (let index = 0; index < length; index++) {
const item = array[index];
// Use `item`
}
return someResult;
}
复制代码
index
和 item
变量受限于 for()
循环块范围, length
被移到靠近使用的地方。
重构的代码很容易明白由于变量不会分布在整个函数做用域内。它们放在使用地点附近。
参数定义在使用的区块范围内:
// Bad
let message;
// ...
if (notFound) {
message = 'Item not found';
// Use `message`
}
复制代码
// Good
if (notFound) {
const message = 'Item not found';
// Use `message`
}
复制代码
// Bad
let item;
for (item of array) {
// Use `item`
}
复制代码
// Good
for (const item of array) {
// Use `item`
}
复制代码
一个变量没赋值会被计算为 undefined
。例如:
let count;
console.log(count); // => undefined
const hero = {
name: 'Batman'
};
console.log(hero.city); // => undefined
复制代码
count
变量被定义,可是没有初始值,JavaScript 隐式赋值为 undefined
。
当访问不存的属性 hero.city
,也是返回 undefined
。
为何直接使用 undefined
是个坏习惯? 由于当你开始对比 undefined
时,你正在处理未初始化状态的变量。
变量、对象属性、数组在使用前必须有初始化的值!
JavaScript 提供了不少可能性来避免与 undefined
进行比较。
// Bad
const object = {
prop: 'value'
};
if (object.nonExistingProp === undefined) {
// ...
}
复制代码
// Good
const object = {
prop: 'value'
};
if ('nonExistingProp' in object) {
// ...
}
复制代码
// Bad
function foo(options) {
if (object.optionalProp1 === undefined) {
object.optionalProp1 = 'Default value 1';
}
// ...
}
复制代码
// Good
function foo(options) {
const defaultProps = {
optionalProp1: 'Default value 1'
};
options = {
...defaultProps,
...options
};
// ...
}
复制代码
// Bad
function foo(param1, param2) {
if (param2 === undefined) {
param2 = 'Some default value';
}
// ...
}
复制代码
// Good
function foo(param1, param2 = 'Some default value') {
// ...
}
复制代码
null
是一个缺乏对象的指示符。
你应该努力去避免在函数中返回 null
, 更重要的是,避免使用 null 参数去访问函数。
只要 null
出如今你的调用堆栈中,你必须在每一个可能访问 null
的函数中检验它是否存在, 这会产生错误。
function bar(something) {
if (something) {
return foo({ value: 'Some value' });
} else {
return foo(null);
}
}
function foo(options) {
let value = null;
if (options !== null) {
value = options.value;
// ...
}
return value;
}
复制代码
尝试去书写没有涉及 null
的代码。可能的备选方案是 try/catch
机制,默认对象的用法。
ALGOL 的创造者 Tony Hoare ,曾经说过:
“我称之为十亿美圆的错误...[...] 我正在设计第一个用于面向对象语言的引用的全面类型系统。[...]可是我没法抗拒提出空引用的诱惑,很简单由于它很容易实现。 这致使无数错误,漏洞和系统崩溃,在过去40年里,这可能致使数十亿美圆的痛苦和伤害。”
The worst mistake of computer science” 这篇文章深刻解释了为何 null
会对你的代码质量形成伤害。
还有什么比阅读混乱的编码风格更使人畏惧的呢?你永远不知道会发生什么!
假如代码库包含不一样不少开发者不一样的编码风格会怎么样?各类各样的字符涂鸦墙。
整个团队和应用程序代码库中相同的编码风格是必须的。 它提升了代码的可读性。
有用的编码风格的示例:
但说实话,我很懒。当我在截止日期前,或者在准备回家以前提交时,我可能就“忘记”了优化个人代码。
“我本身很懒”的意思是:保持代码不变,之后更新。可是之后意味着永远不会。
我推荐自动化编码风格验证的过程:
从 eslint-prettier-husky-boilerplate.开始。
书写高质量、简洁的的代码须要自律,克服不良的编码习惯。
JavaScript 是一个宽容的语言,有不少的灵活性。 可是你要注意你用的功能是什么。 个人建议是避免隐式类型转换并减小undefined
和 null
的使用。
近期 JavaScript 发展十分迅速,识别复杂的代码,而且使用最新的 JavaScript 特性去重构它。
贯穿代码库的一致的编码风格有利于可读性。
良好的编码技一直是一个成功的解决方案。
你知道 JavaScript 中还有哪些很差的编码习惯吗?