代码之美JS篇
前言
本篇文章讲述如何编写高效美丽的JavaScript代码,让你在开发中有更多的选择而不是停留在基础编码环节。
纯函数
纯函数指的是在给定相同输入的情况下始终返回相同输出的函数。除了他提供的输入以外,他不依赖于任何外部变量,也不更改任何外部变量。拥有纯函数使得测试更加容易,因为可以随时模拟并测试输入的值,如下:
1 | function plusAbc(a, b, c) { // 这个函数的输出将变化无常,因为api返回的值一旦改变,同样输入函数的a,b,c的值,但函数返回的结果却不一定相同。 |
虽然上面的代码看起来很合理,但是该函数本身依赖一个外部变量,所以如果这个变量他被修改,就会有不同的输出,在我们排查错误的时候就比较不方便。如果写了纯函数,确保他没有引入和修改外部的变量,那么就能降低错误的发生。下面是经过改造后的代码
1 | function plusAbc(a, b, c) { // 同样输入函数的a,b,c的值,但函数返回的结果永远相同。 |
参数传递与解析
使用函数的时候,如果我们需要大量的传入参数,那么这会在调用的时候变成一件非常可怕的事情1
2
3
4
5const createButton = (title, color, disabled, padding, margin, border, shadow) => {
console.log(title, color, disabled, padding, margin, border, shadow)
}
//调用
createButton('Sudongyu er', undefined /* optional color */, true ,'2px....', undefined /* optional margin*/);
使用对象的形式来传入数据也许会变得更容易观看1
2
3
4
5
6
7
8
9const createButton = ({title, color, disabled, padding, margin, border, shadow}) => {
console.log(title, color, disabled, padding, margin, border, shadow)
}
createButton({
title: 'Sudongyu er',
disabled: true,
shadow: '2px....'
});
不知道大家有没有在用异步请求的时候,写过下面的代码1
2
3
4
5
6
7
8
9axios.get('/api/xxx',params:{
mydata
},(res)=>{
let res = res.data;
if(res.code === 200){
//doSomething
res.data.records...
}
})
使用数据解构的方式让代码更加清晰1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17axios.get('/api/xxx',
params:{
mydata
},({
data:{
code,
msg,
foodData:{
records
}
}
})=>{
if(code === 200){
//doSomething...
}
}
)
对象数组解构
也许大家都知道这样的解构1
2
3
4
5
6
7const user = {
name: 'Sudongyu',
email: 'hi@xxx',
designation: 'Software Architect',
loves: 'The Code'
}
const {name, email, loves} = user;
另外一个例子,能显示很多问题1
2
3
4
5
6
7
8
9
10
11const getDetails = () => {
return ['xxx', 'sudongyu', 'Some Street', 'Some City', 'Some Zip', 'Some Country'];
}
const details = getDetails();
const uName = details[0];
const uEmail = details[1];
const uAddress = `${details[2]}, ${details[3]}, ${details[4]}, ${details[5]}`;
const uFirstName = uName.split(' ')[0];
const uLastName = uName.split(' ')[1];
非常的恶心,下面来看解构代码1
2
3
4
5
6
7
8
9
10
11
12
13const getDetails = () => {
return ['xxx', 'sudongyu', 'Some Street', 'Some City', 'Some Zip', 'Some Country'];
}
const [uName, uEmail, ...uAddressArr] = getDetails();
const uAddress = uAddressArr.join(', ');
const [uFirstName, uLastName] = uName.split('');
console.log({
uFirstName,
uLastName,
uEmail,
uAddress
});
我们可以通过上面的代码了解到对于一条数据的解构时机
- 需要直接获取就能使用的数据,直接解构
- 需要获取之后通过相同功能加工的数据,用剩余参数解构出来加工
- split之后的数据,建议直接用数组解构
总之就是减少数组下标的访问
避免硬编码
看例子1
2
3
4setInterval(() => {
// do something
}, 86400000);
// WHAT IS THIS 86400000 ??? 🤔
看代码的人可能不知道这个数字代表什么以及他是如何计算的,以及他背后的业务逻辑是什么,我们可以创建一个常量来代替它。1
2
3
4
5
6
7const DAY_IN_MILLISECONDS = 3600 * 24 * 1000; // 86400000
setInterval(() => {
// do something
}, DAY_IN_MILLISECONDS);
// now this makes sense
看另外一个例子1
2
3
4
5
6
7const createUser = (name, designation, type) => {
console.log({name, designation, type});
}
createUser('SudongYu', 'Software Architect', '1');
// WHAT IS this '1'? 🤔
这里这个1很难让人理解代表的含义是什么,即type这是什么用户。因此,我们可以创建一个我们拥有的用户类型的对象映射,而不是在这里对值进行硬编码’1’,如下所示:
1 | const USER_TYPES = { |
避免使用简写变量名
速记变量在需要它们的地方才有意义。就像如果你有像xand这样的位置坐标y,那是可行的。p但是,如果我们在没有上下文的情况下创建像,t之类的变量c,那么真的很难阅读、跟踪和维护这样的代码。例如看这个例子:1
2
3
4
5
6
7
8
9
10
11
12const t = 25;
let users = ['Sudongyuer', 'xxx'];
users = users.map((user) => {
...
return {
...user,
tax: user.salary * t / 100 // WHAT IS `t` again? 🤔
}
})
上面的例子表明,现在开发人员/读者必须一直向上滚动或转到定义来尝试理解这个变量是什么。因此是不干净的代码😠。这也称为对变量进行思维导图,其中只有作者知道它们的含义。因此,我们可以给它一个适当的名称,而不是简写变量名称,如下所示:1
2
3
4
5
6
7
8
9
10
11
12const taxFactor = 25;
let users = ['Sudongyuer', 'xxx'];
users = users.map((user) => {
// some code
return {
...user,
tax: user.salary * taxFactor / 100
}
})
设置默认对象值
在某些情况下,你可能希望你的对象能够提供一些默认值,如果没有就使用传入的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const createButton = ({title, color, disabled, padding}) => {
const button = {};
button.color = color || '#333';
button.disabled = disabled || false;
button.title = title || '';
button.padding = padding || 0;
return button;
}
const buttonConfig = {
title: 'Click me!',
disabled: true
}
const newButton = createButton(buttonConfig);
console.log('newButton', newButton)
修改后1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20const createButton = (config) => {
return {
...{
color: '#dcdcdc',
disabled: false,
title: '',
padding: 0
},
...config
};
}
const buttonConfig = {
title: 'Click me!',
disabled: true
}
const newButton = createButton(buttonConfig);
console.log('newButton', newButton)
使用方法链
如果我们知道类/对象的用户将一起使用多个函数,则方法链接是一种很有用的技术。您可能已经在诸如 moment.js 之类的库中看到了这一点。让我们看一个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Player {
constructor (name, score, position) {
this.position = position;
this.score = score;
this.name = name;
}
setName(name) {
this.name = name;
}
setPosition(position) {
this.position = position;
}
setScore(score) {
this.score = score;
}
}
const player = new Player();
player.setScore(0);
player.setName('Sudongyuer');
player..setPosition([2, 0]);
console.log(player)
你可以看到我们需要为这个实例调用一系列的函数,这看起来有点不太美观,修改后的代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class Player {
constructor (name, score, position) {
this.position = position;
this.score = score;
this.name = name;
}
setName(name) {
this.name = name;
return this; // <-- THIS
}
setPosition(position) {
this.position = position;
return this; // <-- THIS
}
setScore(score) {
this.score = score;
return this; // <-- THIS
}
}
const player = new Player();
player.setScore(0).setName('Sudongyuer').setPosition([2, 0]);
// SUPER COOL 😎
console.log(player)
该方法的核心是每个方法都返回this对象,让他能够链式调用
在回调上使用Promise
回调地狱.jpg
看例子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
33
34
35
36
37 const getSocials = (callback) => {
setTimeout(() => {
callback({socials: {youtube: 'xxx', twitter: 'xxx'}});
}, 1500);
}
const getBooks = (callback) => {
setTimeout(() => {
callback({books: ['React Cookbook']});
}, 1500);
}
const getDesignation = (callback) => {
setTimeout(() => {
callback({designation: 'Software Architect'});
}, 1500);
}
const getUser = (callback) => {
setTimeout(() => {
callback({user: 'Sudongyuer'});
}, 1500);
}
getUser(({user}) => {
console.log('user retrieved', user)
getDesignation(({designation}) => {
console.log('designation retrieved', designation)
getBooks(({books}) => {
console.log('books retrieved', books)
getSocials(({socials}) => {
console.log('socials retrieved', socials)
})
})
})
})
这代码看起来非常不顺眼,如果有大量的异步任务,写起来会更加难看,特别是缩进。为了更好的可读性,我们使用Promise包装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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50const getSocials = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({socials: {youtube: 'xxx', twitter: 'xxx'}});
}, 1500);
})
}
const getBooks = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({books: ['React Cookbook']});
}, 1500);
})
}
const getDesignation = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({designation: 'Software Architect'});
}, 1500);
})
}
const getUser = () => {
return new Promise(resolve => {
setTimeout(() => {
resolve({user: 'Sudongyuer'});
}, 1500);
})
}
getUser()
.then(({user}) => {
console.log('user retrieved', user);
return getDesignation();
})
.then(({designation}) => {
console.log('designation retrieved', designation)
return getBooks();
})
.then(({books}) => {
console.log('books retrieved', books);
return getSocials();
})
.then(({socials}) => {
console.log('socials retrieved', socials)
})
这样的话,很大程度就解决了缩进的问题,而且也比较美观,当然我们还有更具可读性的方法,利用到async和await
1 | const getSocials = () => { |
变量兜底
1 | // 👎 对于求值获取的变量,没有兜底 |