更优雅的方式处理数组

使用Set处理数组去重和元素剔除问题

Set是es6新增的一种数据结构,它和数组非常相似,但是成员的值都是唯一的,没有重复的值。它提供了4个语义化的API:

  1. add(value):添加某个值,返回Set结构本身。
  2. delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  3. has(value):返回一个布尔值,表示该值是否为Set的成员。
  4. clear():清除所有成员,没有返回值。

参考自@阮一峰 老师的《ECMAScript 6 入门》

那么我们可以用Set来干嘛呢?

第一个用法,数组去重。对于一个一维数组,我们可以先把它转化成Set,再配合...解构运算符重新转化为数组,达到去重的目的。请看例子:

1
2
3
4
5
6
7
const arr = [1, 1, 2, 2, 3, 4, 5, 5]

const newArr = [...new Set(arr)]

console.log(newArr)

// [1, 2, 3, 4, 5]

值得注意的是,这个方法不能对元素为“对象”的数组奏效:

1
2
3
4
5
6
7
const arr = [{ name: 'Alice', age: 12 }, { name: 'Alice', age: 12 }, { name: 'Bob', age: 13 }]

const newArr = [...new Set(arr)]

console.log(newArr)

// [{ name: 'Alice', age: 12 }, { name: 'Alice', age: 12 }, { name: 'Bob', age: 13 }]

这是因为Set判断元素是否重复的办法类似于===运算符,两个对象总是不相等的。

除了去重,Set提供的delete()方法也是非常实用。在以往的做法中,如果要删除数组中指定的元素,我们需要先获取该元素所在下标,然后通过splice()方法去删除对应下标的元素,在理解上容易引起混乱:

1
2
3
4
5
6
7
8
9
// 我想删除数组当中值为2的元素const arr = [1, 2, 3]
const index = arr.indexOf(2)
if (index !== -1) {
arr.splice(index, 1)
}

console.log(arr)

// [1, 3]

使用Set就清晰多了:

1
2
3
4
5
6
7
8
const arr = [1, 2, 3]
const set = newSet(arr)
set.delete(2)
arr = [...set]

console.log(arr)

// [1, 3]

使用map()方法和对象解构语法提取字段

请求后台接口返回的数据中,很可能会遇到下面这种数据格式:

1
2
3
4
5
6
7
studentInfo = [
{ name: 'Alice', age: 18, no: 2 },
{ name: 'Bob', age: 16, no: 5 },
{ name: 'Candy', age: 17, no: 3 },
{ name: 'Den', age: 18, no: 4 },
{ name: 'Eve', age: 16, no: 1 },
]

当我们要获取姓名列表、年龄列表和编号列表的时候,我们可以通过map()再配合对象的解构语法方便快捷地进行处理:

1
2
3
4
5
const nameList = studentInfo.map(({ name }) => name)
const ageList = studentInfo.map(({ age }) => age)
const noList = studentInfo.map(({ no }) => no)

// nameList: [ 'Alice', 'Bob', 'Candy', 'Den', 'Eve' ]// ageList: [ 18, 16, 17, 18, 16 ]// noList: [ 2, 5, 3, 4, 1 ]

使用filter()方法和对象解构语法过滤数组

接上上面的例子,如果我想获取一个“年龄小于等于17岁”的新列表,应该怎么做呢?类似map()方法,我们可以用filter()方法进行过滤:

1
2
3
4
5
6
7
8
9
10
11
const newStudentInfo = studentInfo.filter(({ age }) => {
return age <= 17
})

/*
newStudentInfo: [
{ name: 'Bob', age: 16, no: 5 },
{ name: 'Candy', age: 17, no: 3 },
{ name: 'Eve', age: 16, no: 1 }
]
*/

借助includes()方法求两个数组的差集

假设我们有以下两个数组:

1
2
var a = [1, 2, {s:3}, {s:4}, {s:5}]
var b = [{s:2}, {s:3}, {s:4}, 'a']

我们应该如何找到它们的差集呢?传统的方法可能需要把它们以Object形式hash化,但其实我们可以通过.includes()方法更加优雅方便地找出差集,代码如下:

1
2
3
4
5
6
7
8
var a = [1, 2, {s:3}, {s:4}, {s:5}].map(item =>JSON.stringify(item))
var b = [{s:2}, {s:3}, {s:4}, 'a'].map(item =>JSON.stringify(item))

var diff = a.concat(b)
.filter(v => !a.includes(v) || !b.includes(v))
.map(item =>JSON.parse(item))

// diff: [1, 2, {s:5}, {s:2}, "a"]

至于为什么要JSON.stringify(),是因为要对比两个“对象元素”是否相等,是无法直接以“对象”形式比较的(永远返回不相等)。


Array.includes 与条件判断

一般我们判断或用 ||

1
2
3
4
5
6
// condition
function test(fruit) {
if (fruit == "apple" || fruit == "strawberry") {
console.log("red");
}
}

如果我们有更多水果

1
2
3
4
5
6
7
function test(fruit) {
const redFruits = ["apple", "strawberry", "cherry", "cranberries"];

if (redFruits.includes(fruit)) {
console.log("red");
}
}

Set 与去重

ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 本身是一个构造函数,用来生成 Set 数据结构。

数组去重

1
2
3
const arr = [3, 5, 2, 2, 5, 5];
const unique = [...new Set(arr)];
// [3,5,2]

Array.from 方法可以将 Set 结构转为数组。我们可以专门编写使用一个去重的函数

1
2
3
4
5
function unique(array) {
return Array.from(new Set(array));
}

unique([1, 1, 2, 3]); // [1, 2, 3]

字符去重

1
2
3
let str = [...new Set("ababbc")].join("");
console.log(str);
// 'abc'

另外 Set 是如此强大,因此使用 Set 可以很容易地实现并集(Union)、交集(Intersect)和差集(Difference)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

Map 与字典类型数据

一般而已,JavaScript 实现字典数据是基于 Object 对象。但是 JavaScript 的对象的键只能是字符串。对于编程来说有很多不便。 ES6 提供了 Map 数据结构。它类似于 Object 对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值,字符串、数值、布尔值、数组、对象等等都可以当作键。

1
2
3
4
5
6
7
8
const resultMap = new Map()
.set(-1, {text:'小于',color:'yellow')
.set(0, {text:'等于',color:'black')
.set(1, {text:'大于',color:'green')
.set(null,{text:'没有物品',color:'red'})

let state = resultMap.get(null)
// {text:'没有物品',color:'red'}

Map 的遍历顺序就是插入顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
const map = new Map([["F", "no"], ["T", "yes"]]);

for (let key of map.keys) {
console.log(key);
}
// "F"
// "T"

for (let value of map.value()) {
console.log(value);
}
// "no"
// "yes"

函数式的方式处理数据

按照我的理解,函数式编程主张函数必须接受至少一个参数并返回一个值。所以所有的关于数据的操作,都可以用函数式的方式处理。

假设我们有这样的需求,需要先把数组 foo 中的对象结构更改,然后从中挑选出一些符合条件的对象,并且把这些对象放进新数组 result 里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
let foo = [
{
name: "Stark",
age: 21
},
{
name: "Jarvis",
age: 20
},
{
name: "Pepper",
age: 16
}
];

//我们希望得到结构稍微不同,age大于16的对象:
let result = [
{
person: {
name: "Stark",
age: 21
},
friends: []
},
{
person: {
name: "Jarvis",
age: 20
},
friends: []
}
];

从直觉上我们很容易写出这样的代码:

1
2
3
4
5
6
7
8
9
10
11
12
let result = [];

//有时甚至是普通的for循环
foo.forEach(function(person){
if(person.age > 16){
let newItem = {
person: person,
friends: [];
};
result.push(newItem);
}
})

使用函数式的写法,可以优雅得多

1
2
3
4
5
6
let result = foo
.filter(person => person.age > 16)
.map(person => ({
person: person,
friends: []
}));

数组求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let foo = [1, 2, 3, 4, 5];

//不优雅
function sum(arr) {
let x = 0;
for (let i = 0; i < arr.length; i++) {
x += arr[i];
}
return x;
}
sum(foo); // => 15

//优雅
foo.reduce((a, b) => a + b); // => 15

compose 与函数组合

以下代码称为组合 compose

1
2
3
4
5
const compose = function(f, g) {
return function(x) {
return f(g(x));
};
};

由于函数式编程大行其道,所以现在将会在 JavaScript 代码看到大量的箭头()=>()=>()=>的代码。

ES6 版本 compose

1
const compose = (f, g) => x => f(g(x));

在 compose 的定义中, g 将先于 f 执行,因此就创建了一个从右到左的数据 流。这样做的可读性远远高于嵌套一大堆的函数调用.

我们选择一些函数,让它们结合,生成一个崭新的函数。

reverse 反转列表, head 取列表中的第一个元素;

1
2
3
4
5
6
const head = arr => arr[0];
const reverse = arr => [].concat(arr).reverse();

const last = compose(head, reverse);
last(["jumpkick", "roundhouse", "uppercut"]);
// "uppercut"

但是我们这个这个compose不够完善,只能处理两个函数参数。redux源码有个很完备的compose函数,我们借鉴一下。

1
2
3
4
5
6
7
8
9
10
11
function compose(...funcs){
if (funcs.length === 0){
return arg => arg
}

if (funcs.length === 1 ){
return funcs[0]
}

return funcs.reduce((a,b)=>(...args) => a(b(...args)))
}

有了这个函数,我们可以随意组合无数个函数。现在我们增加需求,组合出一个lastAndUpper函数,内容是先reverse 反转列表, head 取列表中的第一个元素, 最后toUpperCase大写。

1
2
3
4
5
6
7
8
9
10
11
12
const head = arr => arr[0];
const reverse = arr => [].concat(arr).reverse();
const toUpperCase = str => str.toUpperCase();

const last = compose(head, reverse);

const lastAndUpper = compose(toUpperCase, head, reverse,);

console.log(last(["jumpkick", "roundhouse", "uppercut"]));
// "uppercut"
console.log(lastAndUpper(["jumpkick", "roundhouse", "uppercut"]))
// "UPPERCUT"


过去的一年, JavaScript 在持续变化着,其使用范围也越来越广。接下来,我将针对 JavaScript 的使用,列出 9 条 建议,以帮助你写出更加整洁高效的代码,成为更好的开发者。

async/await

JavaScript 极速发展的今天,回调地狱所产生的问题已不复存在。实际开发过程中我们应当尽量避免使用回调函数,除非为了遵守代码库规则或是维护性能。而解决回调地狱的一个常用方法为 Promise,但在代码量较多时使用会适得其反。于是提出了 async / await,使代码结构更加清晰明了,便于阅读和维护。一般而言,可以 await 任何 Promise 以防止正使用的库的返回值为 Promise ,也就是说 async/awaitPromise 的语法糖,而且使用方法也十分简单:在函数前加 async。下面是一个简单的例子:

1
2
3
4
5
6
7
async function getData() {
const result = await axios.get('https://dube.io/service/ping')
const data = result.data
console.log('data', data)
return data
}
getData()

await 只能使用在 async 函数中,不能用于全局作用域。

async/await 是 ES2017 中引入的,使用时请进行转换。

异步控制流

当我们进行异步调用并获得返回值时,通常期望直接获取多个数据集,并且分别操作每个数据集。因此有了以下方式:

for…of

假设页面上要展示 Pokemon 数据,可以通过 axios 获取它们的详细信息,我们所期望的是在得到返回值时立即更新页面中的所有数据,而不是等所有调用完成后才进行更新。

我们可以使用 for...of 解决上述问题。 首先循环遍历数组,并在每个循环内执行异步代码,当所有调用都成功时跳出循环。需要注意的是,这种方法虽然会对性能产生一些影响,但也不乏是一个很好的方法。

以下是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import axios from'axios'
let myData = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]

async function fetchData(dataSet) {
for (entry of dataSet) {
const result = await axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
const newData = result.data
updateData(newData)
console.log(myData)
}
}
function updateData(newData) {
myData = myData.map(el => {
if (el.id === newData.id) return newData
return el
})
}
fetchData(myData)

可以将这些例子复制粘贴到编辑器中调试运行。

译者注:除了循环本身带来的性能问题之外,在使用 async/await 处理异步请求时也会对性能造成影响:如果使用过多 await 语句,而且候这些语句并不需要依赖于之前的语句,则会产生 async/await 地狱,影响性能。

Promise.all

如果想要并行获取所有的 Pokemon,我们可以使用 Promise.all 方法来 await 所有 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import axios from'axios'
let myData = [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }]
async function fetchData(dataSet) {
const pokemonPromises = dataSet.map(entry => {
return axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
})
const results = awaitPromise.all(pokemonPromises)
results.forEach(result => {
updateData(result.data)
})
console.log(myData)
}
function updateData(newData) {
myData = myData.map(el => {
if (el.id === newData.id) return newData
return el
})
}
fetchData(myData)

for...ofPromise.all 都是 ES6+ 引入的,使用时请进行转换。

解构赋值 & 默认值

回到上个例子:

1
2
const result = axios.get(`https://ironhack-pokeapi.herokuapp.com/pokemon/${entry.id}`)
const data = result.data

现在有一种更简单的方法来实现它:通过解构赋值的方式从对象或数组中获取一个或多个值:

1
const { data } = await axios.get(...)

也可对变量重命名:

1
const { data: newData } = await axios.get(...)

另一种方法是在解构赋值时指定默认值,这样做可以确保代码不会出现 undefined,也避免手动检查变量的麻烦。

1
2
const { id = 5 } = {}
console.log(id) // 5

这些方法也可以用于函数参数,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function calculate({ operands = [1, 2], type = 'addition' } = {}) {
return operands.reduce((acc, val) => {
switch (type) {
case'addition':
return acc + val
case'subtraction':
return acc - val
case'multiplication':
return acc * val
case'division':
return acc / val
}
}, ['addition', 'subtraction'].includes(type) ? 0 : 1)
}
console.log(calculate()) // 3
console.log(calculate({ type: 'division' })) // 0.5
console.log(calculate({ operands: [2, 3, 4], type: 'multiplication' })) // 24

ES6 引入了解构赋值和默认值,使用时请进行转换。

真值和虚值

当我们使用默认值时,通常要对现有值进行一系列判断,这种方法使代码变得异常繁琐,而现在我们可以真值(Truthy)和虚值(Falsy)的方式来改进它,不仅可以节省代码量,还使人更加信服。

以下是之前的做法:

1
2
3
4
5
6
7
8
9
10
11
if (myBool === true) {
console.log(...)
}
// OR
if (myString.length > 0) {
console.log(...)
}
// OR
if (isNaN(myNumber)) {
console.log(...)
}

简化后:

1
2
3
4
5
6
7
8
9
10
11
if (myBool) {
console.log(...)
}
// OR
if (myString) {
console.log(...)
}
// OR
if (!myNumber) {
console.log(...)
}

以下为 FalsyTruthy 的概念:

False

  • 长度为0的字符串
  • 数字 0
  • false
  • undefined
  • null
  • NaN

True

  • 空数组
  • 空对象
  • 其他

使用真值和虚值时没有确切的比较方式,这类似于我们进行比较时常使用双等号 == 而不是三等号 ===。一般而言,这两者的判定方式相同,但在某些情况下也会遇到一些错误,对我来说主要为数字 0

逻辑运算符和三元运算符

逻辑运算符和三元运算符主要用于精简代码,有助于保持代码整洁度,但当他们形成运算链时会显得杂乱。

逻辑运算符

逻辑运算符:和(&&)、或(||),一般用于比较两个表达式,返回值为: truefalse 或着它的匹配值。如下例:

1
2
3
4
5
6
7
8
console.log(true && true) // true
console.log(false && true) // false
console.log(true && false) // false
console.log(false && false) // false
console.log(true || true) // true
console.log(true || false) // true
console.log(false || true) // true
console.log(false || false) // false

我们可以将逻辑运算符与真值和虚值的相关知识结合起来。

如果有表达式 AB,针对两种逻辑运算符,有以下规则:

  • A && B : 当 Afalse 时则直接返回 A 的值 ;否则返回 B 的值。
  • A || B : 当 Atrue 时则直接返回 A 的值 ;否则返回 B 的值。

译者注:上述规则为逻辑运算中的短路现象。

1
2
3
4
5
6
console.log(0 && { a: 1 }) // 0
console.log(false && 'a') // false
console.log('2' && 5) // 5
console.log([] || false) // []
console.log(NaN || null) // null
console.log(true || 'a') // true

三元运算符

三元运算符与逻辑运算符非常相似,但有由三个部分组成:

  1. 条件表达式:其结果为真值或是虚值
  2. 返回值 1:条件表达式为真值时,返回该值
  3. 返回值 2:条件表达式为虚值时,返回该值

例如:

1
2
3
4
const lang = 'German'
console.log(lang === 'German' ? 'Hallo' : 'Hello') // Hallo
console.log(lang ? 'Ja' : 'Yes') // Ja
console.log(lang === 'French' ? 'Bon soir' : 'Good evening') // Good eveing

自判断链接

当访问某个嵌套对象的属性时,由于不能确定目标对象或者属性性是否存在,而需要进行一系列判断:

1
2
3
let data
if (myObj && myObj.firstProp && myObj.firstProp.secondProp && myObj.firstProp.secondProp.actualData)
data = myObj.firstProp.secondProp.actualData

显而易见,代码变得非常臃肿难看。而自判断链接(optional chaining)的提出,正好可以满足对嵌套属性的校验需求,并使代码更加清晰整洁。如下例:

1
const data = myObj?.firstProp?.secondProp?.actualData

1
2
3
4
5
6
7
8
function safeGet(o, path){

return path.split('.').reduce((o={},b)={ //用到参数默认值

return o[b]
},o)

}

译者注:自判断链接: 检查一个对象上面是否存在某属性。

出现原因:调用某 Object属性链中的某个属性时,如果该属性不存在,会导致 Cannot read property xxx of undefined 错误。于是自判断链接 ?. 出现。

使用方式:obj?.a?.b?.c。依次对代码中的属性进行判断,如果为 null 或者 undefined 时,结束调用,返回 undefined

目前,自判断链接还未纳入官方规范中,只处于第一阶段的实验特性。您需要在 babelrc 中添加 @ babel / plugin-proposal-optional-chaining 后方可使用它。

类属性 & 绑定

JavaScript 中经常会用到绑定(bind)。ES6 规范中箭头函数的引入,使 JavaScript 开发人员有了一种将函数自动绑定到执行上下文中的常用方法,同时这种方法非常重要。

由于 JavaScript 中的类方法有特定的调用方式,因此当我们首次声明一个类时不能使用箭头函数,因此需要在其他位置进行函数绑定,比如在构造函数中(以 React.js 为例)。工作当中我总是先定义类方法再对其进行绑定,这种方法非常繁琐且容易出错。但如果使用 class 语法,我们可以通过箭头函数自动绑定它。以下是绑定 _increaseCount 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
classCounterextendsReact.Component{
constructor(props) {
super(props)
this.state = { count: 0 }
}
render() {
return (
<div><h1>{this.state.count}</h1><buttononClick={this._increaseCount}>Increase Count</button></div>
)
}
_increaseCount = () => {
this.setState({ count: this.state.count + 1 })
}
}

目前,类属性还未纳入官方规范中,只处于第三阶段的实验特性。您需要在 babelrc 中添加 @ babel / plugin-proposal-class-properties 后方可使用。


JavaScript开发技巧

String Skill

时间对比:时间个位数形式需补0

1
2
3
4
const time1 = "2019-03-31 21:00:00";
const time2 = "2019-05-01 09:00:00";
const overtime = time1 > time2;
// overtime => false

格式化金钱:带小数无效

1
2
3
const thousand = num => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const money = thousand(19941112);
// money => "19,941,112"

生成随机ID

1
2
3
4

const randomId = len =>Math.random().toString(36).substr(3, len);
const id = randomId(10);
// id => "jg7zpgiqva"

生成随机HEX色值

1
2
3
const randomColor = () =>"#" + Math.floor(Math.random() * 0xffffff).toString(16).padEnd(6, "0");
const color = randomColor();
// color => "#f03665"

生成星级评分

1
2
3
const startScore = rate =>"★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);
const start = startScore(3);
// start => "★★★"

操作URL查询参数

1
2
3
const params = new URLSearchParams(location.search); // location.search = "?name=yajun&sex=female"
params.has("yajun"); // true
params.get("sex"); // "female"

Number Skill

取整:代替正数的Math.floor(),代替负数的Math.ceil()

1
2
3
4
const num1 = ~~ 1.69;
const num2 = 1.69 | 0;
const num3 = 1.69 >> 0;
// num1 num2 num3 => 1 1 1

补零

1
2
3
const fillZero = (num, len) => num.toString().padStart(len, "0");
const num = fillZero(169, 5);
// num => "00169"

转数值:只对null、""、false、数值字符串有效

1
2
3
4
5
const num1 = +null;
const num2 = +"";
const num3 = +false;
const num4 = +"169";
// num1 num2 num3 num4 => 0 0 0 169

精确小数

1
2
3
const round = (num, decimal) =>Math.round(num * 10 ** decimal) / 10 ** decimal;
const num = round(1.69, 1);
// num => 1.7

判断奇偶

1
2
3
const num = 0;
const odd = !!(num & 1);
// odd => false

取最小最大值

1
2
3
4
const arr = [0, 1, 2];
const min = Math.min(...arr);
const max = Math.max(...arr);
// min max => 0 2

Boolean Skill

短路运算符

1
2
3
const a = d && 1; // 满足条件赋值:取假运算,从左到右依次判断,遇到假值返回假值,后面不再执行,否则返回最后一个真值
const b = d || 1; // 默认赋值:取真运算,从左到右依次判断,遇到真值返回真值,后面不再执行,否则返回最后一个假值
const c = !d; // 取假赋值:单个表达式转换为true则返回false,否则返回true

是否为空数组

1
2
3
const arr = [];
const flag = Array.isArray(arr) && !arr.length;
// flag => true

是否为空对象

1
2
3
const obj = {};
const flag = Object.prototype.toString.call(obj) && !Object.keys(obj).length;
// flag => true

满足条件时执行

1
2
3
4
5
6
const flagA = true; // 条件A
const flagB = false; // 条件B
(flagA || flagB) && Func(); // 满足A或B时执行
(flagA || !flagB) && Func(); // 满足A或不满足B时执行
flagA && flagB && Func(); // 同时满足A和B时执行
flagA && !flagB && Func(); // 满足A且不满足B时执行

为非假值时执行

1
2
const flag = false; // undefined、null、""、0、false、NaN
!flag && Func();

数组不为空时执行

1
2
const arr = [0, 1, 2];
arr.length && Func();

对象不为空时执行

1
2
const obj = { a: 0, b: 1, c: 2 };
Object.keys(obj).length && Func();

函数退出代替条件分支退出

1
2
3
4
5
6
7
if (flag) {
Func();
return false;
}
// 换成if (flag) {
return Func();
}

Array Skill

克隆数组

1
2
3
const _arr = [0, 1, 2];
const arr = [..._arr];
// arr => [0, 1, 2]

合并数组

1
2
3
4
const arr1 = [0, 1, 2];
const arr2 = [3, 4, 5];
const arr = [...arr1, ...arr2];
// arr => [0, 1, 2, 3, 4, 5];

去重数组

1
2
const arr = [...new Set([0, 1, 1, null, null])];
// arr => [0, 1, null]

混淆数组

1
2
const arr = [0, 1, 2, 3, 4, 5].slice().sort(() =>Math.random() - .5);
// arr => [3, 4, 0, 5, 1, 2]

交换赋值

1
2
3
4
let a = 0;
let b = 1;
[a, b] = [b, a];
// a b => 1 0

过滤空值:undefined、null、””、0、false、NaN

1
2
const arr = [undefined, null, "", 0, false, NaN, 1, 2].filter(Boolean);
// arr => [1, 2]

异步累计

1
2
3
4
5
6
7
8
9
asyncfunctionFunc(deps) {
return deps.reduce(async(t, v) => {
const dep = await t;
const version = await Todo(v);
dep[v] = version;
return dep;
}, Promise.resolve({}));
}
const result = await Func(); // 需在async包围下使用

首部插入元素

1
2
3
4
5
let arr = [1, 2]; // 以下方法任选一种
arr.unshift(0);
arr = [0].concat(arr);
arr = [0, ...arr];
// arr => [0, 1, 2]

尾部插入元素

1
2
3
4
5
6
let arr = [0, 1]; // 以下方法任选一种
arr.push(2);
arr.concat(2);
arr[arr.length] = 2;
arr = [...arr, 2];
// arr => [0, 1, 2]

统计元素个数

1
2
3
4
5
6
const arr = [0, 1, 1, 2, 2, 2];
const count = arr.reduce((t, c) => {
t[c] = t[c] ? ++ t[c] : 1;
return t;
}, {});
// count => { 0: 1, 1: 2, 2: 3 }

创建指定长度数组

1
2
const arr = [...new Array(3).keys()];
// arr => [0, 1, 2]

创建指定长度且值相等的数组

1
2
const arr = [...new Array(3).keys()].fill(0);
// arr => [0, 0, 0]

reduce代替map和filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const _arr = [0, 1, 2];

// map
const arr = _arr.map(v => v * 2);
const arr = _arr.reduce((t, c) => {
t.push(c * 2);
return t;
}, []);
// arr => [0, 2, 4]

// filter
const arr = _arr.filter(v => v > 0);
const arr = _arr.reduce((t, c) => {
c > 0 && t.push(c);
return t;
}, []);
// arr => [1, 2]

// map和filter
const arr = _arr.map(v => v * 2).filter(v => v > 2);
const arr = _arr.reduce((t, c) => {
c = c * 2;
c > 2 && t.push(c);
return t;
}, []);
// arr => [4]

Object Skill

克隆对象

1
2
3
const _obj = { a: 0, b: 1, c: 2 }; // 以下方法任选一种const obj = { ..._obj };
const obj = JSON.parse(JSON.stringify(_obj));
// obj => { a: 0, b: 1, c: 2 }

合并对象

1
2
3
4
const obj1 = { a: 0, b: 1, c: 2 };
const obj2 = { c: 3, d: 4, e: 5 };
const obj = { ...obj1, ...obj2 };
// obj => { a: 0, b: 1, c: 3, d: 4, e: 5 }

对象字面量:获取环境变量时必用此方法,用它一直爽,一直用它一直爽

1
2
3
4
5
6
7
const env = "prod";
const link = {
dev: "Development Address",
test: "Testing Address",
prod: "Production Address"
}[env];
// env => "Production Address"

创建纯空对象

1
2
3
const obj = Object.create(null);
Object.prototype.a = 0;
// obj => {}

解构嵌套属性

1
2
3
const obj = { a: 0, b: 1, c: { d: 2, e: 3 } };
const { c: { d, e } } = obj;
// d e => 2 3

解构对象别名

1
2
3
const obj = { a: 0, b: 1, c: 2 };
const { a, b: d, c: e } = obj;
// a d e => 0 1 2

删除无用属性

1
2
3
const obj = { a: 0, b: 1, c: 2 }; // 只想拿b和c
const { a, ...rest } = obj;
// rest => { b: 1, c: 2 }

Function Skill

函数自执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Func = function() {}(); // 常用

(function() {})(); // 常用
(function() {}()); // 常用
[function() {}()];

+ function() {}();
- function() {}();
~ function() {}();
! function() {}();

newfunction() {};
newfunction() {}();
voidfunction() {}();
typeoffunction() {}();
deletefunction() {}();

1, function() {}();
1 ^ function() {}();
1 > function() {}();

隐式返回值:只能用于单语句返回值箭头函数,如果返回值是对象必须使用()包住

1
2
3
4
const Func = function(name) {
return"I Love " + name;
};
// 换成const Func = name =>"I Love " + name;

一次性函数:适用于运行一些只需执行一次的初始化代码

1
2
3
4
5
6
functionFunc() {
console.log("x");
Func = function() {
console.log("y");
}
}

惰性载入函数:函数内判断分支较多较复杂时可大大节约资源开销

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
functionFunc() {
if (a === b) {
console.log("x");
} else {
console.log("y");
}
}
// 换成functionFunc() {
if (a === b) {
Func = function() {
console.log("x");
}
} else {
Func = function() {
console.log("y");
}
}
return Func();
}

检测非空参数

1
2
3
4
5
6
7
8
functionIsRequired() {
throw new Error("param is required");
}
functionFunc(name = IsRequired()) {
console.log("I Love " + name);
}
Func(); // "param is required"
Func("雅君妹纸"); // "I Love 雅君妹纸"

字符串创建函数

1
const Func = newFunction("name", "console.log(\"I Love \" + name)");

优雅处理错误信息

1
2
3
4
5
try {
Func();
} catch (e) {
location.href = "https://stackoverflow.com/search?q=[js]+" + e.message;
}

优雅处理Async/Await参数

1
2
3
4
functionAsyncTo(promise) {
return promise.then(data => [null, data]).catch(err => [err]);
}
const [err, res] = await AsyncTo(Func());

优雅处理多个函数返回值

1
2
3
4
5
6
7
asyncfunctiongetAll() {
return await Promise.all([
fetch("/user"),
fetch("/comment")
]);
}
const [user, comment] = getAll();

DOM Skill

显示全部DOM边框:调试页面元素边界时使用

1
2
3
[].forEach.call($$("*"), dom => {
dom.style.outline = "1px solid #" + (~~(Math.random() * (1 << 24))).toString(16);
});

自适应页面:页面基于一张设计图但需做多款机型自适应,元素尺寸使用rem进行设置

functionAutoResponse(width = 750) {
    const target = document.documentElement;
    target.clientWidth >= 600
        ? (target.style.fontSize = "80px")
        : (target.style.fontSize = target.clientWidth / width * 100 + "px");
}