翻译:Jessie
原文链接:链接
每当阅读 JavaScript 代码的时候,你是否有以下感受:
- 你几乎不明白代码的作用是什么?
- 代码使用了很多 JavaScript 技巧?
- 命名和代码风格相当混乱?
上面这些就是典型的不良的编码习惯。
在这篇文章里,我会描述在 JavaScript 中 5 个常见 不良的编码习惯,最重要的是我会推荐我的可行性建议来去摆脱托这些习惯。
1. 不要使用隐式类型转换
JavaScript 是弱类型语言, 如果使用正确,这是一个好处,因为它给你提供了灵活性
大多数操作符+ - * / ==(但不是===)在处理不同类型的操作数时使用类型的隐式转换
语法 if (condition) {...} while(condition) {...} 隐式的将条件转换为布尔值。
下面的例子依赖隐式类型转换,我打赌你会感觉到困惑:
console.log("2" + "1"); // => "21"
console.log("2" - "1"); // => 1
console.log('' == 0); // => true
console.log(true == []); // -> false
console.log(true == ![]); // -> false
过分依赖隐式类型转换是一种坏习惯, 首先,它让你的代码在边界情况下面不那么稳定,其次,你增加了引入难以复制和修复的 bug 的机会。
我们实现一个传入对象类型参数的函数,如果属性不存在,这个函数返回一个默认值:
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) {...}等语法:条件应该是布尔类型
你可能会说这种方法需要编写更多的代码...是的!但是使用显式方法,你控制了你代码的行为。再者,显式提高了可读性。
2. 不要使用旧的 JavaScript 技巧
JavaScript 的有趣之处在于它的作者没料到这种语言会如此流行。
基于 JavaScript 构建应用程序的复杂性比语言发展的速度还要快。这种情况导致开发者使用 JavaScript 技巧和变通方法,只是为了功能实现。
一个典型例子是查询一个数组是否包含一个项,我从来不喜欢使用 array.indexOf(item) !== -1 去验证某一项是否存在。
ECMAScript 2015 及更高版本更强大,你可以使用新语法特性来安全重构许多技巧。
重构 array.indexOf(item) !== -1 以支持新的 ES2015 方法 array.includes(item)。
按照 my compiled list of refactorings 去移除你 JavaScript 代码中老的技巧。
3. 不要污染函数的作用域
在 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 被移到靠近使用的地方。
重构的代码很容易明白因为变量不会分布在整个函数作用域内。它们放在使用地点附近。
参数定义在使用的区块范围内:
if 块范围
// Bad
let message;
// ...
if (notFound) {
message = 'Item not found';
// Use `message`
}
// Good
if (notFound) {
const message = 'Item not found';
// Use `message`
}
for 块范围
// Bad
let item;
for (item of array) {
// Use `item`
}
// Good
for (const item of array) {
// Use `item`
}
4. 尽量去避免 undefined 和 null
一个变量没赋值会被计算为 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 会对你的代码质量造成伤害。
5. 不要使用随意的代码风格,执行一个标准
还有什么比阅读混乱的编码风格更令人畏惧的呢?你永远不知道会发生什么!
假如代码库包含不同很多开发者不同的编码风格会怎么样?各种各样的字符涂鸦墙。
整个团队和应用程序代码库中相同的编码风格是必须的。 它提高了代码的可读性。
有用的编码风格的示例:
但说实话,我很懒。当我在截止日期前,或者在准备回家之前提交时,我可能就“忘记”了优化我的代码。
“我自己很懒”的意思是:保持代码不变,以后更新。但是以后意味着永远不会。
我推荐自动化编码风格验证的过程:
- 安装 eslint
- 配置适合你代码风格的 eslint
- 设置预提交的 hook, 在提交之前运行 eslint 验证
从 eslint-prettier-husky-boilerplate.开始。
6. 总结
书写高质量、简洁的的代码需要自律,克服不良的编码习惯。
JavaScript 是一个宽容的语言,有很多的灵活性。 但是你要注意你用的功能是什么。 我的建议是避免隐式类型转换并减少undefined 和 null的使用。
近期 JavaScript 发展十分迅速,识别复杂的代码,并且使用最新的 JavaScript 特性去重构它。
贯穿代码库的一致的编码风格有利于可读性。
良好的编码技一直是一个成功的解决方案。
你知道 JavaScript 中还有哪些不好的编码习惯吗?