javascript
JavaScript速查表
目录
基础知识
类型
- 基本类型
最新的 ECMAScript 标准定义了 8 种数据类型,分别是string
number
bigint
boolean
null
undefined
symbol
(ECMAScript 2016新增)
所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。
- 引用类型
Object
(包含普通对象-Object,数组对象-Array,正则对象-RegExp,日期对象-Date,数学函数-Math,函数对象-Function)
使用 typeof 运算符检查: |
引用
推荐常量赋值都使用const
, 值可能会发生改变的变量赋值都使用 let
。
为什么?
let
const
都是块级作用域,而var
是函数级作用域
|
对象
-
使用字面语法创建对象:
// bad
const item = new Object();
// good
const item = {}; -
在创建具有动态属性名称的对象时使用属性名称:
function getKey(k) {
return `a key named ${k}`;
}
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
}; -
属性值简写,并且推荐将缩写 写在前面 :
const lukeSkywalker = 'Luke Skywalker';
//常量名就是你想设置的属性名
// bad
const obj = {
lukeSkywalker: lukeSkywalker,
};
// good
const obj = {
lukeSkywalker,
};
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';
// good
const obj = {
lukeSkywalker,
anakinSkywalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
}; -
不要直接调用
Object.prototype
上的方法,如hasOwnProperty
、propertyIsEnumerable
、isPrototypeOf
为什么?在一些有问题的对象上,这些方法可能会被屏蔽掉,如:
{ hasOwnProperty: false }
或空对象Object.create(null)
// bad
console.log(object.hasOwnProperty(key));
// good
console.log(Object.prototype.hasOwnProperty.call(object, key));
// best
const has = Object.prototype.hasOwnProperty;
console.log(has.call(object, key));
/* or */
import has from 'has'; // https://www.npmjs.com/package/has
console.log(has(object, key)); -
对象拷贝时,推荐使用
...
运算符来代替Object.assign
, 获取大对象的多个属性时,也推荐使用...
运算符// very bad, 因为line2的操作更改了original
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
// 将不需要的属性删除了
delete copy.a;
// bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
// good 使用 es6 扩展运算符 ...
const original = { a: 1, b: 2 };
// 浅拷贝
const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
// rest 解构运算符
const { a, ...noA } = copy; // noA => { b: 2, c: 3 }
数组
-
用扩展运算符做数组浅拷贝,类似上面的对象浅拷贝:
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i += 1) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items]; -
用
...
运算符而不是Array.from
来将一个可迭代的对象转换成数组:const foo = document.querySelectorAll('.foo');
// good
const nodes = Array.from(foo);
// best
const nodes = [...foo]; -
使用
Array.from
而不是...
运算符去做 map 遍历。 因为这样可以避免创建一个临时数组:// bad
const baz = [...foo].map(bar);
// good
const baz = Array.from(foo, bar); -
如果一个数组有很多行,在数组的
[
后和]
前断行 :
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
const numberInArray = [
1,
2,
];
解构
-
用对象的解构赋值来获取和使用对象某个或多个属性值:
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
return `${firstName} ${lastName}`;
}
// good
function getFullName(user) {
const { firstName, lastName } = user;
return `${firstName} ${lastName}`;
}
// best
function getFullName({ firstName, lastName }) {
return `${firstName} ${lastName}`;
} -
数组解构:
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
const four = arr[3];
// good
const [first, second, _,four] = arr; -
多个返回值用对象的解构,而不是数组解构:
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// 数组解构,必须明确前后顺序
const [left, __, top] = processInput(input);
// good
function processInput(input) {
return { left, right, top, bottom };
}
// 只需要关注值,而不用关注顺序
const { left, top } = processInput(input);
字符串
-
当需要动态生成字符串时,使用模板字符串而不是字符串拼接:
// bad
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// good 可读性比上面更强
function sayHi(name) {
return `How are you, ${name}?`;
} -
永远不要使用
eval()
,该方法有太多漏洞。
变量
- 不要使用链式变量赋值
因为会产生隐式的全局变量
// bad |
-
不要使用一元自增自减运算符(
++
,--
)根据 eslint 文档,一元增量和减量语句受到自动分号插入的影响,并且可能会导致应用程序中的值递增或递减的静默错误。 使用
num + = 1
而不是num ++
或num ++
语句也是含义清晰的。
// bad |
属性
- 访问属性时使用点符号
const luke = { |
- 根据表达式访问属性时使用
[]
const luke = { |
测试
- 无论用哪个测试框架,都需要写测试。
- 尽量去写很多小而美的函数,减少突变的发生
- 小心 stub 和 mock —— 这会让你的测试变得容易出现问题。
- 100% 测试覆盖率是我们努力的目标,即便实际上很少达到。
- 每当你修了一个 bug, 都要尽量写一个回归测试。 如果一个 bug 修复了,没有回归测试,很可能以后会再次出问题。
公共约束
注释
- 多行注释用
/** ... */
// bad |
- 单行注释用
//
// bad |
- 用
// FIXME:
给问题注释,用// TODO:
去注释待办
分号
为什么?当 JavaScript 遇到没有分号结尾的一行,它会执行 自动插入分号 这一规则来决定行末是否加分号。如果 JavaScript 在你的断行里错误的插入了分号,就会出现一些古怪的行为。显式配置代码检查去检查没有带分号的地方可以帮助你防止这种错误。
// bad - raises exception |
命名规范
export default
导出模块A,则这个文件名也叫A.*
,import
时候的参数也叫A
:
// file 1 contents |
- 当你
export default
一个函数时,函数名用小驼峰,文件名和函数名一致, export 一个结构体/类/单例/函数库/对象 时用大驼峰。
function makeStyleGuide() { |
标准库
标准库中包含一些由于历史原因遗留的工具类
-
用
Number.isNaN
代替全局的isNaN
:// bad
isNaN('1.2'); // false
isNaN('1.2.3'); // true
// good
Number.isNaN('1.2.3'); // false
Number.isNaN(Number('1.2.3')); // true -
用
Number.isFinite
代替isFinite
// bad |
类与函数
函数
-
使用命名函数表达式而不是函数声明
为什么?这是因为函数声明会发生提升,这意味着在一个文件里函数很容易在其被定义之前就被引用了。这样伤害了代码可读性和可维护性。如果你发现一个函数又大又复杂,且这个函数妨碍了这个文件其他部分的理解性,你应当单独把这个函数提取成一个单独的模块。不管这个名字是不是由一个确定的变量推断出来的,别忘了给表达式清晰的命名(这在现代浏览器和类似 babel 编译器中很常见)。这消除了由匿名函数在错误调用栈产生的所有假设。 (讨论)
// bad
function foo() {
// ...
}
// bad
const foo = function () {
// ...
};
// good
// lexical name distinguished from the variable-referenced invocation(s)
// 函数表达式名和声明的函数名是不一样的
const short = function longUniqueMoreDescriptiveLexicalFoo() {
// ...
}; -
把立即执行函数包裹在圆括号里:
立即执行函数:Immediately Invoked Function expression = IIFE。 为什么?因为这样使代码读起来更清晰(译者注:我咋不觉得)。 另外,在模块化世界里,你几乎用不着 IIFE。
// immediately-invoked function expression (IIFE)
( ()=> {
console.log('Welcome to the Internet. Please follow me.');
}() ); -
不要用
arguments
命名参数。他的优先级高于每个函数作用域自带的arguments
对象,这会导致函数自带的arguments
值被覆盖:// bad
function foo(name, options, arguments) {
// ...
}
// good
function foo(name, options, args) {
// ...
} -
用默认参数语法而不是在函数里对参数重新赋值
// really bad
function handleThings(opts) {
// 如果 opts 的值为 false, 它会被赋值为 {}
// 虽然你想这么写,但是这个会带来一些微妙的 bug。
opts = opts || {};
// ...
}
// still bad
function handleThings(opts) {
if (opts === void 0) {
opts = {};
}
// ...
}
// good
function handleThings(opts = {}) {
// ...
} -
把默认参数赋值放在最后面
// bad
function handleThings(opts = {}, name) {
// ...
}
// good
function handleThings(name, opts = {}) {
// ...
} -
不要修改参数,也不要重新对函数参数赋值:
容易导致bug,另外重新对参数赋值也会导致优化问题。
// bad
function f1(a) {
a = 1;
// ...
}
function f2(a) {
if (!a) { a = 1; }
// ...
}
// good
function f3(a) {
const b = a || 1;
// ...
}
function f4(a = 1) {
// ...
}
箭头函数
-
当需要使用箭头函数的时候,使用它,但是不要滥用
当函数逻辑复杂时,不推荐使用箭头函数,而是单独抽出来放在一个函数里。
// bad
[1, 2, 3].map(function (x) {
const y = x + 1;
return x * y;
});
// good
[1, 2, 3].map((x) => {
const y = x + 1;
return x * y;
}); -
避免箭头函数与比较操作符混淆
// bad
const itemHeight = (item) => item.height <= 256 ? item.largeSize : item.smallSize;
// bad
const itemHeight = (item) => item.height >= 256 ? item.largeSize : item.smallSize;
// good
const itemHeight = (item) => (item.height <= 256 ? item.largeSize : item.smallSize);
// good
const itemHeight = (item) => {
const { height, largeSize, smallSize } = item;
return height <= 256 ? largeSize : smallSize;
};
类与构造函数
-
使用
class
语法。避免直接操作prototype
// bad
function Queue(contents = []) {
this.queue = [...contents];
}
Queue.prototype.pop = function () {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
};
// good
class Queue {
constructor(contents = []) {
this.queue = [...contents];
}
pop() {
const value = this.queue[0];
this.queue.splice(0, 1);
return value;
}
}
- 用 `extends` 实现继承:
> 为什么?它是一种内置的方法来继承原型功能而不破坏 `instanceof`
```javascript
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function () {
return this.queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this.queue[0];
}
} -
方法可以返回
this
来实现链式调用
// bad |
- 自定义
toString()
方法是可以的,但需要保证它可以正常工作
class Jedi { |
- 如果没有特别定义,类有默认的构造方法。一个空的构造函数或只是代表父类的构造函数是不需要写的。
// bad |
模块
- 使用(
import
/export
)模块
// bad |
- 不要导出可变的东西:
变化通常都是需要避免,特别是当你要输出可变的绑定。虽然在某些场景下可能需要这种技术,但总的来说应该导出常量。
// bad |
- import JavaScript文件不用包含扩展名
// bad |
迭代器与生成器
-
不要用迭代器。使用 JavaScript 高级函数代替
for-in
、for-of
用数组的这些迭代方法:
map()
/every()
/filter()
/find()
/findIndex()
/reduce()
/some()
/ … , 对象的这些方法Object.keys()
/Object.values()
/Object.entries()
得到一个数组,就能去遍历对象。const numbers = [1, 2, 3, 4, 5];
// bad
let sum = 0;
for (let num of numbers) {
sum += num;
}
sum === 15;
// good
let sum = 0;
numbers.forEach((num) => sum += num);
sum === 15;
// best (use the functional force)
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;
// bad
const increasedByOne = [];
for (let i = 0; i < numbers.length; i++) {
increasedByOne.push(numbers[i] + 1);
}
// good
const increasedByOne = [];
numbers.forEach((num) => {
increasedByOne.push(num + 1);
});
// best (keeping it functional)
const increasedByOne = numbers.map((num) => num + 1);
提升
var
声明会被提前到离他最近的作用域的最前面,但是它的赋值语句并没有提前。const
和let
被赋予了新的概念 暂时性死区 (TDZ)。 重要的是要知道为什么 typeof 不再安全。
// 我们知道这个不会工作,假设没有定义全局的 notDefined |
- 已命名函数表达式提升他的变量名,不是函数名或函数体
function example() { |
比较运算符与相等
-
用
===
和!==
严格比较而不是==
和!=
-
条件语句,例如if语句使用coercion与tobooleant抽象方法评估它们的表达式,始终遵循这些简单的规则:
- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evaluate to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string
''
, otherwise true
if ([0] && []) { |
- 三元表达式不应该嵌套,尽量保持单行表达式
// bad |
事件
- 当把数据载荷传递给事件时(例如是 DOM 还是像 Backbone 这样有很多属性的事件)。这使得后续的贡献者(程序员)向这个事件添加更多的数据时不用去找或者更新每个处理器。
// bad |
类型转换与强制转换
- 字符串
// => this.reviewScore = 9; |
- 数字: 用
Number
做类型转换,parseInt
转换string
应总是带上进制位
const inputValue = '4'; |
-
移位运算要小心
移位运算对大于 32 位的整数会导致意外行为。Discussion. 最大的 32 位整数是 2,147,483,647:
2147483647 >> 0 //=> 2147483647 |
推荐资源
-
网站:
-
书籍(为了尊重作者的版权,下列书籍仅开源书籍提供链接):
- JavaScript权威指南(原书第7版)
- 你不知道的JS
- Eloquent JavaScript