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对象中添加即可,非常便捷。
# 四、模块化思想
现代的前端应用程序功能越来越强大,代码量也越来越庞大。因此,将网页应用分为各个独立的模块单独开发,每个模块之间互不影响,可以使程序的结构更加清晰,方便维护。
现代浏览器已经原生支持了模块功能,使用export
和import
语句就可以实现。
在以前,实现一个应用需要引入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集合对象只能支持包含原始类型的数组:undefined
、null
、boolean
、string
和number
。但如果你的数组包含了对象、函数或其他嵌套数组,就不能使用这种方法了。
# 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=1
,a=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
、""
、null
、undefined
、NaN
和 false
是假值之外,其他的都是真值。
我们可以使用 !
来切换 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"
//}'