查看原文
其他

如何让你的代码更优雅

前端大全 2021-07-15

The following article is from 前端万有引力 Author Symbol卢

前言

你有没有骂过“前任”的代码?你的代码有没有被吐槽过?没有注释,命名随意,没有封装,结构混乱,一个页面几千行。。。。

如何写出优雅的代码,让自己不被吐槽,让人羡慕,接下来咱就一起聊聊如何写出优雅的代码,如果大家有更好的写法和经验,欢迎在评论区留言,一起学习

什么样的代码是优雅的代码

咱先聊聊,什么样的代码是优雅的代码,在我看来,优雅的代码包括以下方面,首先就是代码的简洁,能够使用一行代码解决的,就不要写两行及以上,其次就是便于阅读,代码不仅仅是用来让机器(服务器,浏览器)来运行的,而是需要用人来编写,维护的,可能是自己,也可能是现在的同事,未来的同事,这个就是代码的可维护性,还有一个就是我们很关注的就是性能,就比如我们经常写的循环,在满足业务功能条件的时候,能少一次循环就少一次循环,还有就是不再使用的引用类型的数据,要及时手动的进行内存的释放,(虽然有垃圾回收机制),但是早一点释放内存是不是更好呢

注释

注释,写代码少不了注释,尤其是优雅的代码,更少不了注释;注释是给人看的,可能是自己,也可能是同事以及未来的同事看的,(这几句话说了好多遍了),那么我们为什么要写注释,以及该怎么样去写注释呢?,接下来咱就先来聊聊注释

如何使用注释?(在哪些地方需要添加注释)

通用的规则是在代码不清晰时需要添加注释,个人觉得包含以下几个方面:

  1. 难于理解的代码添加注释(逻辑比较复杂,嵌套层级比较多,传递参数不明显等)

2. 可能被误认为错误的代码(没有按照正常套路出牌的代码   

3. 数据的状态(就拿订单状态来说,订单可能有多钟状态,此时在写这段的叶落逻辑的时候,就把不同的状态表示的含义写上去,以后的维护,或者是有新人接收这个项目的时候能够更快的上手)   

4. 其他注释(文档注释,变量注释,函数注释)

1.文档注释:

简单描述当前js文件作用,是页面js逻辑处理,还是公共的方法等

如果是vue项目则是描述出当前vue文件的作用,

2.变量注释:

注明变量是用来做什么的,来进行语义化的命名,需要注释的则加上注释

// difference
let uName = 'Symbol卢'// 虽然用了驼峰,但是简写严重。无法直观看出变量的含义
// grace
let userName = 'Symbol卢'// 语义化命名,不能过于缩写

3.函数的注释

参数注释包括参数的数据类型,参数的命名语义化,返回参数注释包括了默认值,已经返回参数的数据类型

例如:

/**
 * @description: 数据类型的检测的第二种方式
 * @param {any} data 要检测数据类型的变量
 * @return {string} type 返回具体的类型名称【小写】
 */

export const $typeOf = (data) => {
    return Object.prototype.toString.call(data).replace(/\[object (\w+)\]/'$1').toLowerCase()
}

可以在vscode的插件市场,下载koroFileHeader 进行安装,可以一键生成注释格式

变量

定义变量的准则:变量用小写(声明变量用let),常量用大写(声明常量用const),变量命名语义化,指定变量的数据类型,避免使用太多的全局变量,先声明,后使用

1. let和const 取代 var

定义常量

// difference
var myLove = 'My love is Code';//没有使用const进行命名,且常量名称没有全部大写

// grace
const MY_LOVE = 'My love is Code';

使用 var 关键字 定义的 常量 是可以变化的,也就是,我们定义一个常量之后,还可以给它赋值来改变这个常量,然后按照我们在程序当中的规范,在声明一个常量之后,这个常量在整个程序也就全局中是不能够再被改变的,所以声明一个常量,要使用关键字 const 来声明常量,并且通常常量的名称都是大写的,并且语义化,让人看到这个常量的时候,就知道这个常量是干什么的

刚好聊到了常量声明中的字符串,那咱再看看,在使用字符串常量的时候,要注意哪些规范,上代码

//要注意。静态字符串一律使用单引号或反引号(模板字符串),不使用双引号,动态字符串使用反引号,拒绝使用 + 进行字符串的拼接
// difference
const name = "Symbol卢";
const say = 'My name is' + name + 'and I love FE';

// grace
const NAME = 'Symbol卢';
const b = `My name is ${ NAME } and I love FE`;

定义变量

定义局部的变量的时候使用let ,定义全局的变量的时候使用var,要记住一个原则就是,变量的声明要只在其声明的代码块中有效

if (true) {
  let x = 'hello';
}
let name = () => {
  // ...  
}
// difference
one.js
window.name = 'Symbol卢';
two.js
window.name = 'Jack李';
three.js
window.name = 'Leo';

在window对象中定义全局的变量,容易污染全局的环境,在平时的工作中,多人协作,很有可能会出现小惊喜,所有要慎重慎重,再慎重

给常量赋值

let lastName = fullName[1]; //如果fullName=[],那么fullName[1]=undefined 很容易就出问题了
let propertyValue = Object.attr; //如果Object={},那么Object.attr=undefined

对于引用数据类型(也包括接口中返回的数据),要做好兜底(没有数据的话,给个默认值)你怎么却确定它里面一定有值??

let lastName = fullName[1] || ''
let propertyValue=Object.attr || 0

(1)要按强类型风格写代码定义变量的时候要指明类型,并且在变量声明之后,不要随意的去更改变量的数据类型,改着改着搞不好就出问题了

// 假设声明三个变量a,b,c
let a,b,c; // difference,定义变量时没有指明类型
let a = "", b = [], c = {}; // good

// difference
let a = ""
...
a = 100// 改变了定义变量时的类型(容易出错问题哦!)


let b = 5;  
...
b = 200// grace 定义了数据类型之后,就不要去随便的更改它

2、避免使用 == 进行逻辑判断(不要给自己的代码埋雷)

大家也都知道,js是一门弱类型的语言,表达式的赋值运算等操作也都会导致数据类型的转换,

== 表示只要值相等即为真,=== 要求不仅值相等,而且也要求类型相同,这句话相信大家也都知道

但是它们之前的运算规则是什么样的呢?咱们下一篇的js中的数据类型的时候再好好的聊聊;

使用== 有时候会达不到预期的结果,埋下隐患,来看看下面的这些情况(你品,你细品)

0  == ''          // true
0  == '0'         // true
'' == 0           // true
'' == '0'         // false
false == '0'        // true
false == 'false'    // false
false == undefined  // false
false == null       // false
null == undefined   // true
true == 1           // true

如果确定了这个变量的数据类型,那么就没必要使用 == 多巧一个等号,给程序多一份保障,又不要钱,怕啥??干就完了;判断调试的时候就使用===,那数据类型不确定咋办,,,,(小声逼逼,数据类型不确定,那就动动发财手,转换一下数据类型呗)

如果变量的数据类型不确定,那咱就手动的转换一下,让它确定

let total = "6";
if(total == 6) {} // difference
if(parseInt(total) === 6){} // grace 手动转换一下数据类型

数组

在对数组进行操作的时候,推荐优先使用ES6+中的新的语法。

// 数组拷贝
// difference
let items=['1','2','3'];
let items2=['4','5','6'];
for (i = 0; i < items.lenght; i++){
    items.push(items2[i]);
}

// grace
const itemsCopy = [...items];



// 扩展运算符简写
// joining arrays 
const odd = [135];
const nums = [2 ,4 , 6].concat(odd);
// cloning arrays 
const arr = [1234]; 
const arr2 = arr.slice()



// joining arrays 
const odd = [135 ]; 
const nums = [2 ,4 , 6, ...odd]; 
console.log(nums); // [ 2, 4, 6, 1, 3, 5 ]
// cloning arrays 
const arr = [1234]; 
const arr2 = [...arr];
  • 使用数组成员对变量赋值时,优先使用解构赋值
const Arr = [1234];

// difference
const first = Arr[0];
const second = Arr[1];

// grace
const [first, second] = Arr; // 有简单的方法。肯定要用简单的方法啦

函数

函数是我们工作当中用到非常多的,但是如何写出优雅的函数呢??接下来咱就来用函数的命名,函数的入参,参数校验,函数的出参等几个方面来一起聊聊如何让你写的函数更加的优雅,来吧 come quick!!

函数的命名

函数的命名,我们首先就是需要语义化 (功能性命名,也就是说,这个函数是为干什么事情而生的),比如:返回布尔值函数应以is/can/has等单词开头,能够让人更直观的了解到这个函数的功能;获取接口中的数据使用get开头进行命名。

let isEmpty = () => {...}
let canCreateDocuments = () => {...}
let hasLicense = () => {...}

// 不用「否定」语法命名函数
//difference
let isNotSupport = () => {};
let canNotUpdate = () => {};

// grace
let isSupport = () => {};
let canUpdate = () => {};

动作函数要以动词开头

let sendEmailToUser = (user) => {
    ....
}
let geUserInfo = (user) => {
    ....
}
let setUserInfo = (user) => {
    ....
}

优先使用箭头函数

箭头函数相比于传统的函数(function)而言,箭头函数更简洁,并且也绑定好 了this(箭头函数不会去改变this的指向)。

let arr [1819202122,23,24,25]
// commonly
function findAge (arr, age) {
    return arr.filter(function (num) {
        return num === age
    })
}
// grace 是不是看着更简介优雅了
let findAge = (arr, age)=> arr.filter(num => num === age)

函数的入参

函数的入参,是能够让使用者,在调用这个函数的时候,能够更加的清晰明了的把这个函数所需要的参数传递给函数,不容易出现,参数传递错误(参数的顺序颠倒等)一些低级,而又不好查找的问题,

// difference
function getImages(api, true, false); // true和false啥意思,没有个注释的话,看上去就是一脸懵逼
// grace
function getImages({
    imageApi: api,
    includePageBackground: true, // 一目了然,知道这些true和false是啥意思
    compress: false,
})

接收参数

如果函数的的参数是对象,也要优先使用解构赋值,上代码

// 假设现在的场景是获取用户的信息上的现用名,和曾用名
// difference
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;
}

// commonly
function getFullName(obj) {
  const { firstName, lastName } = obj;
}

// grace
function getFullName({ firstName, lastName }) {
}



// grace 给它个默认值
function getFullName({firstName, lastName = '无'}) {
}
 
// 觉得参数的名称太长,咱再来个重命名  解构时重命名简化命名
// grace
function getFullName ({firstName: first, lastName: last}) {
 
}

参数校验

更少的嵌套,不满足条件尽早 return,尽可能的减少嵌套,嵌套的层级越少,函数看着越简洁优雅,

function test(name, sex = 1) {
  if (!name){ // 不满足条件尽早抛出错误
      throw new Error('没有传递name参数');
  }
  // ...
}

函数的出参

聊完了函数的入参,咱在聊聊函数的出参,在函数的出参中,首先就是要保证函数出参的数据类型是固定的,如果函数返回多个值,优先推荐优先使用对象作为返回值,而不是数组。为什么呢???因为对象作为返回值,更便于以后添加返回值,以及更改返回值的顺序,相对于数组更加的灵活,更便于扩展

// 注意,返回参数的数据类型要是固定的,
// difference返回了不同的数据类型
function getResult(cont) {
 if(count < 0) {
  return "";
 } else {
  return count * 10;
 }
}

// grace
function getResult(cont) {
 if(count < 0) {
  return -1;
 } else {
  return count * 10;
 }
}




// 函数返回多个值,推荐使用对象作为函数的返回值
// commonly
function processInput(input) {
  return [left, right, top, bottom];
}

// grace
function processInput(input) {
  return { left, right, top, bottom };
}
const { left, right } = processInput(input);

立即执行函数

立即执行函数也推荐写成箭头函数的形式。首先是因为更简洁,并且也绑定好 this(箭头函数不会去改变this的指向)。

(() => {
  console.log('感觉这篇文章还可以的话,欢迎点赞+关注+转发');
})();

优先使用函数式编程

// difference
for(i = 1; i <= 10; i++) {
   a[i] = a[i] +1;
}
// grace
let b = a.map(item => ++item) //是不是更简洁了

函数中过多的采用if else ..

注:为了方便大家看优化格式,这是随便使用了a来记性演示语法结构(我词穷了)

// commonly
if (a === 1) {
    //...
else if (a ===2) {
   // ...
else if (a === 3) {
    //...
else {
   //...
}
    
// 一般
switch(a) {
    case 1:
        //....
    case 2:
        //....
    case 3:
        //....
    default:
    //....
}


// grace ===》》》 Object
const fruit = {
    1: ['1''11'],
    2: ['2''22'],
    3: ['3''33']
 };
let test = (a) => {
  return fruit[a] || [];
}

// grace ===》》》 Map
const fruit = newMap()
  .set('张三', ['张三丰''张三力'])
  .set('李四', ['李思维''李素丽'])
let test = (a) => {
 return fruit.get(a) || [];
}

// grace ===》》》filter
const fruits = [
    { name'张三'work'js' }, 
    { name'李四'work'php' }, 
    { name'王五'work'java' }, 
];
let test = (a) => {
  return fruits.filter(item => item.name === a);
}


// grace===》》》策略模式
let handler = {
    1() => {
        //....
    },
    2() => {
        //....
    },
    3() => {
        //....
    },
    default() => {
        //....
    }
}
handler[a]( "a") || handler['default']( "'default'")

温馨小提示

一个函数完成一个独立的功能,不要一个函数混杂多个功能,在项目开发中有一条非常重要的原则【单一原则】所谓的单一原则就是,一个函数(文件),只做一件事情,在开发当中,没有那个项目是开发完成之后,就结束了。需要不断的更新,维护,那么单一原则,就是为了方便开发,和维护的,不要让一个函数“又当爹,又当妈”,这样代码的耦合性太高了,不好维护

其他

在if中判断数组长度的判断

// difference
if (arr.length !== 0) {
    //...
}

// grace
if (arr.length) {
    //...
}


// difference
if (arr.length === 0) {
    //...
}

// grace
if (!arr.length) {
    //...
}

逻辑运算符

if (a === 1) {
    b()
}
//可以写成
a === 1 && b()


const arr = [1,2,3];
if(!arr.length){
   b()
 }
//可以写出
arr.length || b()


// &&判断依赖的键是否存在,防止报错'xxx of undfined'
let user = {
    name'Symbol卢',
    age18,
    children: {
        name'小Symbol卢'
    }
}
let childrenName = user.children && user.childre.name

能使用三目运算符(三元运算符)就使用,留着舍不得用,要留着过年用嘛

// difference
const a = '';
let b;
if( a === '' ){
    b = 'no'
else {
    b = 'ok'
}

const a = ''
let b = a ? 'no' : 'ok'// 'ok'

判断元素是否存在,当类型为undefined或null或空的string(' ')或0时,可以直接使用if(a)/if(!a)的形式。当类型为array或object时,就可以使用if(a instanceof Array)或if(a instanceof String)的形式进行判断,这里也送上笔者之前写的一个关于数据类型判断的一个函数

/**
 * @description: 数据类型的检测的第二种方式
 * @param {any} data 要检测数据类型的变量
 * @return {string} type 返回具体的类型名称【小写】
 */

export const isTypeOf = (data) => {
    return Object.prototype.toString.call(data).replace(/\[object (\w+)\]/'$1').toLowerCase()
}

对于for循环优先使用命令式编程

// difference
let result=[]
let numberArr=[1,2,3,4,5,6]
for(i = 1; i <= 10; i++) { 
    if(numberArr[i]>4){
        result.push(numberArr[i])
    }
}

优先的使用命令式编程,例如:find、some、every、map、filter

使用 Array.includes 来处理多重 || 条件

// difference
if (a === 1 || a === 2 || a === 3 || a === 4) {
    //...
}
// grace
let arr = [1234]
if (arr.includes(a)) {
    //...
}

使用 Array.every 和 Array.some 来处理全部/部分满足条件

// grace
const users = [
    { name'张三'sex:1 },
    { name'李四'sex:2 },
    { name'王五'sex:1 }
  ];
function test() {
  // 条件:(简短形式)所有的用户都必须是女
  const isAllGirl = users.every(item => item.sex === 1);
  
  // 条件:至少一个用户是男的
  const isAnyMan = users.some(item => item.sex === 2);

}

使用正则表达式

const imgType ='jpg'
if(imgType === 'jpg' || imgType === 'png' || imgType === 'gif'){
    console.log('is image')
}

// 使用match匹配正则表达式
const imgType='jpg'
if(imgType.match(/.*?(gif|png|jpg)/gi)){
    console.log('is image')
}

连接字符串

let name = 'Symbol'
let message = 'Hello,I\'m' + name + 'take care '// 采用传统加号,看着很冗余,且容易出错
// 艾玛,模板字符香,真想
let message = `Hello,I'm ${name} take care `

总结

在自己完成某一个功能之后,问问自己,除了自己当前的写法,还有没有其他更简洁的写法呢???

项目推荐 vue-pure-admin

简介:vue-pure-admin是一个免费开源的中后台模板,使用了最新的 vue3 , vite2 , TypeScript ,Element-Plus 等主流技术开发,开箱即用的中后台前端解决方案 项目预览地址:

http://yiming_chang.gitee.io/manages "vue-pure-admin"

GitHub地址:

https://github.com/xiaoxian521/vue-pure-admin "vue-pure-admin"

- EOF -

推荐阅读  点击标题可跳转

1、再见,整洁代码

2、缩减 SCSS 50%样式代码的 14 条实战经验

3、“克隆战争”来了?100 多个知名网站克隆版的开源代码


觉得本文对你有帮助?请分享给更多人

推荐关注「前端大全」,提升前端技能

点赞和在看就是最大的支持❤️

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存