github原作者:https://github.com/alivebao/clean-code-js

翻译:https://segmentfault.com/p/1210000008065582

常用编程简化代码方法:(window方法速查表驱动编程优雅的写if/else)http://www.fly63.com/tag/js

# 简洁的js代码写法

# 1】表驱动编程

如果你的代码有很多 if ... else ...结构,你不知道怎么优化,你就应该使用表驱动编程。

善用map哈希表的数据结构,js对象也就是字典,设置obj={key:value},通过obj[key]取出值,代替if..else..或者switch..case..等逻辑

优化前:

howManyDays(year, month){
    if(month === 1 ||
        month === 3 ||
        month === 5 ||
        month === 7 ||
        month === 8 ||
        month === 10 ||
        month === 12
    ){
        return 31
    }else if(month === 2){
        return isLeapYear(year) ? 29 : 28
    }else{
        return 30
    }
}
复制代码

优化后:

howManyDays(year, month){
    const table = {
        1: 31, 3: 31, 5: 31, 7: 31, 8: 31, 10: 31, 12:31,
        4: 30, 6:30, 9: 30, 11: 30,
        2: isLeapYear(year) ? 29 : 28
    }
    return table[month]
}
复制代码

优化前:

function calculateGrade(score){
    if(score>=90){
        return 'A'
    }else if(score >= 80){
        return 'B'
    }else if(score >= 70){
        return 'C'
    }else if(score >= 60){
        return 'D'
    }else {
        return 'E'
    }
}
复制代码

优化后:

function calculateGrade(score){
    const table = {
        100: 'A', 
        90: 'A',
        80: 'B',
        70: 'C',
        60: 'D',
        others: 'E'
    }
    return table[Math.floor(score/10)*10] || table['others']
}    

# 下面还有一篇文章是讲框架中用到的mvc表驱动编程思想

(通过表驱动编程抽离出重复的代码,减少代码量)

一个复杂的、功能完善的应用程序往往拥有成千上万行的代码量,如果这些代码杂乱无章、冗余重复,势必会对开发和日后的维护升级造成很大的麻烦。因此,为了使代码层次分明、条理清晰,必须让它具备合理的架构,即架构设计模式。MVC就是最著名的设计模式之一,在前端领域也具有广泛的应用。

# 一、何为MVC

MVC模式将一个应用程序分为三个对象,M、V、C

  • M(Model):数据模型,负责数据及其相关的任务
const m = {
    data: {...},
    methods: {
        增、删、改、查...
    }
    ...
}
  • V(View):视图,负责用户界面
const v = {
    element: xxx,
    template: yyy,
    render(data){
        渲染用户视图
    }
    ...
}
  • C(Controller):控制器,用于控制应用程序的流程,处理用户事件,组织调度M和V更新数据和视图
const c = {
    bindEvents(){
        绑定用户事件    
    }
    ...
}

# 二、eventBus的作用

eventBus是一个对象,它可以用来完成上述M、V、C对象间的通信。

例如,我们希望当M中的数据变化时,能够自动触发V的render,使变化后的数据即时渲染到界面上,如何做到呢?

eventsBus提供了on、off、trigger等方法,用来处理事件。我们可以在M的data发生变动时,用trigger方法触发一个事件A(字符串):

const m = {
    data: {...},
    methods: {
        ...
        update(data){
            修改m.data
            eventBus.trigger(A)
        }
    }
}

然后,用on方法监听这个事件A,一旦事件A触发(即数据变化了),就调用V的render重新渲染视图:

eventBus.on(A, ()=>{
    v.render(m.data)
})

如此,就实现了M和V之间的沟通,使得视图和数据保持同步。

# 三、表驱动编程

表驱动编程是一种很重要的编程思想,它的理念是从大量相似的代码中抽取出本质的东西,组成哈希表,利用表进行编程,以减少重复代码。

例如,需要给多个元素绑定不同的事件,直觉上我们会一个个地罗列:

// jQuery 风格写法
$('#el1').on('事件A', fn1)
$('#el2').on('事件B', fn2)
$('#el3').on('事件C', fn3)
$('#el4').on('事件D', fn4)
$('#el5').on('事件E', fn5)

一旦重复次数过多,代码就会变得冗余、臃肿。纵观整体,可以发现每行代码最关键的信息其实就是'#el'、'事件'和fn,将关键信息抽离,组成一个对象:

const events = {
    '#el1 事件A': 'fn1',
    '#el2 事件B': 'fn2',
    '#el3 事件C': 'fn3',
    '#el4 事件D': 'fn4',
    '#el5 事件E': 'fn5'
}

const eventFunctions = { //事件处理函数
    fn1(){}
    fn2(){}
    fn3(){}
    fn4(){}
    fn5(){}
}

然后创建一个函数,给这些元素绑定相应的事件:

function autoBindEvents(){
    for(let key in events){
        const spaceIndex = key.indexOf(' ')
        const element = key.slice(0, spaceIndex)  //得到各个'#el'
        const event = key.slice(spaceIndex + 1) //得到各个'事件'
        const fn = eventFunctions[events[key]] //得到各个fn
        $(element).on(event, fn) //绑定事件
    }
}

如此,就去除了所有重复的代码。乍看可能觉得更复杂了,但是当事件越来越多时,其简洁性就越来越显著。而且后续再添加新的事件时,只需在events对象中添加即可,非常便捷。

# 四、模块化思想

现代的前端应用程序功能越来越强大,代码量也越来越庞大。因此,将网页应用分为各个独立的模块单独开发,每个模块之间互不影响,可以使程序的结构更加清晰,方便维护。

现代浏览器已经原生支持了模块功能,使用exportimport语句就可以实现。

在以前,实现一个应用需要引入html、css、js。有了模块化的思想,只需引入一个js就可以实现。

在main.js中引入各个模块的js,各个js再各自引用自己的css、创建自己的html。

我们还可以把这些模块用到的类,也封装成单独的模块,然后再将其引入到各个模块中。

如此,所有的模块都是独立的,互不影响,代码的层次结构会变得异常清晰。

# 2】ES6扩展符的使用

# 数组去重:

ES6 引入了 Set 对象延展(spread)语法…,我们可以用它们来创建一个只包含唯一值的数组。

var set = new Set([1, 2, 3, 3, 3]);
console.log(set);//Set { 1, 2, 3 }
console.log(...set);//1 2 3
console.log([...set]);//[ 1, 2, 3 ]

ES6的Set集合对象只能支持包含原始类型的数组undefinednullbooleanstringnumber。但如果你的数组包含了对象、函数或其他嵌套数组,就不能使用这种方法了。

# 3】在循环中缓存数组长度

在我们学习使用 for 循环时,一般建议使用这种结构:

for (let i = 0; i < array.length; i++){
 console.log(i);
}

在使用这种方式时,for循环的每次迭代都会重复计算数组长度

有时候这个会很有用,但在大多数情况下,如果能够缓存数组的长度会更好,这样只需要计算一次就够了。我们可以把数组长度复制给一个叫作 length 的变量,例如:

for (let i = 0, length = array.length; i < length; i++){
 console.log(i);
}

这段代码和上面的差不多,但从性能方面来看,即使数组变得很大,也不需要花费额外的运行时重复计算 array.length

# 4】短路求值

# 三元表达式:

使用三元运算符可以很快地写出条件语句,例如:

function fun(x) {
    x > 100 ? (x > 120 ? console.log("yes100yes120") : console.log("yes100no120")) : console.log("no100")
}
fun(101)//yes100no120
fun(99)//no100
fun(122)//yes100yes120

但有时候三元运算符仍然很复杂,我们可以使用逻辑运算符 && 和||来替代,让代码更简洁一些。这种技巧通常被称为“短路求值”。

# &&符号:

假设我们想要返回两个或多个选项中的一个,使用 &&可以返回第一个false如果所有操作数的值都是true,将返回最后一个表达式的值

tips:&&是“与运算符”,对于one && two && three来说,如果前面的都为真才会执行下去,直到遇到假的那个才返回。如果一来就是假直接返回。||完全相反

从左到右找为假的那个值

let one = 1, two = 2, three = 3;
console.log(one && two && three); // Result: 3
console.log(0 && null); // Result: 0

# ||符号:

使用||可以返回第一个 true。如果所有操作数的值都是 false,将返回最后一个表达式的值。

tips:||是“或运算符”,对于one || two || three来说,如果前面的都是假的才会执行下去,直到遇到真的那个才返回。如果一来就是真直接返回。&&完全相反

从左到右找为真的那个值返回

let one = 1, two = 2, three = 3;
console.log(one || two || three); // Result: 1
console.log(0 || null); // Result: null
console.log(0 || one || null);// Result: 1

# 示例 1:

假设我们想要返回一个变量的 length,但又不知道变量的类型。

我们可以使用 if/else检查foo 是否是一个可接受的类型,但这样会让代码变得很长。这个时候可以使用短路求值:

return (foo || []).length;

对于上述两种情况,如果变量 foo 具有 length 属性,这个属性的值将被返回,否则将返回0

# 示例 2

你是否曾经在访问嵌套对象属性时遇到过问题?你可能不知道对象或某个子属性是否存在,所以经常会碰到让你头疼的错误。

假设我们想要访问this.state中的一个叫作data的属性,但data却是undefined的。在某些情况下调用this.state.data会导致App无法运行。为了解决这个问题,我们可以使用条件语句:

if (this.state.data) {
 return this.state.data;
} else {
 return 'Fetching Data';
}

但这样似乎有点啰嗦,而||提供了更简洁的解决方案:

return (this.state.data || 'Fetching Data');

# if(){}使用&&来的简化

&&的优先级高于||

a&&(b=1);
//&&符号为假就跳出,为真继续执行,很好理解
//等同于
if(a=true){
  b=1
}

a=true则执行b=1a=false则不执行b=1。用if写:if(a=true){b=1}

# else if(){}使用||来简化:

a&&(b=1)||c&&(b=2);
//这里要非常注意&&的优先级比||高,(&&可以看成乘法,||可以看成加法)

//等同于
if(a=true){
  b=1
}else if(c=true){
  b=2
}
//&&优先级更高,||看成或者的意思,
//先执行&&,a和c同时进行判断然后出一个执行的结果
//再执行||去比较刚才的结果

# 5】布尔值的妙用

除了标准的布尔值 true 和 false,在 JavaScript 中,所有的值要么是“真值”要么是“假值”。

在 JavaScript 中,除了 0""nullundefinedNaNfalse假值之外,其他的都是真值

我们可以使用 ! 来切换 true 和 false

const isTrue = !0;
const isFalse = !1;
const alsoFalse = !!0;
console.log(true); // Result: true
console.log(typeof true); // Result: "boolean"

# 6】+运算符的妙用

# ① 数字转换成字符串

要快速将数字转换成字符串,我们可以使用+运算符后面跟上一个空字符串。(可以当成字符串拼接来理解)

const val = 1 + "";
console.log(val); // Result: "1"
console.log(typeof val); // Result: "string"

# ②字符串转换成数字

要把字符串转成数字,也可以使用+运算符。

Tips:这种方式只能是**号写字符串前面**才行。

let int = "15";
console.log(typeof int);// Result: "string"
int = +int;
console.log(int); // Result: 15
console.log(typeof int);// Result: "number"

也可以使用这种方式将布尔值转成数字,例如:

console.log(+true); // Return: 1
console.log(+false); // Return: 0

在某些情况下,+运算符会被解析成连接操作,而不是加法操作。对于这种情况,可以使用两个波浪号:~~

一个波浪号表示按位取反操作,例如,~15 等于 -16

const int = ~~"15"//按位取反,然后再取反,转换成数字类型
console.log(int); // Result: 15
console.log(typeof int);// Result: "number"
  
const num = ~"15"
console.log(num)// Result: -16
console.log(typeof num);// Result: "number"

使用两个波浪号可以再次取反,因为-(-n-1)=n+1-1=n,所以~-16 等于 15

# 7】 **幂运算(^不是幂运算)

ES7开始,可以使用** 进行幂运算,比使用Math.power(2,3)要快得多

console.log(2 ** 3); // Result: 8

但要注意不要把这个运算符于^混淆在一起了,^通常用来表示指数运算,但在JavaScript,中^表示位异或运算

ES7之前,可以使用位左移运算符<<来表示以2为底的幂运算:

// 以下表达式是等效的:
Math.pow(2, n);
2 << (n - 1);
2**n;

例如,2 << 3 = 16 等同于2 ** 4 = 16

# 8】快速取整

我们可以使用 Math.floor()向下取整Math.ceil()向上取整Math.round()四舍五入将浮点数转换成整数,但有另一种更快的方式,即使用位或运算符 |

console.log(23.9 | 0); // Result: 23
console.log(-23.9 | 0); // Result: -23

|的实际行为取决于操作数是正数还是负数,所以在使用这个运算符时要确保你知道操作数是正是负。

如果n是正数,那么n|0向下取整,否则就是向上取整。它会移除小数部分,也可以使用~~达到同样的效果。

移除整数尾部数字

|运算符也可以用来移除整数的尾部数字,这样就不需要像下面这样:

let str = "1553"; 
Number(str.substring(0, str.length - 1));

相反,我们可以这样:

console.log(1553 / 10 | 0) // Result: 155
console.log(1553 / 100 | 0) // Result: 15
console.log(1553 / 1000 | 0) // Result: 1

# 位运算实现向下取整:

除了上面的常用方法外,在一些地方我们还可能会看到使用位运算来实现向下取整。例如:

console.log(0.8 | 0);//输出0
console.log(-0.8 | 0);//输出0
console.log(1 | 0);//输出1

或者使用>>:(位左移运算符)

console.log(0.8 >> 0);//输出0
console.log(-0.8 >> 0);//输出0
console.log(1 >> 0);//输出1

# 位运算实现的原因:

这是由于js内部的类型自动转换,js数值**number型都是由64位浮点型表示的,当进行位运算的时候,会自动转换为32为有符号的整数,并舍弃小数位**。所以就可以实现向下取整了。

# 9】箭头函数绑定this

在 ES6 中,我们可以使用箭头函数进行隐式绑定this,这样可以为类的构造器省下一些代码,并跟一些重复出现的表达式说再见,比如this.myMethod = this.myMethod.bind(this)

this只与执行上下文有关,与作用域一点关系都没有!!

多多注意:嵌套函数中的this指向;回调函数的this

import React, { Component } from React;
export default class App extends Compononent {
 constructor(props) {
  super(props);
  this.state = {};
 }
 myMethod = () => {
  // This method is bound implicitly!
 }
 render() {
  return (<div>
    {this.myMethod()}
   </div>)
 }
};

# 10】截取数组

如果你想从一个数组尾部移除某些元素,可以使用一种比splice()删除更快的方法。

例如,如果你知道初始数组的大小,可以像下面这样重新定义它的 length 属性

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array.length = 4;
console.log(array); // Result: [0, 1, 2, 3]

这显然是一种更简洁的解决方案。不过,我发现 slice()切割的运行速度更快,所以,如果你更看重速度,可以像下面这样:

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
array = array.slice(0, 4);
console.log(array); // Result: [0, 1, 2, 3]

Tips:slice()splice()的区别

  • slice()方法不会改变原数组,而是返回另一个切割后的数组,如果想删除数组中的一段元素,应该使用Array.splice()方法。
  • slice()也可以用于切割字符串
  • splice()方法向或者从数组中添加或者删除项目返回被删除的项目。(该方法会改变原数组

# 11】获取数组最后的元素

数组的 slice() 方法可以接受负整数,并返回从数组的尾部开始获取元素

如下:

let array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(array.slice(-1)); // Result: [9]
console.log(array.slice(-2)); // Result: [8, 9]
console.log(array.slice(-3)); // Result: [7, 8, 9]

# 12】格式化 JSON

你之前可能使用过JSON.stringify()序列化,但你是否知道它还可以用来给 JSON 添加缩进?

stringify()方法可以接受两个额外的参数,第二个参数是过滤函数(形参为 replacer),用于过滤要显示的 JSON,第三个参数是空格个数(形参为 space)。

space 可以是一个整数,表示空格的个数,也可以是一个字符串(比如’ ’表示制表符),这样得到的 JSON 更容易阅读。

console.log(JSON.stringify({ alpha: 'A', beta: 'B' }));
//'{"alpha":"A","beta":"B"}'
console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, ''));
//'{"alpha":"A","beta":"B"}'
console.log(JSON.stringify({ alpha: 'A', beta: 'B' }, null, ' '));
//'{
// "alpha": "A",
// "beta": "B"
//}'
Last Updated: 8/5/2020, 5:39:29 PM