风格指南"/>
Airbnb JavaScript 风格指南
目录
- 1. 类型
- 1.1 基本类型: 你可以直接获取到基本类型的值。(直接存取)
- 1.2 复杂类型: 复杂类型赋值是获取到他的引用的值。
- 2. 引用
- 2.1 所有的赋值都用 const,避免使用 var。eslint: prefer-const, no-const-assign
- 2.2 如果你一定要对参数重新赋值,使用 let,而不是 var。eslint: no-var
- 2.3 let 和 const 都是块级作用域
- 2.4 思考:为什么要用`let`代替`var`
- 3. 对象
- 3.1 使用字面值创建对象
- 3.2 使用计算属性名创建一个带有动态属性名的对象
- 3.3 用对象方法简写
- 3.4 用属性值缩写
- 3.5 将你的所有缩写放在对象声明的前面
- 3.6 只对那些无效的标示使用引号 `''`
- 3.7 不要直接调用 Object.prototype上的方法,如 hasOwnProperty、propertyIsEnumerable、isPrototypeOf。
- 3.8 对象浅拷贝时,更推荐使用扩展运算符(即 ... 运算符),而不是 Object.assign。获取对象指定的几个属性时,用对象的 rest 解构运算符(即 ... 运算符)更好。
- 4. 数组
- 4.1 用字面量创建数组
- 4.2 用 Array#push 代替直接向数组中添加一个值
- 4.3 用扩展运算符做数组浅拷贝
- 4.4 用 ... 运算符而不是 Array.from 来将一个可迭代的对象转换成数组
- 4.5 用 Array.from 将一个类数组对象转成一个数组
- 4.6 用 Array.from 而不是 ... 运算符去做 map 遍历。 因为这样可以避免创建一个临时数组
- 4.7 在数组方法的回调函数中使用 return 语句。如果函数体由一条返回一个表达式的语句组成,并且这个表达式没有副作用, 这个时候可以忽略 return
- 4.8如果一个数组有很多行,在数组的 `[` 后和 `]` 前断行。请看下面示例:
1. 类型
1.1 基本类型: 你可以直接获取到基本类型的值。(直接存取)
string
,number
,boolean
,null
,undefined
,symbol
,bigint
。
symbol
为ES6新引入的原始数据类型,表示独一无二的值,引入原因是为了从根本上防止对象属性名的冲突,故其作用为作为对象属性的标识符。BigInt
是通过在整数末尾附加 n 或调用构造函数来创建的。BigInt
可以表示任意大的整数。
const foo = 1;
let bar = foo;bar = 9;console.log(foo, bar); // => 1, 9
由于 Symbols 和 BigInts 不能被正确的 polyfill。所以不应在不能原生支持这些类型的环境或浏览器中使用他们。
理解这句话:
polyfill
是用JavaScript来实现一些浏览器不支持的原生API。
例如,querySelectorAll是很多现代浏览器都支持的原生Web API,但是有些古老的浏览器并不支持,那么假设有人写了库,只要用了这个库, 你就可以在古老的浏览器里面使用document.querySelectorAll,使用方法跟现代浏览器原生API无异。那么这个库就可以称为Polyfill或者Polyfiller。一个Polyfill
是用来抹平新老浏览器标准原生API
之间的差距。
目前bigInt
浏览器支持情况并不理想,只有 Chrome 支持较好,其他浏览器支持不好。由于和其他 JavaScript
新特性不同,BigInt 不能很好的被编译为 ES5。因为 BigInt
中修改了运算符的工作行为,这些行为是不能直接被 polyfill
转换的。
如果要使用bigInt
目前,更好的选择是使用JSBI
库,纯JS实现了bigInt
。使用JSBI的一个优点是,一旦浏览器支持,就不需要重写代码。 相反,可以使用babel插件自动将JSBI代码编译为原生 BigInt代码。
Babel是一个 JavaScript编译器。他把最新版的javascript编译成当下可以执行的版本,即 利用babel就可以让我们在当前的项目中随意的使用这些新最新的es6,甚至es7的语法。babel作用:(1)语法转换(将高级语法解析为当前可用的实现)(2)源代码转换(codemods)(3)Polyfill 目标环境中缺少的功能(通过如 core-js 的第三方 polyfill)babel-core:Babel编译器的核心,Babel默认只转换新的javascript句法,而不转换新的API,比如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise等全局对象,因此,转换这些新的API需要一个垫片库polyfill。
1.2 复杂类型: 复杂类型赋值是获取到他的引用的值。
object
,array
,function
const foo = [1, 2];
const bar = foo;bar[0] = 9;console.log(foo[0], bar[0]); // => 9, 9
2. 引用
2.1 所有的赋值都用 const,避免使用 var。eslint: prefer-const, no-const-assign
2.2 如果你一定要对参数重新赋值,使用 let,而不是 var。eslint: no-var
- 声明创建一个值时用
const
而不用var
,这样可以保证你声明的值不会被重定义
在多人开发一个项目时,比如都定义了一个变量a,但各自用途不同,后面定义的a会把前面定义的覆盖掉。
2.3 let 和 const 都是块级作用域
// const 和 let 都只存在于它被定义的那个块级作用域。
{let a = 1;const b = 1;
}
console.log(a); // 报错a未定义
console.log(b); // 报错
2.4 思考:为什么要用let
代替var
JS中存在变量提升(变量提升:JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。带var
和function
挂关键字会导致变量提升)
变量提升会带来一些问题:
1. 变量可能会被覆盖掉
var myname = 1;
function showName() {//var myname(变量提升操作)console.log(myname); //undefinedif(0) {var myname = 2;}console.log(myname); //undefined
}
showName();
2. 本应销毁的变量没有被销毁
function foo(){// var i(变量提升)for (var i = 0; i < 7; i++) {}console.log(i); //7
}
foo()
在创建执行上下文阶段,变量 i 就已经被提升了,所以当 for 循环结束之后,变量 i 并没有被销毁。
ES6 引入的 let
和 const
关键字就是为了解决变量提升带来的问题。
function varTest() {var x = 1;if (true) {var x = 2; // 同样的变量!console.log(x); // 2}console.log(x); // 2
}
有两个地方都定义了变量 x,第一个地方在函数块的顶部,第二个地方在 if 块的内部,由于 var 的作用范围是整个函数,所以在编译阶段,最终只生成了一个变量 x,函数体内所有对 x 的赋值操作都会直接改变变量环境中的 x 值。
function letTest() {let x = 1;if (true) {let x = 2; // 不同的变量console.log(x); // 2}console.log(x); // 1
}
let支持块级作用域,代码块内部定义的变量在代码块外部是访问不到的,并且等该代码块中的代码执行完成之后,代码块中定义的变量会被销毁。
3. 对象
3.1 使用字面值创建对象
不用new
关键字,用{}
。
// bad
const item = new Object();// good
const item = {};
3.2 使用计算属性名创建一个带有动态属性名的对象
创建拥有动态属性名的对象时,用计算属性名来表示,这样可以在创建对象时,将所有的属性写在同一个地方。
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,
};
3.3 用对象方法简写
// bad
const atom = {value: 1,addValue: function (value) {return atom.value + value;},
};// good
const atom = {value: 1,// 对象的方法addValue(value) {return atom.value + value;},
};
3.4 用属性值缩写
如果属性名和属性值一样的话,可以缩写。
const lukeSkywalker = 'Luke Skywalker';// bad
const obj = {lukeSkywalker: lukeSkywalker,
};// good
const obj = {lukeSkywalker,
};
3.5 将你的所有缩写放在对象声明的前面
因为这样能更方便地知道有哪些属性用了缩写。
const anakinSkywalker = 'Anakin Skywalker';
const lukeSkywalker = 'Luke Skywalker';// bad
const obj = {episodeOne: 1,twoJediWalkIntoACantina: 2,lukeSkywalker,episodeThree: 3,mayTheFourth: 4,anakinSkywalker,
};// good
const obj = {lukeSkywalker,anakinSkywalker,episodeOne: 1,twoJediWalkIntoACantina: 2,episodeThree: 3,mayTheFourth: 4,
};
3.6 只对那些无效的标示使用引号 ''
通常我们认为这种方式主观上更易读。不仅优化了代码高亮,而且也更容易被许多 JS 引擎优化。
// bad
const bad = {'foo': 3,'bar': 4,'data-blah': 5,
};// good
const good = {foo: 3,bar: 4,'data-blah': 5,
};
3.7 不要直接调用 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'; //
console.log(has(object, key));
使用Object.prototype.hasOwnProperty.call()
的原因:
- 如果使用
Object.create(null)
创建的对象的原型是null
,而不是继承自Object.prototype
,所以没有hasOwnProperty
方法,调用只会得到undefined
。 - 如果
hasOwnProperty
在object
的原型链中被重写 - 如果
hasOwnProperty
在object
上被重新声明
3.8 对象浅拷贝时,更推荐使用扩展运算符(即 … 运算符),而不是 Object.assign。获取对象指定的几个属性时,用对象的 rest 解构运算符(即 … 运算符)更好。
// very bad,拷贝引用地址,改变copy,同时也会改变original的值
const original = { a: 1, b: 2 };
const copy = Object.assign(original, { c: 3 });
delete copy.a; // so does this// bad,只改变第一个对象,所以这里original并没有改变
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }// good es6 扩展运算符 ...,返回一个新的对象,copy是一个新的对象,在内存中另开辟了一个新的空间。
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 }
4. 数组
4.1 用字面量创建数组
不用new
,用[]
// bad
const items = new Array();// good
const items = [];
4.2 用 Array#push 代替直接向数组中添加一个值
const someStack = [];// bad
someStack[someStack.length] = 'abracadabra';// good
someStack.push('abracadabra');
原因:vue
针对数组重写了push
方法,使用push
更新数组vue
才会监听到,进而更新视图。反之,使用数组下标修改数组不能触发视图的更新。
4.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];
4.4 用 … 运算符而不是 Array.from 来将一个可迭代的对象转换成数组
可迭代对象:若一个对象或其原型链中的任意一个对象带有 Symbol.iterator
键(key)的属性,则这个对象就是可迭代对象。
可迭代对象有:String
、Array
、Map
(Map 对象保存键值对,并且能够记住键的原始插入顺序) 和 Set
(Set 对象允许你存储任何类型的唯一值,无论是原始值或者是对象引用)。
Array.from()
方法对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
const foo = document.querySelectorAll('.foo');// good
const nodes = Array.from(foo);// best
const nodes = [...foo];
4.5 用 Array.from 将一个类数组对象转成一个数组
类数组:有索引和length
属性,但可能没有原生数组的其他方法。
const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };// bad,相当于把参数全部截取,然后返回成一个新数组。本质就是arrLike这个对象使用了数组的slice这个方法
const arr = Array.prototype.slice.call(arrLike);// good
const arr = Array.from(arrLike);
4.6 用 Array.from 而不是 … 运算符去做 map 遍历。 因为这样可以避免创建一个临时数组
// bad
const baz = [...foo].map(bar);// good,Array.from可以接收第二个参数,作用类似于数组的map方法,数组中的每个元素会执行该回调函数。
const baz = Array.from(foo, bar);
4.7 在数组方法的回调函数中使用 return 语句。如果函数体由一条返回一个表达式的语句组成,并且这个表达式没有副作用, 这个时候可以忽略 return
// good
[1, 2, 3].map((x) => {const y = x + 1;return x * y;
});// good 函数只有一个语句
[1, 2, 3].map(x => x + 1);// bad - 没有返回值, 因为在第一次迭代后 acc 就变成 undefined 了。
//reduce,对数组中的每个元素执行一个我们提供的函数,结果汇总为单个返回值。acc就是累计器,上一次调用回调函数时,回调函数返回的值。
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item);acc[index] = flatten;
});// good,结果[0, 1, 2, 3, 4, 5]
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {const flatten = acc.concat(item);acc[index] = flatten;return flatten;
});// bad
inbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Mockingbird') {return author === 'Harper Lee';} else {return false;}
});// good
inbox.filter((msg) => {const { subject, author } = msg;if (subject === 'Mockingbird') {return author === 'Harper Lee';}return false;
});
4.8如果一个数组有很多行,在数组的 [
后和 ]
前断行。请看下面示例:
// bad
const arr = [[0, 1], [2, 3], [4, 5],
];const objectInArray = [{id: 1,
}, {id: 2,
}];const numberInArray = [1, 2,
];// good
const arr = [[0, 1], [2, 3], [4, 5]];const objectInArray = [{id: 1,},{id: 2,},
];const numberInArray = [1,2,
];
待补充…
参考:
1. polyfill----
2. 变量提升----=%257B%2522request%255Fid%2522%253A%2522164437829116780271988463%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164437829116780271988463&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-4-110489515.first_rank_v2_pc_rank_v29&utm_term=js+var的缺陷&spm=1018.2226.3001.4187
3. 扩展运算符和Object.assign的区别----=%257B%2522request%255Fid%2522%253A%2522164447964616781683919322%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164447964616781683919322&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-7-103233926.first_rank_v2_pc_rank_v29&utm_term=Object.assign和扩展运算符的区别&spm=1018.2226.3001.4187
更多推荐
Airbnb JavaScript 风格指南
发布评论