《如何写出高质量的前端代码》学习笔记
1. 最少知识原则 (Law of Demeter)
1.1 定义
最少知识原则(LOD)强调一个对象应该对其他对象保持最少的了解,只与直接的朋友交谈。这个原则也被称为“迪米特法则”。
1.2 核心思想
-
一个对象应该对其他对象有尽可能少的了解
-
一个类/组件/方法应该只调用:
- 自身的方法
- 参数的方法
- 自身创建对象的方法
- 自身直接依赖对象的方法
1.3 示例
❌ 下面是一个违反最少知识原则的例子:
// 反面示例
class UserService {
getOrderDetails(userId) {
const user = this.userRepository.findById(userId);
// 违反原则:直接访问用户对象内部的订单对象,再访问订单的详情
return user.orders[0].orderDetails;
}
}
✅ 正确示例
// 正确示例
class UserService {
getOrderDetails(userId) {
const user = this.userRepository.findById(userId);
// 通过用户对象提供的方法获取订单详情
return user.getLatestOrderDetails();
}
}
class User {
getLatestOrderDetails() {
if(this.orders.length > 0) {
return this.orders[0].getDetails();
}
return null;
}
}
1.5 在前端开发中的应用
在前端组件开发中,应该:
❌ 避免跨层级访问组件
<!-- 错误示例 -->
<template>
<div>
<child-component ref="child">
<grand-child ref="grandChild"></grand-child>
</child-component>
</div>
</template>
<script>
export default {
methods: {
// 错误:直接访问孙组件
updateGrandChild() {
this.$refs.child.$refs.grandChild.update();
}
}
}
</script>
✅ 正确的组件通信方式
<!-- 正确示例 -->
<template>
<div>
<child-component
@update="handleUpdate"
></child-component>
</div>
</template>
<script>
export default {
methods: {
// 通过事件通信
handleUpdate(data) {
// 处理更新
}
}
}
</script>
2. DRY原则 (Don't Repeat Yourself)
2.1 定义
DRY原则要求系统中的每一个功能都应该有唯一的实现。如果出现重复的代码,就意味着我们需要将其抽象成一个可重用的部分。
2.2 常见的代码重复类型
- 完全重复:相同的代码块在多处出现
- 几乎重复:代码结构相似,只有少量差异
- 语义重复:实现相同功能的不同代码
2.3 示例
❌ 重复代码示例:
// 重复的验证逻辑
function validateEmail(email) {
if(!email) return false;
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(email);
}
function validateUserForm(data) {
if(!data.email) return false;
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(data.email);
}
function validateCompanyForm(data) {
if(!data.contactEmail) return false;
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(data.contactEmail);
}
✅ 改进后:
// 抽取公共验证方法
const validators = {
email(value) {
if(!value) return false;
return /^[^\s@]+@[^\s@]+.[^\s@]+$/.test(value);
}
};
function validateUserForm(data) {
return validators.email(data.email);
}
function validateCompanyForm(data) {
return validators.email(data.contactEmail);
}
2.4 实践建议
- 创建通用组件库
- 提取共用的工具函数
- 使用继承和组合来复用代码
- 建立代码评审机制,及时发现重复代码
3. KISS原则 (Keep It Simple, Stupid)
3.1 定义
KISS原则强调简单性应该是软件设计的核心目标。代码应该简单易懂,而不是复杂晦涩。
3.2 示例
❌ 过度复杂的实现:
// 复杂的实现
function getWeekday(date) {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'];
let day = date.getDay();
return days[((day % 7) + 7) % 7];
}
✅ 简化后的实现:
// 简单的实现
function getWeekday(date) {
const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday'];
return days[date.getDay()];
}
3.3 实践建议
- 避免过度工程化
- 使用清晰的命名
- 保持函数短小精悍
- 避免过早优化
- 及时重构复杂代码
4. YAGNI原则 (You Aren't Gonna Need It)
4.1 定义
YAGNI原则建议开发者不要去实现当前不需要的功能。只实现目前需要的功能,而不是预测性地开发可能用到的功能。
4.2 示例
❌ 过度设计的例子:
// 过度设计的缓存类
class Cache {
constructor() {
this.cache = new Map();
this.maxSize = 1000;
this.timeout = 3600;
this.strategy = 'LRU';
// ... 更多复杂的配置
}
set(key, value, timeout, strategy) {
// 复杂的缓存策略实现
}
get(key) {
// 复杂的获取逻辑
}
}
✅ 符合YAGNI的简单实现:
// 简单的缓存实现
class Cache {
constructor() {
this.cache = new Map();
}
set(key, value) {
this.cache.set(key, value);
}
get(key) {
return this.cache.get(key);
}
}
4.3 实践建议
- 专注于当前需求
- 避免过度抽象
- 在需要时才添加功能
- 保持代码简单直接
5. SOLID原则
5.1 单一职责原则 (SRP)
每个类或模块应该只有一个改变的理由。
示例:
❌ 违反SRP的例子
// 违反SRP的例子
class UserManager {
createUser() { /* ... */ }
validateEmail() { /* ... */ }
sendEmail() { /* ... */ }
generateReport() { /* ... */ }
}
✅ 符合SRP的设计
// 符合SRP的设计
class UserManager {
createUser() { /* ... */ }
updateUser() { /* ... */ }
}
class EmailService {
validateEmail() { /* ... */ }
sendEmail() { /* ... */ }
}
class ReportGenerator {
generateReport() { /* ... */ }
}
5.2 开闭原则 (OCP)
软件实体应该对扩展开放,对修改关闭。
示例:
// 支持扩展的通知系统
class Notifier {
constructor(strategy) {
this.strategy = strategy;
}
send(message) {
return this.strategy.send(message);
}
}
class EmailStrategy {
send(message) {
// 发送邮件
}
}
class SMSStrategy {
send(message) {
// 发送短信
}
}
// 扩展新的通知方式不需要修改现有代码
class PushStrategy {
send(message) {
// 发送推送
}
}
5.3 里氏替换原则 (LSP)
子类必须能够替换其基类。
示例:
interface Shape {
getArea(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
getArea(): number {
return this.width * this.height;
}
}
class Square implements Shape {
constructor(private size: number) {}
getArea(): number {
return this.size * this.size;
}
}
// 任何使用Shape的地方都可以使用Rectangle或Square
function printArea(shape: Shape) {
console.log(shape.getArea());
}
5.4 接口隔离原则 (ISP)
客户端不应该依赖它不需要的接口。
示例:
// 违反ISP的接口
interface Worker {
work(): void;
eat(): void;
}
// 符合ISP的接口拆分
interface Workable {
work(): void;
}
interface Eatable {
eat(): void;
}
interface Sleepable {
sleep(): void;
}
class Human implements Workable, Eatable, Sleepable {
work() { /* ... */ }
eat() { /* ... */ }
sleep() { /* ... */ }
}
class Robot implements Workable {
work() { /* ... */ }
}
5.5 依赖倒置原则 (DIP)
高层模块不应该依赖低层模块,两者都应该依赖于抽象。
示例:
// 依赖抽象而不是具体实现
interface Database {
save(data: any): void;
delete(id: string): void;
}
class MongoDB implements Database {
save(data: any) { /* ... */ }
delete(id: string) { /* ... */ }
}
class MySQL implements Database {
save(data: any) { /* ... */ }
delete(id: string) { /* ... */ }
}
// UserService依赖抽象的Database接口
class UserService {
constructor(private db: Database) {}
createUser(userData: any) {
this.db.save(userData);
}
}
6. 高内聚低耦合原则
6.1 定义
- 高内聚:模块内部的元素应该紧密关联
- 低耦合:模块之间的依赖应该最小化
6.2 示例
低内聚的组件:
<!-- 违反高内聚原则的组件 -->
<template>
<div>
<!-- 用户管理相关 -->
<user-list></user-list>
<user-form></user-form>
<!-- 订单管理相关 -->
<order-list></order-list>
<order-form></order-form>
<!-- 商品管理相关 -->
<product-list></product-list>
<product-form></product-form>
</div>
</template>
高内聚的组件设计:
<!-- 用户管理组件 -->
<template>
<div>
<user-list></user-list>
<user-form></user-form>
</div>
</template>
<!-- 订单管理组件 -->
<template>
<div>
<order-list></order-list>
<order-form></order-form>
</div>
</template>
<!-- 商品管理组件 -->
<template>
<div>
<product-list></product-list>
<product-form></product-form>
</div>
</template>
6.3 实践建议
- 将相关的功能组织在一起
- 减少组件之间的直接依赖
- 使用事件总线或状态管理来处理组件通信
- 定期重构提高内聚性
- 使用依赖注入降低耦合
总结
这些编程原则不是硬性规定,而是指导方针。在实际开发中需要根据具体情况灵活运用,找到合适的平衡点。好的代码设计应该是:
- 容易理解
- 容易维护
- 容易测试
- 容易扩展
- 可重用性高