在编程的世界中,有这样的一个原则,简称二八定律
二八定律:影响程序的80%性能的往往是20%的代码
在js的编写过程中,函数设计就相当于那20%,时刻影响着你的代码,可以说是至关重要。那么对于函数的设计原则,你又了解多少?
1. 功能单一原则
一个函数做一件事-函数功能单一原则能让各功能之间达到很高程度的解耦
举个栗子:
假设需要给客户发短信;
如果是男生,短信开头:某先生;
如果是女生,短信开头:某女士;
某女士 = firstName+lastName+'女士';
某男士 = firstName+lastName+'男士';
//bad
let title = "",
content = '祝愿收到短信的您,幸福、安康!',
customers = [{ sex: 'male', firstName: '李', lastName: '斌', phone: '13245' }, ...];
customers.forEach((item) => {
if (item.sex == 'male') {
title = `${item.firstName}${item.lastName}先生`;
} else {
title = `${item.firstName}${item.lastName}女士`;
}
message.title = title
message.content = content
message.send(item.phone)
})
//good
let title = "",
content = '祝愿收到短信的您,幸福、安康!',
customers = [{ sex: 'male', firstName: '李', lastName: '斌', phone: '13245' }, ...];
customers.forEach((item) => {
title = getMessageTitle(item.sex,item.firstName,item.lastName)
sendMessage(title,content,item.phone)
})
function sendMessage(title, content, phone) {
message.title = title
message.content = content
message.send(phone)
}
function getMessageTitle(sex, firstName, lastName) {
return sex == 'male' ? `${item.firstName}${item.lastName}先生` : `${item.firstName}${item.lastName}女士`;
}
//sendMessage函数抽离到公共的utils文件里,完全可以在其他地方调用;getMessageTitle函数稍作修改也能兼容其他地方的调用;
多了一个发短信函数,一个获取短信标题函数,带来的优点却很明显
- 灵活性,出现了两个可以复用的函数
- 函数职责比较单一,扩展性提高,维护成本降低
- 代码的可读性也大大提升
2.命名规范、合理
js命名应遵循 简洁、语义化 的原则
变量命名
命名方法:小驼峰命名
命名规范:形容词(前缀)+ 所属类型 (函数名前缀为动词,变量名前缀为形容词)
//good:
let dialogTitle = "提示"
let minLength = 1
//bad:
let getTitle = "提示"
let setMinLength = 1
常量命名
命名方法:名词全部大写
命名规范:大写字母加下划线组合命名,下划线来分割单词
const PDF_URL = '122.14.23.56';
const MAX_LENGTH = 8;
函数 & 方法
命名方法:小驼峰命名
命名规范:动词前缀
| 动词 | 含义 |
|---|---|
| can | 判断某个动作是否可执行 |
| has | 判断是否含有某个值 |
| is | 判断是否为某个值 |
| get | 获取某个值 |
| set | 设置某个值 |
| load | 加载某个数据 |
//可写
function canWrite(){
// to do
}
//设置模态框提示文字
function setDialogTitle(){
// to do
}
3.函数参数
函数参数简单的说就是函数的入口
传参数量
函数接受的参数最佳数量->0,1,2(越少越好)
//good
function setMarket(param){
//to do
}
// bad
function setMarket(param1,param2,param3){
// to do
}
//一般传递的参数大于2个,笔者都会选择转对象传,避免参数混乱
默认参数
函数的参数默认值建议使用es6的参数默认值
// es5的参数默认设置
function myFunction(x, y) {
if (y === undefined) {
y = 0;
}
}
// 或者更简单的
function myFunction(x, y) {
y = y || 0;
}
// es6的参数默认设置
function myFunction(x, y = 10) {
// y is 10 if not passed or undefined
return x + y;
}
myFunction(0, 2) // 输出 2
myFunction(5); // 输出 15, y 参数的默认值
4.参数检查 && 返回值
参数检查和返回值是函数编写的标准形态
参数检查
某些特定情况下,不能给参数设定默认值,但是还必须做参数检查
// 节选自loadsh源码中的片段
// array没有设定默认值,参数检查如果是null,函数直接返回空数组;
function chunk(array, size = 1) {
size = Math.max(toInteger(size), 0)
const length = array == null ? 0 : array.length
if (!length || size < 1) {
return []
}
//...
}
默认参数的设置和参数检查原理是一样的,都是参数异常情况的处理,对于函数的健壮性来说是不言而喻的
返回值
对于函数的返回值来讲,分必要性和合理性
- 必要性
属于方法类的函数,只负责处理数据,需要返回值
// 节选自loadsh源码中的片段 filter函数
function filter(array, predicate) {
let index = -1
let resIndex = 0
const length = array == null ? 0 : array.length
const result = []
while (++index < length) {
const value = array[index]
if (predicate(value, index, array)) {
result[resIndex++] = value
}
}
return result
}
属于业务类的函数,负责数据的整合,就不需要返回值,此处省略栗子
- 合理性 函数返回值得合理性和参数检查息息相关。如下代码,如果array是null,直接就返回空数组,如果array不是null,再继续执行。
// 节选自loadsh源码中的片段
function compact(array) {
let resIndex = 0
const result = []
if (array == null) {
return result
}
for (const value of array) {
if (value) {
result[resIndex++] = value
}
}
return result
}
5.避免产生副作用
函数如果不是接受一个参数并返回一个值的话,就可能会产生副作用 简单理解对于函数编写来说,遵循以下三个原则:
- 函数接收参数,不修改参数(入口)
- 函数块内只处理传递的参数,不去修改函数外的变量,如全局变量
- 函数运算后的结果返回给外部(出口) 如果没有遵循以上原则,就可能会产生副作用
// 节选自loadsh源码中的片段
function isElement(value) {
return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value)
}
6.避免代码的重复
重复的代码会让后期维护时非常的被动且尴尬
- 一个方法 N多个地方写了N遍
- 一个方法 N多个地方写了N遍 其中n多个地方做了修改
- 一个方法 N多个地方写了N遍 其中n多个地方做了修改,现在因为需求的变化需要再修改N个地方 ...重构这个代码,瞬间崩溃。。。
7.封装条件语句
多个逻辑判断让代码看着比较乱,每次看都要重新理一遍;
如果把多个逻辑判断封装进一个函数,不仅能达到功能单一原则,也能使代码编写更加优雅且容易阅读
// bad
function setForm(identity,status){
if(identity == 'Admin' && status == 1){
// to do
}
}
//good
function canOperate(identity,status){
let result =false
if(identity == 'Admin' && status ==1){
result = true
}
return result
}
function setForm(canOperate(identity,status)){
// to do
}
多了一个身份判断函数,带来的优点却很明显
- 灵活性,出现了一个可以复用的函数
- 函数职责比较单一,扩展性提高,维护成本降低
- 代码的可读性也大大提升
8.其他
es6语法
- 模板字符串
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`
- 默认参数代替短路表达式
上面已经讲过,这里就不再赘述 - Object.assign设置默认对象
const messageConfig = {
title: '山丘',
content:"停车坐爱枫林晚,霜叶红于二月花"
}
function sendMessage(config) {
config = Object.assign({
title:'hello',
content:'打工人',
time:new Date()
}, config);
}
sendMessage(messageConfig);
- 箭头函数
- async函数
这里就列举几个最常用的,其他的感兴趣的同学请参考阮一峰老师ECMAScript 6入门
函数式编程
在理解上文的基础之上,基本的函数式编程算是有所了解,简单介绍下概念:
"函数式编程"是一种"编程范式"(programming paradigm),也就是如何编写程序的方法论。
函数式编程有如下5个鲜明的特点:
- 函数是“第一等公民”
- 只用"表达式",不用"语句"
- 没有"副作用"
- 不修改状态
- 引用透明
此处介绍的函数式编程的概念及特点节选自阮一峰老师的函数式编程初探,感兴趣的读者可自行查阅。
后续文章笔者会更详细的剖析函数式编程的核心思想及在日常开发中的使用
结语
本问主要总结了个人编码过程中函数编写注意的几点:
- 功能单一
- 命名规范、合理
- 默认参数处理
- 参数检查 & 返回值
- 函数何种情况下会产生副作用
- 删除重复且冗余的代码
- 条件语句的封装
- es6语法的引入