基本原则
1 易读性优先
2 不要为了优化而优化
如果代码没有造成性能瓶颈,那么就不要为了性能而修改代码
var array = []
for(let i=0;i<array.length;i++){}
复制代码
有人就说,代码有问题,每循环一次就会调用一次 array.length 会造成性能上的浪费。因此他会写作
for(let i=0, l=array.length;i<l;i++){}
复制代码
这样每次读一个常数就好了,但是如果不是性能瓶颈,就不需要去优化。
3 复杂性守恒原则
无论你怎么写代码,复杂性都不会消失
代码不会降低事物本身的复杂。代码和逻辑的复杂性是成正比的。
命名
程序员三大难题:
- 变量命名
- 缓存失效
- 循环边界
1 注意词性
- 普通变量/属性用「名词」
var person = {
name: 'Jack'
}
var student = {
class: 3,
grade: 2
}
复制代码
- bool变量/属性用「形容词」或「be动词」或「情态动词」或「hasX」
var person = {
dead: false, // 形容词可以直接命名
canSpeak: true, // 情态动词有 can、should、will、need,后加动词
isVip: false, // is、was加名词
hasChildren: true // has 后加名词
}
复制代码
- 普通函数/属性用「动词」
var person = {
run(){}, //不及物动词
drinkWater(){} // 及物动词
}
复制代码
- 回调函数/钩子函数用「介词」开头,或者用「动词的现在完成时态」
var person = {
beforeDie(){},
afterDie(){},
// 或者
willDie(){},
dead(){} // 过去分词
}
button.addEventListener('click', onButtonClick)
// vue 中的钩子函数
var component = {
beforeCreate(){},
created(){},
beforeMount(){},
mounted(){},
beforeUpdate(){},
updated(){},
activated(){},
beforeDestroy(){},
destroyed(){}
}
复制代码
- 容易混淆的地方加「前缀」
div1.classList.add('active') // DOM 对象
div2.addClass('active') // jQuery对象
// 不如改成
domDiv1 或 elDiv1.classList.add('active')
$div2.addClass('active')
复制代码
- 属性访问器可以用「名词」
$div.text() // $div.getText()
$div.text('hello') // $div.setText('hello')
复制代码
2 注意一致性
介词一致性
如果你使用了 before + after,那么就在代码的所有地方都坚持使用
如果你使用了 before + 完成时,那么就坚持使用
如果你改来改去,就「不一致」了,不一致将导致「不可预测」
顺序一致性
比如 updateContainerWidth 和 updateHeightOfContainer 的顺序就令人很别扭,同样会引发「不可预测」
时间一致性
有可能随着代码的变迁,一个变量的含义已经不同于它一开始的含义了,这个时候你需要及时改掉这个变量的名字。这一条是最难做到的,因为写代码容易,改代码难。如果这个代码组织得不好,很可能会出现牵一发而动全身的情况。
表里一致性
函数名必须完美体现函数的功能,既不能多也不能少。比如
function getSongs(){
return $.get('/songs).then((response){
div.innerText = response.songs
})
}
复制代码
就违背了表里一致性,getSongs 表示获取歌曲,并没有暗示这个函数会更新页面,但是实际上函数更新了 div,这就是表里不一,正确的写法是:
// 要么纠正函数名
function getSongsAndUpdateDiv(){
return $.get('/songs').then((response){
div.innerText = response.songs
})
}
// 要么写成两个函数
function getSongs(){
return $.get('/songs')
}
function updateDiv(songs){
div.innerText = response.songs
}
getSongs().then((response)=>{
updateDiv(response.songs)
})
复制代码
改代码
我们首先来看一个轮播的代码。
// html
<div class="slidesWindow">
<div class="slides">
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
<div class="slide"></div>
</div>
</div>
// js 优化前
let currentIndex = 0
let $slides = $('.slides')
let $slidesWindow = $('.slidesWindow')
function playNextSlide(){
$slides.css({
transform: `translateX(${-400*(currentIndex+1)}px)`
})
currentIndex += 1
}
function playPrevSlide(){
$slides.css({
transform: `translateX(${-400*(currentIndex-1)}px)`
})
currentIndex -= 1
}
// 下一张
buttonNext.onclick = ()=>{
playNextSlide()
}
// 上一张
buttonPrev.onclick = ()=>{
playPrevSlide()
}
// 每隔3秒自动播放下一张
let timerId = setInterval(()=>{
playNextSlide()
}, 3000)
// 鼠标移入暂停播放
$slidesWindow.on('mouseenter', ()=>{
window.clearInterval(timerId)
})
// 鼠标移出 恢复播放
$slidesWindow.on('mouseleave', ()=>{
timerId = setInterval(()=>{
playNextSlide()
}, 3000)
})
复制代码
使用函数来改代码
步骤:
- 将一坨代码放到一个函数里
- 将代码依赖的外部变量作为参数
- 将代码的输出作为函数的返回值
- 给函数取一个合适的名字
- 调用这个函数并传入参数
- 如果这个函数里的代码超过5行,则依然有优化的空间,请返回第一步
- 使用上述步骤来优化轮播的代码。
// 优化后
// 里面的变量与函数都可以叫做闭包
!function(){ // 全局变量优化
let currentIndex = 0
let timerId
let $slides = $('.slides')
let $slidesWindow = $('.slidesWindow')
let playNext = () => { playSlide(currentIndex+1) }
let playPrev = () => { playSlide(currentIndex-1) }
let clearTimer = () => { window.clearInterval(timerId) }
let resetTimer = () => { timerId = autoPlay() }
function bindEvents(){
let events = [
{el: buttonNext, event: 'click', fn: playNext},
{el: buttonPrev, event: 'click', fn: playPrev},
{el: $slidesWindow, event: 'mouseenter', fn: clearTimer},
{el: $slidesWindow, event: 'mouseleave', fn: resetTimer},
]
events.forEach((eventObject)=>{
$(eventObject.el).on(eventObject.event, eventObject.fn)
})
}
function autoPlay(){
// 每隔3秒自动播放下一张
return setInterval(()=>{
playSlide(currentIndex+1)
}, 3000)
}
function playSlide(index){
index = fixIndex(index)
$slides.css({
transform: `translateX(${-400*(index)}px)`
})
currentIndex = index
return index
}
function fixIndex(index){
if(index < 0){
index = 4
}else if(index > 4){
index = 0
}
return index
}
bindEvents()
timerId = autoPlay()
}()
复制代码
使用对象来改代码
将上述代码使用对象来改。
!function () {
let slides = {
currentIndex: 0,
timerId: undefined,
$slides: $('.slides'),
$slidesWindow: $('.slidesWindow'),
$buttonNext: $('#buttonNext'),
$buttonPrev: $('#buttonPrev'),
// 为什么会不能改,对象没有this上下文,只有函数里面才有this
events() {
return [
{ el: this.$buttonNext, event: 'click', fn: this.playNext },
{ el: this.$buttonPrev, event: 'click', fn: this.playPrev },
{ el: this.$slidesWindow, event: 'mouseenter', fn: this.clearTimer },
{ el: this.$slidesWindow, event: 'mouseleave', fn: this.resetTimer },
]
},
// 另一种
events: [
{ el: '#buttonNext', event: 'click', fn: 'playNext' },
{ el: '#buttonPrev', event: 'click', fn: 'playPrev' },
{ el: '.slidesWindow', event: 'mouseenter', fn: 'clearTimer' },
{ el: '.slidesWindow', event: 'mouseleave', fn: 'resetTimer' },
],
bindEvents: function () {
this.events().forEach((eventObject) => {
$(eventObject.el).on(
eventObject.event,
this[eventObject.fn].bind(this) // ()=>{this[eventObject.fn].call(this)}
)
})
},
init() {
this.bindEvents()
this.timerId = this.autoPlay()
},
playNext() { this.playSlide(this.currentIndex + 1) },
playPrev() { this.playSlide(this.currentIndex - 1) },
clearTimer() { window.clearInterval(this.timerId) },
resetTimer() { this.timerId = this.autoPlay() },
bindEvents: function () {
this.events().forEach((eventObject) => {
$(eventObject.el).on(eventObject.event, eventObject.fn)
})
},
autoPlay() {
// this === slides
// 每隔3秒自动播放下一张
// return setInterval(function() {
// this.playSlide(this.currentIndex + 1)
// }.bind(this), 3000)
return setInterval(() => {
this.playSlide(this.currentIndex + 1)
}, 3000)
},
playSlide(index) {
index = this.fixIndex(index)
this.$slides.css({
transform: `translateX(${-400 * (index)}px)`
})
this.currentIndex = index
return index
},
fixIndex(index) {
if (index < 0) {
index = 4
} else if (index > 4) {
index = 0
}
return index
}
}
slides.init() // slides.init.call(slides)
}()
复制代码
一些固定的套路
1. 表驱动编程(《代码大全》里说的:所有一一对应的关系都可以用表来做
// 打出每个月的天数
// 优化前
function howManyDays(month) {
if (month === 1 ||
month === 3 ||
month === 5 ||
month === 7 ||
month === 8 ||
month === 10 ||
month === 12
) {
return 31
} else if(month === 2){
return 28
}else{
return 30
}
}
// 优化后
function howManyDays(month){
var table = {
1: 31,
2: 28,
3: 31,
4: 30,
5: 31,
6: 30,
7: 31,
8: 31,
9: 30,
10: 31,
11: 30,
12: 31
}
return table[month]
}
复制代码
根据分数计算等级
function caculateGrade(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 caculateGrade(score) {
var table = {
10: 'A',
9: 'A',
8: 'B',
7: 'C',
6: 'D',
others: 'E'
}
var level = parseInt(score / 10, 10)
return table[level] || table['others']
}
复制代码
上述表驱动编程,可以使if else 荡然无存。
2. 自说明代码(以 API 参数为例)
把别人关心的东西放在显眼的位置
class Dialog(){
constructor(options){
let defaultOptions: {
title: '',
content: '<h2>请写content<h2>',
buttons: [
{text:'确定', action: function(close){close()}},
{text:'取消', action: function(close){close()}}
]
}
this.options = Object.assign({}, defaultOptions, options)
}
}
复制代码
代码的 bad smell
- 表里不一的代码
- 过时的注释
- 逻辑很简单,但是看起来很复杂的代码
- 重复的代码
- 相似的代码
- 总是一起出现的代码
破窗效应
此理论认为环境中的不良现象如果被放任存在,会诱使人们仿效,甚至变本加厉。一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里定居或者纵火。一面墙,如果出现一些涂鸦没有被清洗掉,很快的,墙上就布满了乱七八糟、不堪入目的东西;一条人行道有些许纸屑,不久后就会有更多垃圾,最终人们会视若理所当然地将垃圾顺手丢弃在地上。这个现象,就是犯罪心理学中的破窗效应。
写在最后
如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力,多谢支持。