当我们完成项目的构建,进入开发阶段的时候,除了你需要了解框架本身的知识点外,我们还需要提前掌握一些项目的编码技巧与规范,在根源上解决之后因编码缺陷而导致的项目维护困难、性能下降等常见问题,为项目多人开发提供编码的一致性。
本文将罗列项目中常用的一些编码技巧与规范来帮助大家提升代码质量,并会结合代码片段加强大家的理解与认知。当然不是所有实例都是针对 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;breakcase 'lisi':age = 18;breakcase '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 快速生成数组
Array.from()
是一个非常有用的 JavaScript 方法,它可以从类数组对象或可迭代对象创建一个新的数组实例。通过 Array.from()
,可以快速而方便地生成数组,甚至可以通过传递一个映射函数来对生成的数组项进行处理。
1.从类数组对象创建数组:
例如,可以将字符串转换为字符数组。
const str = "hello";
const charArray = Array.from(str);
console.log(charArray); // ['h', 'e', 'l', 'l', 'o']
2.从可迭代对象创建数组:
例如,将 Set 转换为数组。
const mySet = new Set([1, 2, 3]);
const arrayFromSet = Array.from(mySet);
console.log(arrayFromSet); // [1, 2, 3]
3.通过映射函数生成数组:
Array.from()
还可以接受一个映射函数作为第二个参数,用于对生成的数组项进行处理。
const nums = Array.from([1, 2, 3, 4, 5], x => x * 2);
console.log(nums); // [2, 4, 6, 8, 10]
4.生成特定长度的数组
可以使用 Array.from()
生成一个特定长度的数组,并用自定义的逻辑填充它。例如,传递了一个对象 { length: 10 }
作为第一个参数,以指定生成的数组的长度。第二个参数是一个映射函数,它返回随机数
const randomNumbers = Array.from({ length: 10 }, () => Math.random());
console.log(randomNumbers);
5.生成序列
可以使用 Array.from()
生成一个数值序列。例如,生成一个从 1 到 10 的数字数组:
const sequence = Array.from({ length: 10 }, (_, index) => index + 1);
console.log(sequence); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
又或者是下面例子
一般我们生成一个有规律的数组会使用循环插入的方法,比如使用时间选择插件时,我们可能需要将小时数存放在数组中:
let hours = [];for (let i = 0; i < 24; i++) {hours.push(i + '时');
}
如果使用 Array.from 我们可以简写为:
let hours = Array.from({ length: 24 }, (value, index) => index + '时');
在后面的映射函数中,value
是当前元素的值(在这种情况下未使用),index
是当前元素的索引(从 0 到 23)。上面的例子同理。
3.使用 router.beforeEach 来处理跳转前逻辑
在 Vue Router 中,router.beforeEach
是一个全局前置守卫,用于在路由跳转之前执行一些逻辑。可以使用它来进行身份验证、权限检查或任何其他需要在路由切换之前进行的操作。
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 单页,$openRuter 方法在第 5 节中封装Vue.$openRouter({name: 'page2'});}next();
})export default router
4. 使用 v-if 来优化页面加载
在 Vue.js 中,使用 v-if
指令可以控制元素的条件渲染,优化页面加载的性能。通过在组件中使用 v-if
,可以根据特定条件选择性地渲染某些部分,从而减少初始加载时 DOM 的复杂性和资源消耗。下面罗列三种实例来展示v-if如何优化页面加载的:
示例 1: 条件渲染组件
假设有一个应用程序,其中某些组件仅在特定条件下显示,例如用户是否已登录。
<template><div><h1>欢迎来到我的网站</h1><button @click="toggleLogin">{{ isAuthenticated ? '登出' : '登录' }}</button><div v-if="isAuthenticated"><h2>欢迎回来,用户!</h2><p>这是仪表盘内容。</p></div><div v-else><h2>请登录以查看仪表盘内容。</h2></div></div>
</template><script>
export default {data() {return {isAuthenticated: false // 用户的登录状态};},methods: {toggleLogin() {this.isAuthenticated = !this.isAuthenticated; // 切换登录状态}}
};
</script>
示例 2: 懒加载组件
使用 v-if
来懒加载组件也是一种优化方式。只有在用户需要时才加载特定组件,这样可以减少初始加载开销。
<template><div><h1>我的应用</h1><button @click="showDashboard = !showDashboard">{{ showDashboard ? '隐藏仪表盘' : '显示仪表盘' }}</button><div v-if="showDashboard"><DashboardComponent v-if="isComponentLoaded" /><button @click="loadComponent">加载仪表盘组件</button></div></div>
</template><script>
import { defineAsyncComponent } from 'vue';export default {data() {return {showDashboard: false,isComponentLoaded: false};},components: {DashboardComponent: defineAsyncComponent(() => import('./DashboardComponent.vue'))},methods: {loadComponent() {this.isComponentLoaded = true; // 加载组件}}
};
</script>
DashboardComponent
组件是懒加载的,只有在用户点击按钮后才会被加载并渲染。- defineAsyncComponent`: 是 Vue 3 提供的一个函数,用于定义异步组件。
v-if
用于控制组件的渲染,从而避免在初始加载时加载不必要的组件。
示例 3: 加载指示器
在数据请求期间,可以使用 v-if
显示一个加载指示器,以提高用户体验。
<template><div><h1>数据加载示例</h1><button @click="fetchData">加载数据</button><div v-if="isLoading">正在加载...</div><div v-else-if="data"><h2>数据:</h2><pre>{{ data }}</pre></div><div v-else><h2>没有数据可显示</h2></div></div>
</template><script>
export default {data() {return {data: null,isLoading: false};},methods: {async fetchData() {this.isLoading = true;this.data = null;// 模拟数据请求await new Promise(resolve => setTimeout(resolve, 2000));this.data = { name: 'John Doe', age: 30 }; // 模拟获取的数据this.isLoading = false;}}
};
</script>
5. 路由跳转尽量使用 name 而不是 path
使用路由的 name
属性而不是 path
进行导航是一种良好的实践,因为它可以提高代码的可维护性和可读性。在大型应用中,路径可能会发生变化,而使用名称则能够保持路由导航的稳定性。
使用 name
的优势
- 清晰性: 使用命名路由,可以更直接地理解跳转的目标,而不需要解析完整的路径。
- 可维护性: 如果需要更改某个路由的路径,只需在路由配置中进行修改,而不需要在多个地方更新路径字符串。这降低了出错的风险。
- 动态参数: 如果路由包含动态参数,使用
name
可以更方便地传递这些参数。 - 简化重构: 随着应用程序的发展,路由可能会频繁更改,使用命名路由可以减少因路径更改而导致的代码改动。
示例
在定义路由时,给每个路由分配一个 name
属性:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';const routes = [{path: '/',name: 'Home',component: Home},{path: '/about',name: 'About',component: About},{path: '/user/:id',name: 'User',component: () => import('../views/User.vue')}
];const router = createRouter({history: createWebHistory(),routes
});export default router;
在组件中,使用 name
属性来进行路由跳转:
<template><div><h1>欢迎来到我的应用</h1><button @click="goToAbout">关于我们</button><button @click="goToUser(123)">用户 123</button></div>
</template><script>
export default {methods: {goToAbout() {this.$router.push({ name: 'About' });},goToUser(userId) {this.$router.push({ name: 'User', params: { id: userId } });}}
};
</script>
- 路由定义: 在定义路由时,每个路由都有一个独特的
name
属性。在上面的示例中,Home
、About
和User
是路由的名称。 - 路由跳转:
this.$router.push({ name: 'About' })
: 使用名称进行跳转。this.$router.push({ name: 'User', params: { id: userId } })
: 在跳转到带有动态参数的路由时,使用params
传递参数。
6. 使用 key 来优化 v-for 循环
为什么要使用 key
?
- 提高性能: Vue 会根据
key
在更新时追踪每个节点,从而知道哪些节点是新增的、哪些是被删除的,哪些是被移动的。这使得 Vue 可以更高效地进行 DOM 更新。 - 保持组件状态: 当在列表中渲染组件时,如果没有
key
,Vue 可能会错误地复用组件,导致组件状态混乱。使用key
可以确保每个组件都能正确维护自己的状态。
这里如果数据中存在唯一表示 id,则推荐使用 id 作为 key,如果没有则可以使用数组的下标 index 作为 key。因为如果在数组中间插入值,其之后的 index 会发生改变,即使数据没变 Vue 也会进行重新渲染,所以最好的办法是使用数组中不会变化且唯一的那一项作为 key 值。这样,Vue 就能在更新时追踪每个列表项。例如:
<template><div><h1>用户列表</h1><ul><li v-for="user in users" :key="user.id">{{ user.name }}</li></ul></div>
</template><script>
export default {data() {return {users: [{ id: 1, name: 'Alice' },{ id: 2, name: 'Bob' },{ id: 3, name: 'Charlie' }]};}
};
</script>
注意事项
- 唯一性: 确保
key
是唯一的,不同的元素应该有不同的key
。 - 稳定性: 尽量使用稳定的标识符(如数据库中的 ID),避免使用容易变化的数据(如索引),因为这可能会导致不必要的组件重用和状态混乱。
- 组件: 如果在
v-for
中渲染的是组件,也同样需要为组件指定key
。
7. 使用 computed 代替 watch
在 Vue.js 中,computed
和 watch
都是响应式系统的两个重要特性,它们可以用于监测数据的变化并执行相应的逻辑。然而,它们的应用场景不同,通常可以用 computed
来代替一些 watch
的场景,以提高代码的简洁性和可读性。首先需要区别它们有什么区别:
-
watch:当监测的属性变化时会自动执行对应的回调函数
-
computed:计算的属性只有在它的相关依赖发生改变时才会重新求值
何时使用 computed
computed
属性通常用于基于已存在的数据计算出新的数据。如果需要根据某些响应式数据的变化来计算一个值,那么使用 computed
是一个更好的选择。
何时使用 watch
尽管 computed
能够替代许多 watch
的场景,但 watch
仍然有其适用的场景,例如:
- 异步操作: 当需要在数据变化时执行异步操作(例如 API 请求)时,
watch
更为合适。 - 多个数据依赖: 当需要监测多个数据源并根据其变化执行一些逻辑时,
watch
提供的灵活性更高。
使用 computed
的优势
- 自动缓存:
computed
属性的结果会被缓存,只有当其依赖的响应式数据发生变化时,才会重新计算。这可以提高性能,尤其是在计算开销较大的情况下。 - 简化代码:
computed
可以使代码更简洁,更容易理解。它可以直接在模板中使用,而不用额外的watch
逻辑。
示例:
假设我们有一个简单的示例,其中我们需要根据输入的数字来计算它的平方值,原本可以使用 watch
来实现,但我们可以使用 computed
来简化代码。
使用 watch
的示例
<template><div><input v-model="number" type="number" /><p>平方: {{ square }}</p></div>
</template><script>
export default {data() {return {number: 0,square: 0};},watch: {number(newValue) {this.square = newValue * newValue;}}
};
</script>
使用 computed
的示例
<template><div><input v-model="number" type="number" /><p>平方: {{ square }}</p></div>
</template><script>
export default {data() {return {number: 0};},computed: {square() {return this.number * this.number;}}
};
</script>
- 在使用
watch
的示例中,我们手动监测number
的变化,并在变化时更新square
。 - 在使用
computed
的示例中,我们直接定义一个计算属性square
,它依赖于number
。当number
发生变化时,square
会自动重新计算,而我们不需要额外的逻辑来进行更新。
8. 统一管理缓存变量
在项目中或多或少会使用浏览器缓存,比如 sessionStorage 和 localStorage,当一个项目中存在很多这样的缓存存取情况的时候就会变得难以维护和管理,因为其就像全局变量一样散落在项目的各个地方,这时候我们应该将这些变量统一管理起来,放到一个或多个文件中去,比如使用 Vuex、Composition API 或者简单的 Vue 实例数据来实现这一目标。:
1. 使用 Vuex 统一管理缓存变量
如果项目较大,使用 Vuex 可以帮助你更好地管理状态。Vuex 是 Vue 的官方状态管理库,可以将所有的状态集中在一个地方。
创建一个 Vuex Store
// store.js
import { createStore } from 'vuex';const store = createStore({state() {return {cache: {}};},mutations: {setCache(state, { key, value }) {state.cache[key] = value;},clearCache(state) {state.cache = {};}},actions: {updateCache({ commit }, cacheData) {commit('setCache', cacheData);},resetCache({ commit }) {commit('clearCache');}}
});export default store;
在 Vue 应用中使用 Vuex Store
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';const app = createApp(App);
app.use(store);
app.mount('#app');
使用缓存变量
在组件中,可以通过 mapState
或 mapActions
来访问和修改缓存变量。
<template><div><input v-model="cacheValue" @input="updateCache({ key: 'myKey', value: cacheValue })" /><button @click="resetCache">重置缓存</button><p>当前缓存值: {{ cache['myKey'] }}</p></div>
</template><script>
import { mapState, mapActions } from 'vuex';export default {computed: {...mapState({cache: state => state.cache}),cacheValue: {get() {return this.cache['myKey'] || '';},set(value) {this.updateCache({ key: 'myKey', value });}}},methods: {...mapActions(['updateCache', 'resetCache'])}
};
</script>
2. 使用 Composition API 统一管理缓存变量
如果使用的是 Vue 3,可以使用 Composition API 来管理缓存变量。
创建一个缓存管理的组合函数
// useCache.js
import { reactive } from 'vue';const cache = reactive({});export function useCache() {const setCache = (key, value) => {cache[key] = value;};const clearCache = () => {Object.keys(cache).forEach(key => delete cache[key]);};return {cache,setCache,clearCache};
}
在组件中使用缓存管理
<template><div><input v-model="cacheValue" @input="setCache('myKey', cacheValue)" /><button @click="clearCache">清除缓存</button><p>当前缓存值: {{ cache.myKey }}</p></div>
</template><script>
import { useCache } from './useCache';export default {setup() {const { cache, setCache, clearCache } = useCache();const cacheValue = computed(() => cache.myKey || '');return {cache,cacheValue,setCache,clearCache};}
};
</script>
3. 在 Vue 实例中统一管理缓存变量
如果项目比较小,也可以直接在 Vue 实例中管理缓存变量。
<template><div><input v-model="cacheValue" @input="updateCache('myKey', cacheValue)" /><button @click="clearCache">清除缓存</button><p>当前缓存值: {{ cache.myKey }}</p></div>
</template><script>
export default {data() {return {cache: {}};},computed: {cacheValue: {get() {return this.cache['myKey'] || '';},set(value) {this.updateCache('myKey', value);}}},methods: {updateCache(key, value) {this.$set(this.cache, key, value);},clearCache() {this.cache = {};}}
};
</script>
9. 使用 setTimeout 代替 setInterval
一般情况下我们在项目里不建议使用 setInterval
,因为其会存在代码的执行间隔比预期小以及 “丢帧” 的现象,原因在于其本身的实现逻辑。很多人会认为 setInterval 中第二个时间参数的作用是经过该毫秒数执行回调方法,其实不然,其真正的作用是经过该毫秒数将回调方法放置到队列中去,但是如果队列中存在正在执行的方法,其会等待之前的方法完毕再执行,如果存在还未执行的代码实例,其不会插入到队列中去,也就产生了 “丢帧”。
而 setTimeout 并不会出现这样的现象,因为每一次调用都会产生了一个新定时器,同时在前一个定时器代码执行完之前,不会向队列插入新的定时器代码。
下面是一个使用 setTimeout
的示例,模拟 setInterval
的行为:
let count = 0;function repeatFunction() {console.log('当前计数:', count);count++;// 在一定条件下停止if (count < 5) {// 重新调用自身以实现循环setTimeout(repeatFunction, 1000); // 每隔 1 秒调用一次} else {console.log('计数结束');}
}// 开始执行
repeatFunction();
-
循环控制: 通过在函数内部使用
setTimeout
递归调用自身,可以在每次执行后决定是否继续执行。这种方式可以避免setInterval
中可能出现的定时任务重叠问题。 -
灵活性: 使用
setTimeout
可以在每次调用时调整下次调用的时间间隔。例如,可以根据某些条件动态改变下一次的延迟时间。 -
错误处理: 如果在
setInterval
内部的函数发生错误,可能会导致后续的定时任务无法执行。而使用setTimeout
的方式可以在每次调用后捕获错误,从而较好地处理异常情况。
如果希望在 Vue 组件中使用 setTimeout
来代替 setInterval
,可以按照以下示例进行操作:
<template><div><p>当前计数: {{ count }}</p><button @click="startCounting">开始计数</button><button @click="stopCounting">停止计数</button></div>
</template><script>
export default {data() {return {count: 0,timer: null, // 用于保存定时器 ID};},methods: {repeatFunction() {this.count++;console.log('当前计数:', this.count);if (this.count < 5) {this.timer = setTimeout(this.repeatFunction, 1000); // 每隔 1 秒调用} else {console.log('计数结束');}},startCounting() {this.count = 0; // 重置计数this.repeatFunction(); // 开始计数},stopCounting() {clearTimeout(this.timer); // 清除定时器console.log('计数停止');}},
};
</script>
startCounting
方法启动计数,每秒更新一次计数,并使用setTimeout
调用repeatFunction
。stopCounting
方法可以停止计数,通过clearTimeout
清除定时器。- 计数达到 5 时,输出 "计数结束" 信息。
延伸阅读:对于“不用setInterval,用setTimeout”的理解
10. 不要使用 for in 循环来遍历数组
在 JavaScript 中,有多种方法可以遍历数组,而使用 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 循环来遍历数组。
推荐使用以下几种方法来遍历数组:
1. for
循环
经典的 for
循环是遍历数组的直接方法:
const array = [1, 2, 3, 4, 5];for (let i = 0; i < array.length; i++) {console.log(array[i]);
}
2. forEach
方法
forEach
是一个数组方法,能够更简洁地遍历数组。它接受一个函数作为参数,该函数会对每个元素执行。
const array = [1, 2, 3, 4, 5];array.forEach((item) => {console.log(item);
});
3. for...of
循环
for...of
循环用于遍历可迭代对象,包括数组,语法简单且易于阅读。
javascript
const array = [1, 2, 3, 4, 5];for (const item of array) {console.log(item);
}
4. map
方法
map
方法不仅可以遍历数组,还可以返回一个新数组,包含对原数组每个元素调用函数的结果。
const array = [1, 2, 3, 4, 5];const newArray = array.map((item) => {return item * 2; // 示例: 每个元素乘以 2
});console.log(newArray); // 输出: [2, 4, 6, 8, 10]
5. filter
方法
如果你想要遍历数组并创建一个新数组,包含满足特定条件的元素,可以使用 filter
方法。
const array = [1, 2, 3, 4, 5];const filteredArray = array.filter((item) => {return item > 2; // 示例: 只保留大于 2 的元素
});console.log(filteredArray); // 输出: [3, 4, 5]
6. reduce
方法
如果你需要对数组的每个元素进行累积操作,可以使用 reduce
方法。
const array = [1, 2, 3, 4, 5];const sum = array.reduce((accumulator, item) => {return accumulator + item; // 累加所有元素
}, 0);console.log(sum); // 输出: 15
拓展阅读:前端各类规范集合
拓展
可以使用哪些技巧来实现数组的循环遍历、去重等?
数组的循环遍历
-
for
循环经典的
for
循环可以针对每个元素执行操作。const array = [1, 2, 3, 4, 5];for (let i = 0; i < array.length; i++) {console.log(array[i]); }
-
forEach
方法forEach
是数组的方法,用于对每个元素执行回调函数。const array = [1, 2, 3, 4, 5];array.forEach((item) => {console.log(item); });
-
for...of
循环for...of
提供了一个简洁的语法来遍历可迭代对象,包括数组。const array = [1, 2, 3, 4, 5];for (const item of array) {console.log(item); }
-
map
方法map
方法创建一个新数组,其结果是调用提供的函数处理数组中的每个元素后的返回值。const array = [1, 2, 3, 4, 5];const doubled = array.map((item) => item * 2); console.log(doubled); // 输出: [2, 4, 6, 8, 10]
数组的去重
-
使用 Set
Set
是一种数据结构,它只允许存储唯一值。可以利用这一特性来去重数组。const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = [...new Set(array)]; console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
-
filter
方法使用
filter
方法结合indexOf
可以实现数组的去重。const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.filter((item, index) => array.indexOf(item) === index); console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
-
reduce
方法使用
reduce
方法结合一个空数组,可以去重。const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = array.reduce((acc, item) => {if (!acc.includes(item)) {acc.push(item);}return acc; }, []); console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]
-
使用对象作为哈希表
利用对象的键唯一性来去重数组元素。
const array = [1, 2, 2, 3, 4, 4, 5]; const uniqueArray = []; const hashTable = {};for (const item of array) {if (!hashTable[item]) {hashTable[item] = true;uniqueArray.push(item);} }console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]