前端开发规范
一、代码风格
1. 缩进
- 统一使用2个空格缩进,避免使用Tab键,确保代码在不同编辑器和开发环境中的显示一致性。
Vue模板中使用2个空格缩进,保持与JavaScript一致。 - 示例:
<script setup>
function fetchData() {
return axios.get('/api/data')
.then(response => {
return response.data;
})
.catch(error => {
console.error('Error fetching data:', error);
});
}
</script>
2.大括号使用
- 对于控制语句(如if、for、while等)和函数声明,即使只有一行代码,也必须使用大括号包裹,增强代码的可读性和可维护性。
- 大括号位于语句行的末尾,左大括号({)与语句行在同一行,右大括号(})单独占一行。
- 示例:
if (condition) {
executeSomeLogic();
} else {
executeAlternativeLogic();
}
3.括号使用
- 函数调用和定义时,始终使用括号,即使没有参数,也保留空括号,明确表示这是一个函数调用或定义。
- 示例:
function logMessage() {
console.log('Message logged');
}
logMessage();
4.逗号使用
- 对象或数组定义时,最后一个元素后不加多余的逗号,避免潜在的解析错误和兼容性问题。
- 示例:
const person = {
name: 'John Doe',
age: 30,
occupation: 'Developer' // 没有多余的逗号
};
const numbers = [1, 2, 3]; // 没有多余的逗号
5.引号使用
- 字符串优先使用单引号(')进行包裹,只有在字符串中包含单引号字符时,才使用双引号(")。
- 示例:
const greeting = 'Hello, World!';
const quote = "Don't worry, be happy!";
二、命名规范
1.变量命名
- 采用驼峰命名法(camelCase),即除首个单词外,每个单词的首字母大写,其余字母小写,无下划线或其他分隔符。
- 变量名应简洁明了,准确反映其存储的数据内容和用途,避免使用模糊、无意义的名称,如temp、data等。
- 示例:
// 正确示例
let userName = 'John Doe';
let userAge = 30;
let isLoggedIn = true;
// 错误示例
let uName = 'John Doe'; // 变量名不够清晰
let data = 30; // 变量名过于模糊
let flag = true; // 变量名不够具体
2.常量命名
- 常量使用全大写字母,单词之间用下划线(_)分隔,明确标识该变量的值不会改变。
- 示例:
const API_URL = '<https://api.example.com>';
const MAX_LIMIT = 100;
const DAYS_IN_WEEK = 7;
3.函数命名
- 函数名采用驼峰命名法,且函数名应具有动词性质,能准确体现函数的功能和作用,如getUserInfo、submitForm、calculateTotal等。
- 示例:
// 正确示例
function fetchUserData(userId) {
// 获取用户数据的逻辑
}
function validateFormInputs(form) {
// 验证表单输入的逻辑
}
// 错误示例
function ud(userId) { // 函数名不够清晰
// 获取用户数据的逻辑
}
function vf(form) { // 函数名不够清晰
// 验证表单输入的逻辑
}
4.类命名
- 类名采用帕斯卡命名法(PascalCase),即每个单词的首字母大写,无下划线或其他分隔符,类名应能体现该类的职责和功能。
- 示例:
class UserManager {
// 用户管理类的逻辑
}
class DataProcessor {
// 数据处理类的逻辑
}
三、注释规范
1.单行注释
- 在代码的单行注释中,在注释内容前添加空格,提高注释的可读性,注释内容应简洁明了,解释代码的功能、目的或注意事项。
- 示例:
// 用户名验证
if(userName === 'admin') {
}
// 用户名验证正则表达式,规则为:长度4-20位,只能包含字母、数字和下划线
const usernameRegex = /^[a-zA-Z0-9_]{4,20}$/;
2.多行注释
- 对于多行注释,使用/.../包裹,每行注释内容前添加空格,并在注释的开头和结尾留出一行空行,使注释区域清晰易读,用于解释较复杂的逻辑或算法。
- 示例:
/*
* 用户管理类
* 负责用户数据的增删改查操作。
*/
class UserManager {
async getUsers() {
// 获取用户列表
}
async addUser(user) {
// 添加用户
}
async deleteUser(userId) {
// 删除用户
}
}
/*
* 定义用户资料卡片的样式。
* 包括背景颜色、边框、圆角和内边距。
*/
.user-profile {
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
}
3.函数注释
- 在每个函数上方添加注释,说明函数的功能、参数、返回值和可能的异常情况,便于其他开发人员理解和使用该函数,特别是对于公共函数或库函数。
- 示例:
/**
* 格式化日期为指定的字符串格式
* @param {Date} date - 要格式化的日期对象
* @param {string} format - 目标日期格式,如'yyyy-MM-dd', 'MM/dd/yyyy'等
* @returns {string} 格式化后的日期字符串
* @throws {Error} 如果format参数格式不正确或不支持
*/
function formatDate(date, format) {
// 格式化日期的逻辑
}
四、组件开发规范
1.组件命名
- 组件名采用帕斯卡命名法,体现组件的功能和用途,确保组件名在整个项目中具有唯一性,避免与其他组件或全局元素冲突。
- 示例:
<!-- 正确示例:UserCard.vue -->
<template>
<div class="user-card">
<img :src="user.avatar" :alt="user.name" class="user-avatar" />
<h3 class="user-name">{{ user.name }}</h3>
<p class="user-info">{{ user.info }}</p>
</div>
</template>
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
user: {
type: Object,
required: true
}
});
</script>
<style scoped>
.user-card {
/* 样式定义 */
}
</style>
2.组件结构
- 组件代码结构清晰,包含script(定义逻辑)、template(描述模板)、style(定义样式)部分,各部分之间使用明确的分隔线(如注释)区分,提高代码的可读性和可维护性。
- 示例:
<template>
<!-- 组件模板部分 -->
<div class="login-form">
<h2>{{ onLogin }}</h2>
</div>
</template>
<script setup>
// props
const props = defineProps({
onLogin: {
type: string,
default: '登录',
required: true
}
});
</script>
<style scoped>
/* 组件样式部分 */
.login-form {
max-width: 400px;
margin: 0 auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
</style>
3.组件交互
3.1 父子组件通信
- 父组件向子组件传递数据通过props,子组件需对props进行类型验证和必要性检查,确保接收到的数据符合预期。
- 子组件向父组件传递事件通过emits,父组件监听子组件触发的事件,执行相应的回调函数。
- 示例:
<!-- 父组件:ParentComponent.vue -->
<template>
<div>
<child-component :user="selectedUser" @update-user-info="handleUserInfoUpdate" />
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue';
import { ref } from 'vue';
const selectedUser = ref({
id: 1,
name: 'John Doe',
email: 'john@example.com'
});
const handleUserInfoUpdate = (updatedUser) => {
// 处理用户信息更新的逻辑
console.log('User info updated:', updatedUser);
selectedUser.value = updatedUser;
};
</script>
<!-- 子组件:ChildComponent.vue -->
<template>
<div>
<h3>User Information</h3>
<p>Name: {{ user.name }}</p>
<p>Email: {{ user.email }}</p>
<button @click="updateUserInfo">Update Info</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
user: {
type: Object,
required: true
}
});
const emit = defineEmits(['update-user-info']);
const updateUserInfo = () => {
const updatedUser = {
...props.user,
name: 'Updated Name',
email: 'updated@example.com'
};
emit('update-user-info', updatedUser); // 触发自定义事件,将更新后的用户信息传递给父组件
};
</script>
3.2 兄弟组件通信
- 通过共同的祖先组件或状态管理库(如Pinia)进行通信,避免直接的操作和数据共享,确保组件之间的解耦和数据的一致性。
4.组件性能优化
避免不必要的渲染
在组件中,合理使用computed属性和watch监听器,只在必要的时候触发组件的重新渲染,避免因数据频繁变化导致的性能问题。使用keep-alive缓存组件
对频繁切换但内容不经常变化的组件,使用<keep-alive>标签进行缓存,减少组件的销毁和重新创建的开销,提升用户体验。示例:
<template>
<div>
<button @click="activeTab = 'TabA'">Tab A</button>
<button @click="activeTab = 'TabB'">Tab B</button>
<button @click="activeTab = 'TabC'">Tab C</button>
<keep-alive>
<component :is="activeTabComponent" />
</keep-alive>
</div>
</template>
<script setup>
import TabA from './TabA.vue';
import TabB from './TabB.vue';
import TabC from './TabC.vue';
import { ref, computed } from 'vue';
const activeTab = ref('TabA');
const activeTabComponent = computed(() => {
return activeTab.value;
});
</script>
五、CSS 规范
1.命名规范
- 采用有意义的名称:CSS 类名应能直观地反映其样式的作用和用途,避免使用无意义的名称,如div1、box等,同时避免过度依赖元素的外观特征命名,如red-box、big-button,因为样式可能会随需求变化而改变。
- 使用局部命名空间:对于组件内部的样式,采用基于组件名的命名空间,避免样式污染和冲突,增强样式的可维护性和可复用性。
- 示例:
/* 错误示例 */
.red-button {
background-color: red;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 正确示例 */
.primary-button {
background-color: #4CAF50;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
/* 基于组件名的命名空间示例 */
.user-card {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 16px;
margin-bottom: 16px;
}
.user-card-avatar {
width: 64px;
height: 64px;
border-radius: 50%;
object-fit: cover;
}
2.样式组织
采用BEM命名方法(可选)
BEM(Block, Element, Modifier)是一种流行的CSS命名方法,有助于创建可复用的、一致的CSS样式结构。Block表示独立的、可复用的模块;Element表示Block内的组成部分,其类名以Block名称作为前缀,并用两个下划线(__)分隔;Modifier表示Element的不同状态或样式变体,其类名以Element名称作为前缀,并用两个连字符(--)分隔。使用CSS预处理器(Less)
利用Less提供的变量、混合、函数等功能,实现样式的复用和灵活管理,提高样式的可维护性。示例:
// 变量定义
@primary-color: #4CAF50;
@secondary-color: #2196F3;
@border-radius: 4px;
@box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
// 混合定义
.mixin-box-shadow(@shadow: @box-shadow) {
box-shadow: @shadow;
}
// 样式定义
.primary-button {
background-color: @primary-color;
color: white;
padding: 8px 16px;
border: none;
border-radius: @border-radius;
cursor: pointer;
.mixin-box-shadow();
}
.secondary-button {
background-color: @secondary-color;
color: white;
padding: 8px 16px;
border: none;
border-radius: @border-radius;
cursor: pointer;
.mixin-box-shadow();
}
3.样式顺序
- 在定义样式属性时,按照一定的顺序排列,通常遵循以下顺序:
定位(position、top、right、bottom、left、z-index等)
自身尺寸(width、height、min-width、max-width、min-height、max-height等)
内外边距(margin、padding等)
定义宽高(border、border-radius等)
背景(background-color、background-image等)
文本(color、font-size、font-weight、line-height、text-align等)
其他(transition、transform、cursor等) - 示例:
.card {
position: relative;
width: 300px;
height: 200px;
margin: 20px auto;
padding: 16px;
border: 1px solid #ddd;
border-radius: 8px;
background-color: white;
color: #333;
font-size: 16px;
line-height: 1.5;
text-align: left;
transition: transform 0.3s ease;
cursor: pointer;
}
.card:hover {
transform: translateY(-5px);
}
4.媒体查询
- 对于移动端适配,使用媒体查询(media queries)定义不同屏幕尺寸下的样式,确保页面在各种设备上都能正常显示和操作。
- 示例:
/* 桌面端样式 */
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 16px;
}
/* 平板端样式 */
@media (max-width: 1024px) {
.container {
max-width: 800px;
}
}
/* 移动端样式 */
@media (max-width: 768px) {
.container {
max-width: 100%;
padding: 0 12px;
}
}
六、文件名规范
1.组件文件
- 组件文件名采用帕斯卡命名法,与组件名保持一致,文件后缀名为.vue。
- 示例:
// 正确示例
UserCard.vue
LoginForm.vue
HomePage.vue
// 错误示例
user-card.vue
login_form.vue
home-page.vue
2.JS 文件
- JS 文件名采用驼峰命名法,文件名应能体现文件的内容和功能,文件后缀名为.js。
- 示例:
// 正确示例
userData.js
formValidation.js
apiService.js
// 错误示例
user-data.js
form-validation.js
api-service.js
3.CSS 文件
- CSS 文件名采用驼峰命名法或短横线命名法,文件名应能体现样式的作用域和功能,文件后缀名为.less(使用 Less 预处理器时)或.css。
- 示例:
/* 正确示例(驼峰命名法) */
globalStyles.less
componentStyles.css
themeVariables.less
/* 正确示例(短横线命名法) */
global-styles.less
component-styles.css
theme-variables.less
/* 错误示例 */
global_styles.less
component_styles.css
theme_variables.less
4.图片文件
- 图片文件名采用短横线命名法或下划线命名法,文件名简洁明了,体现图片的内容和用途,文件格式常用.png、.jpg、.jpeg、.gif、.svg 等。
- 示例:
<!-- 正确示例(短横线命名法) -->
<img src="images/user-avatar.png" alt="User Avatar" />
<img src="images/logo.svg" alt="Logo" />
<!-- 正确示例(下划线命名法) -->
<img src="images/user_avatar.png" alt="User Avatar" />
<img src="images/logo.svg" alt="Logo" />
<!-- 错误示例 -->
<img src="images/useravatar.png" alt="User Avatar" />
<img src="images/logo-svg.svg" alt="Logo" />
七、JS 编程规范
1.变量声明
- 使用const或let声明变量:避免使用var,因为它会产生变量提升和作用域污染等问题。使用const声明不变的常量,使用let声明可变的变量。
- 变量使用前必须声明:避免使用未声明的变量,防止潜在的错误和难以排查的bug。
- 示例:
// 使用const声明常量
const API_URL = 'https://api.example.com';
const MAX_LIMIT = 100;
// 使用let声明变量
let count = 0;
let userLoggedIn = false;
// 错误示例(使用var)
var userName = 'John Doe';
var itemCount = 5;
// 变量使用前必须声明
let userInput;
userInput = prompt('Enter your name');
console.log('Hello, ' + userInput);
// 错误示例(变量使用前未声明)
console.log(userInput);
userInput = prompt('Enter your name');
2.对象和数组
- 对象创建和解构
使用对象字面量创建对象,利用对象解构赋值提取对象的属性值,提高代码的可读性和简洁性。 - 示例:
// 创建对象
const user = {
id: 1,
name: 'John Doe',
age: 30,
email: 'john@example.com'
};
// 解构对象
const { id, name, age, email } = user;
console.log(`User ID: ${id}, Name: ${name}, Age: ${age}, Email: ${email}`);
- 数组操作
使用数组的内置方法(如map、filter、reduce等)操作数组,避免使用循环和下标直接操作数组元素,使代码更简洁、优雅。 - 示例:
// 使用map方法处理数组
const numbers = [1, 2, 3, 4, 5];
const doubledNumbers = numbers.map(number => number * 2);
console.log(doubledNumbers); // 输出:[2, 4, 6, 8, 10]
// 使用filter方法过滤数组
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // 输出:[2, 4]
// 使用reduce方法计算数组的和
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出:15
3.函数
- 函数表达式和箭头函数
优先使用函数表达式和箭头函数定义函数,避免使用函数声明语句,特别是对于回调函数和简短的函数处理。 - 示例:
// 函数表达式
const greet = function(name) {
console.log(`Hello, ${name}!`);
};
greet('John');
// 箭头函数
const square = x => x * x;
console.log(square(5)); // 输出:25
const add = (a, b) => a + b;
console.log(add(3, 7)); // 输出:10
- 函数参数和默认值
函数参数数量尽量控制在合理范围内,避免过多参数导致函数难以维护和理解。使用函数参数的默认值简化函数调用。 - 示例:
// 函数参数过多的示例(不推荐)
function createPerson(firstName, lastName, age, gender, occupation) {
// 创建人物对象的逻辑
}
// 使用对象作为参数的示例(推荐)
function createPerson({ firstName, lastName, age, gender, occupation }) {
// 创建人物对象的逻辑
}
// 调用函数
createPerson({
firstName: 'John',
lastName: 'Doe',
age: 30,
gender: 'male',
occupation: 'developer'
});
// 函数参数默认值示例
function multiply(a, b = 1) {
return a * b;
}
console.log(multiply(5)); // 输出:5
console.log(multiply(5, 3)); // 输出:15
4.异步编程
- 使用async/await
对于异步操作,优先使用async/await语法,使异步代码的书写更接近同步代码,提高代码的可读性和可维护性。 - 示例:
// 使用async/await获取用户数据
async function fetchUserData(userId) {
try {
const response = await axios.get(`/api/users/${userId}`);
return response.data;
} catch (error) {
console.error('Error fetching user data:', error);
throw error;
}
}
// 调用异步函数
async function displayUserData(userId) {
try {
const userData = await fetchUserData(userId);
console.log('User Data:', userData);
} catch (error) {
console.error('Failed to display user data:', error);
}
}
displayUserData(1);
- Promise 使用
对于不支持async/await的场景或兼容性要求较高的情况,使用Promise进行异步编程,确保异步操作的正确处理和错误捕获。 - 示例:
// 使用Promise获取用户数据
function fetchUserDataPromise(userId) {
return new Promise((resolve, reject) => {
axios.get(`/api/users/${userId}`)
.then(response => {
resolve(response.data);
})
.catch(error => {
reject(error);
});
});
}
// 调用Promise
fetchUserDataPromise(1)
.then(userData => {
console.log('User Data:', userData);
})
.catch(error => {
console.error('Error fetching user data:', error);
});
5.防御性编程
- 判空处理
在操作对象、数组、字符串等数据类型时,进行判空处理,避免因数据为空导致的运行时错误。 - 示例:
// 对象判空
function getUserRole(user) {
if (!user) {
console.warn('User object is null or undefined');
return null;
}
return user.role || 'guest';
}
// 对象判空,链式调用
function getUserRole(user) {
return user?.role || 'guest';
}
// 数组判空
function getFirstItem(items) {
if (!items || !items.length) {
console.warn('Items array is empty or not an array');
return null;
}
return items[0];
}
// 字符串判空
function displayMessage(message) {
if (!message || message.trim() === '') {
console.warn('Message is empty or contains only whitespace');
return 'No message to display';
}
return message;
}
// 函数调用示例
const user = {
name: 'John Doe',
role: 'admin'
};
console.log(getUserRole(user)); // 输出:'admin'
const items = ['apple', 'banana', 'orange'];
console.log(getFirstItem(items)); // 输出:'apple'
console.log(displayMessage('Hello, World!')); // 输出:'Hello, World!'
console.log(displayMessage('')); // 输出:'No message to display'
- 类型检查
在函数参数和关键变量处进行类型检查,确保数据类型符合预期,避免因类型错误导致的逻辑问题。 - 示例:
// 函数参数类型检查
function calculateTotal(prices) {
if (!Array.isArray(prices)) {
throw new TypeError('Expected prices to be an array');
}
if (!prices.every(price => typeof price === 'number')) {
throw new TypeError('All elements in prices array must be numbers');
}
return prices.reduce((total, price) => total + price, 0);
}
// 调用函数示例
try {
const prices = [10, 20, 30];
const total = calculateTotal(prices);
console.log('Total:', total); // 输出:60
} catch (error) {
console.error('Error calculating total:', error.message);
}
// 错误调用示例
try {
const invalidPrices = [10, '20', 30];
calculateTotal(invalidPrices);
} catch (error) {
console.error('Error calculating total:', error.message); // 输出:All elements in prices array must be numbers
}
- 异常处理
使用try/catch语句捕获和处理可能发生的异常,防止程序因未处理的异常而崩溃,提高程序的健壮性和用户体验。 - 示例:
// 使用try/catch处理异常
try {
const result = divide(10, 0);
console.log('Result:', result);
} catch (error) {
console.error('Error:', error.message);
}
/**
* 除法函数
* @param {number} a - 被除数
* @param {number} b - 除数
* @returns {number} - 结果
*/
function divide(a, b) {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}