當我們完成項目的構建,進入開發階段的時候,除了你需要了解框架本身的知識點外,我們還需要提前掌握一些項目的編碼技巧與規范,在根源上解決之后因編碼缺陷而導致的項目維護困難、性能下降等常見問題,為項目多人開發提供編碼的一致性。
本文將羅列項目中常用的一些編碼技巧與規范來幫助大家提升代碼質量,并會結合代碼片段加強大家的理解與認知。當然不是所有實例都是針對 Vue.js 開發的,有些同樣也適用于其他前端項目。
## 實例
### 1\. 使用對象代替 if 及 switch
在很多情況下,我們經常會遇到循環判斷執行賦值操作的場景,一般我們都會使用 if 及 switch 的條件判斷,如果符合則執行賦值,不符合則進入下個判斷,比如:
```
let name = 'lisi';
let age = 18;
if (name === 'zhangsan') {
age = 21;
} else if (name === 'lisi') {
age = 18;
} else if (name === 'wangwu') {
age = 12;
}
// 或者
switch(name) {
case 'zhangsan':
age = 21;
break
case 'lisi':
age = 18;
break
case 'wangwu':
age = 12;
break
}
```
這樣的寫法不僅冗余,而且代碼執行效率不高,我們可以使用對象的形式簡寫:
```
let name = 'lisi';
let obj = {
zhangsan: 21,
lisi: 18,
wangwu: 12
};
let age = obj[name] || 18;
```
以上這種技巧適用于循環判斷一次賦值的情況,如果判斷過后有較多處理邏輯的還需要使用 if 或 switch 等方法。
### 2\. 使用 Array.from 快速生成數組
一般我們生成一個有規律的數組會使用循環插入的方法,比如使用時間選擇插件時,我們可能需要將小時數存放在數組中:
```
let hours = [];
for (let i = 0; i < 24; i++) {
hours.push(i + '時');
}
```
如果使用 Array.from 我們可以簡寫為:
```
let hours = Array.from({ length: 24 }, (value, index) => index + '時');
```
### 3\. 使用 router.beforeEach 來處理跳轉前邏輯
在某些情況下,我們需要在路由跳轉前處理一些特定的業務邏輯,比如修改路由跳轉、設置 title 等,代碼如下:
```
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
// 首頁
const Home = (resolve => {
require.ensure(['../views/home.vue'], () => {
resolve(require('../views/home.vue'))
})
})
let base = `${process.env.BASE_URL}`;
let router = new Router({
mode: 'history',
base: base,
routes: [
{
path: '/',
name: 'home',
component: Home,
meta: { title: '首頁' }
},
]
})
router.beforeEach((to, from, next) => {
let title = to.meta && to.meta.title;
if (title) {
document.title = title; // 設置頁面 title
}
if (to.name === 'home') {
// 攔截并跳轉至 page2 單頁,$openRouter 方法在第 5 節中封裝
Vue.$openRouter({
name: 'page2'
});
}
next();
})
export default router
```
注意最后需要調用 `next()` 方法執行路由跳轉。
### 4\. 使用 v-if 來優化頁面加載
在 Vue 頁面中,一些模塊可能需要用戶主動觸發才會顯示,比如彈框組件等這樣的子組件,那么我們可以使用 `v-if` 來進行按需渲染,沒必要一進頁面就渲染所有模塊。比如:
```
<template>
<div @click="showModuleB = true"></div>
<module-b v-if="isShowModuleB"></module-b>
</template>
<script>
import moduleB from 'components/moduleB'
export default {
data() {
return {
isShowModuleB: false
}
},
components: {
moduleB
}
}
</script>
```
這樣當 isShowModuleB 為 false 的時候便不會加載該模塊下的代碼,包括一些耗時的接口調用。當然 v-if 主要適用于代碼量較多、用戶點擊不是很頻繁的模塊的顯示隱藏,同時如果涉及到權限問題的代碼都需要使用 v-if,而不是 v-show。
### 5\. 路由跳轉盡量使用 name 而不是 path
我們前期配置的路由路徑后期難免會進行修改,如果我們頁面跳轉的地方全是使用的 `path`,那么我們需要修改所有涉及該 path 的頁面,這樣不利于項目的維護。而相對于 path,name 使用起來就方便多了,因為其具有唯一性,即使我們修改了 path,還可以使用原來的 `name` 值進行跳轉。
```
this.$router.push({
name: 'page1'
});
// 而不是
this.$router.push({
path: 'page1'
});
```
### 6\. 使用 key 來優化 v-for 循環
`v-for` 是 Vue 提供的基于源數據多次渲染元素或模板塊的指令。正因為是數據驅動,所以在修改列表數據的時候,Vue 內部會根據 key 值去判斷某個值是否被修改,其會重新渲染修改后的值,否則復用之前的元素。
這里如果數據中存在唯一表示 id,則推薦使用 id 作為 key,如果沒有則可以使用數組的下標 index 作為 key。因為如果在數組中間插入值,其之后的 index 會發生改變,即使數據沒變 Vue 也會進行重新渲染,所以最好的辦法是使用數組中不會變化且唯一的那一項作為 key 值。例如:
```
<template>
<ul>
<li v-for="(item, index) in arr" :key="item.id">{{ item.data }}</li>
</ul>
</template>
<script>
export default {
data() {
return {
arr: [
{
id: 1,
data: 'a'
},
{
id: 2,
data: 'b'
},
{
id: 3,
data: 'c'
}
]
}
}
}
</script>
```
### 7\. 使用 computed 代替 watch
很多時候頁面會出現 `watch` 的濫用而導致一系列問題的產生,而通常更好的辦法是使用 `computed` 屬性,首先需要區別它們有什么區別:
* watch:當監測的屬性變化時會自動執行對應的回調函數
* computed:計算的屬性只有在它的相關依賴發生改變時才會重新求值
其實它們在功能上還是有所區別的,但是有時候可以實現同樣的效果,而 computed 會更勝一籌,比如:
```
<template>
<div>
<input type="text" v-model="firstName">
<input type="text" v-model="lastName">
<span>{{ fullName }}</span>
<span>{{ fullName2 }}</span>
</div>
</template>
<script>
export default {
data() {
reurn {
firstName: '',
lastName: '',
fullName2: ''
}
},
// 使用 computed
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
}
},
// 使用 watch
watch: {
firstName: function(newVal, oldVal) {
this.fullName2 = newVal + ' ' + this.lastName;
},
lastName: function(newVal, oldVal) {
this.fullName2 = this.firstName + ' ' + newVal;
},
}
}
</script>
```
上方我們通過對比可以看到,在處理多數據聯動的情況下,使用 computed 會更加合理一點。

computed 監測的是依賴值,依賴值不變的情況下其會直接讀取緩存進行復用,變化的情況下才會重新計算;而 watch 監測的是屬性值, 只要屬性值發生變化,其都會觸發執行回調函數來執行一系列操作。
### 8\. 統一管理緩存變量
在項目中或多或少會使用瀏覽器緩存,比如 sessionStorage 和 localStorage,當一個項目中存在很多這樣的緩存存取情況的時候就會變得難以維護和管理,因為其就像全局變量一樣散落在項目的各個地方,這時候我們應該將這些變量統一管理起來,放到一個或多個文件中去,比如:
```
/* types.js */
export const USER_NAME = 'userName';
export const TOKEN = 'token';
```
在需要存取的時候,直接引用:
```
import { USER_NAME, TOKEN } from '../types.js'
sessionStorage[USER_NAME] = '張三';
localStorage[TOKEN] = 'xxx';
```
使用這種方法的好處在于一旦我們需要修改變量名,直接修改管理文件中的值即可,無需修改使用它的頁面,同時這也可以避免命名沖突等問題的出現,這類似于 vuex 中 mutations 變量的管理。
### 9\. 使用 setTimeout 代替 setInterval
一般情況下我們在項目里不建議使用 `setInterval`,因為其會存在代碼的執行間隔比預期小以及 “丟幀” 的現象,原因在于其本身的實現邏輯。很多人會認為 setInterval 中第二個時間參數的作用是經過該毫秒數執行回調方法,其實不然,其真正的作用是**經過該毫秒數將回調方法放置到隊列中去**,但是如果隊列中存在正在執行的方法,其會等待之前的方法完畢再執行,如果存在還未執行的代碼實例,其不會插入到隊列中去,也就產生了 “丟幀”。
而 setTimeout 并不會出現這樣的現象,因為每一次調用都會產生了一個新定時器,同時在前一個定時器代碼執行完之前,不會向隊列插入新的定時器代碼。
```
// 該定時器實際會在 3s 后立即觸發下一次回調
setInterval(() => {
// 執行完這里的代碼需要 2s
}, 1000);
// 使用 setTimeout 改寫,4秒后觸發下一次回調
let doSometing = () => {
// 執行完這里的代碼需要 2s
setTimeout(doSometing, 1000);
}
doSometing();
```
延伸閱讀:[對于“不用setInterval,用setTimeout”的理解](https://segmentfault.com/a/1190000011282175)
### 10\. 不要使用 for in 循環來遍歷數組
大家應該都知道 `for in` 循環是用于遍歷對象的,但它可以用來遍歷數組嗎?答案是可以的,因為數組在某種意義上也是對象,但是如果用其遍歷數組會存在一些隱患:其會遍歷數組原型鏈上的屬性。
```
let arr = [1, 2];
for (let key in arr) {
console.log(arr[key]); // 會正常打印 1, 2
}
// 但是如果在 Array 原型鏈上添加一個方法
Array.prototype.test = function() {};
for (let key in arr) {
console.log(arr[key]); // 此時會打印 1, 2, ? () {}
}
```
因為我們不能保證項目代碼中不會對數組原型鏈進行操作,也不能保證引入的第三方庫不對其進行操作,所以不要使用 for in 循環來遍歷數組。
## 結語
本文羅列了 10 個項目開發中常見的編碼技巧與規范,其實技巧和規范之間本身就是相輔相成的,所以沒有分別進行羅列。當然實際的項目開發中存在著很多這樣的例子需要大家自己去歸納和整理,比如使用 `name` 來命名你的組件等。如果你有不錯的點子,也可以分享在下方的評論區域中供大家學習。
拓展閱讀:[前端各類規范集合](https://github.com/ecomfe/spec)
## 思考 & 作業
* 可以使用哪些技巧來實現數組的循環遍歷、去重等?
* 在 Vue 項目中如何使用 `ESLint` 來規范 JS 代碼的編寫?
* .vue 單文件組件中如何進行代碼的格式化?
- 開篇:Vue CLI 3 項目構建基礎
- 構建基礎篇 1:你需要了解的包管理工具與配置項
- 構建基礎篇 2:webpack 在 CLI 3 中的應用
- 構建基礎篇 3:env 文件與環境設置
- 構建實戰篇 1:單頁應用的基本配置
- 構建實戰篇 2:使用 pages 構建多頁應用
- 構建實戰篇 3:多頁路由與模板解析
- 構建實戰篇 4:項目整合與優化
- 開發指南篇 1:從編碼技巧與規范開始
- 開發指南篇 2:學會編寫可復用性模塊
- 開發指南篇 3:合理劃分容器組件與展示組件
- 開發指南篇 4:數據驅動與拼圖游戲
- 開發指南篇 5:Vue API 盲點解析
- 開發拓展篇 1:擴充你的開發工具
- 開發拓展篇 2:將 UI 界面交給第三方庫
- 開發拓展篇 3:嘗試使用外部數據
- 總結篇:寫在最后