It has been 1162 days since the last update, the content of the article may be outdated.
前言
本篇文章讲述如何编写高效美丽的JavaScript代码,让你在开发中有更多的选择而不是停留在基础编码环节。
纯函数
纯函数指的是在给定相同输入的情况下始终返回相同输出的函数。除了他提供的输入以外,他不依赖于任何外部变量,也不更改任何外部变量。拥有纯函数使得测试更加容易,因为可以随时模拟并测试输入的值,如下:
1 2 3 4
| function plusAbc(a, b, c) { var c = fetch('../api'); return a+b+c; }
|
虽然上面的代码看起来很合理,但是该函数本身依赖一个外部变量,所以如果这个变量他被修改,就会有不同的输出,在我们排查错误的时候就比较不方便。如果写了纯函数,确保他没有引入和修改外部的变量,那么就能降低错误的发生。下面是经过改造后的代码
1 2 3
| function plusAbc(a, b, c) { return a+b+c; }
|
参数传递与解析
使用函数的时候,如果我们需要大量的传入参数,那么这会在调用的时候变成一件非常可怕的事情
1 2 3 4 5
| const createButton = (title, color, disabled, padding, margin, border, shadow) => { console.log(title, color, disabled, padding, margin, border, shadow) }
createButton('Sudongyu er', undefined , true ,'2px....', undefined );
|
使用对象的形式来传入数据也许会变得更容易观看
1 2 3 4 5 6 7 8 9
| const 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 9
| axios.get('/api/xxx',params:{ mydata },(res)=>{ let res = res.data; if(res.code === 200){ res.data.records... } })
|
使用数据解构的方式让代码更加清晰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| axios.get('/api/xxx', params:{ mydata },({ data:{ code, msg, foodData:{ records } } })=>{ if(code === 200){ } } )
|
对象数组解构
也许大家都知道这样的解构
1 2 3 4 5 6 7
| const 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 11
| const 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 13
| const 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 4
| setInterval(() => { }, 86400000);
|
看代码的人可能不知道这个数字代表什么以及他是如何计算的,以及他背后的业务逻辑是什么,我们可以创建一个常量来代替它。
1 2 3 4 5 6 7
| const DAY_IN_MILLISECONDS = 3600 * 24 * 1000;
setInterval(() => { }, DAY_IN_MILLISECONDS);
|
看另外一个例子
1 2 3 4 5 6 7
| const createUser = (name, designation, type) => { console.log({name, designation, type}); }
createUser('SudongYu', 'Software Architect', '1');
|
这里这个1很难让人理解代表的含义是什么,即type这是什么用户。因此,我们可以创建一个我们拥有的用户类型的对象映射,而不是在这里对值进行硬编码’1’,如下所示:
1 2 3 4 5 6 7 8 9 10 11
| const USER_TYPES = { REGULAR_EMPLOYEE: '1' }
const createUser = (name, designation, type) => { console.log({name, designation, type}); }
createUser('Sudongyu', 'Software Architect', USER_TYPES.REGULAR_EMPLOYEE);
|
避免使用简写变量名
速记变量在需要它们的地方才有意义。就像如果你有像xand这样的位置坐标y,那是可行的。p但是,如果我们在没有上下文的情况下创建像,t之类的变量c,那么真的很难阅读、跟踪和维护这样的代码。例如看这个例子:
1 2 3 4 5 6 7 8 9 10 11 12
| const t = 25;
let users = ['Sudongyuer', 'xxx'];
users = users.map((user) => { ... return { ...user, tax: user.salary * t / 100 } })
|
上面的例子表明,现在开发人员/读者必须一直向上滚动或转到定义来尝试理解这个变量是什么。因此是不干净的代码😠。这也称为对变量进行思维导图,其中只有作者知道它们的含义。因此,我们可以给它一个适当的名称,而不是简写变量名称,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12
| const taxFactor = 25;
let users = ['Sudongyuer', 'xxx'];
users = users.map((user) => { return { ...user, tax: user.salary * taxFactor / 100 } })
|
设置默认对象值
在某些情况下,你可能希望你的对象能够提供一些默认值,如果没有就使用传入的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const 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 20
| const 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 23
| class 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 25
| class Player { constructor (name, score, position) { this.position = position; this.score = score; this.name = name; } setName(name) { this.name = name; return this; } setPosition(position) { this.position = position; return this; } setScore(score) { this.score = score; return this; } }
const player = new Player(); player.setScore(0).setName('Sudongyuer').setPosition([2, 0]);
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 50
| const 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 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| const 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); }) }
const performTasks = async () => { const {user} = await getUser(); console.log('user retrieved', user);
const {designation} = await getDesignation(); console.log('designation retrieved', designation);
const {books} = await getBooks(); console.log('books retrieved', books);
const {socials} = await getSocials(); console.log('socials retrieved', socials); }
```
# 明确函数意图 对于返回的是true/false的函数,最好以should/can/is/has开头 ```javascript
let rContent = 'willen';
let firstName = 'jackie';
function showFriendsList() {....}
function shouldShowFriendsList() {...} function isEmpty() {...} function canCreateDocuments() {...} function hasLicense() {...} function sendEmailToUser(user) {.... }
|
变量兜底
1 2 3 4 5 6 7 8
| const { data } = getApiRequest(); data.map((s) => s.id);
const { data = [] } = getApiRequest(); data.map((s) => s?.id);
|
待补充