MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript中的设计模式与架构思想

2021-07-302.1k 阅读

设计模式概述

设计模式是在软件开发过程中,针对反复出现的问题所总结归纳出的通用解决方案。它们就像是建筑师手中的蓝图,帮助开发者构建出更灵活、可维护且易于扩展的软件系统。在JavaScript中,设计模式同样扮演着至关重要的角色,尤其在处理复杂业务逻辑和构建大型应用时,合理运用设计模式能显著提升代码质量。

设计模式的出现是为了解决软件开发中的一些常见问题,比如代码的耦合度高、可维护性差、复用性低等。以一个简单的网页交互为例,如果没有合适的设计模式,随着功能的不断增加,代码可能会变得混乱不堪,各个功能模块之间相互依赖,牵一发而动全身。而通过设计模式,我们可以将不同的功能进行合理拆分,使每个模块职责明确,降低模块间的耦合度,从而提高代码的整体质量。

JavaScript中的设计模式分类

JavaScript中的设计模式大致可分为创建型、结构型和行为型三大类。

  1. 创建型设计模式
    • 单例模式:保证一个类仅有一个实例,并提供一个全局访问点。在JavaScript中,由于没有类的概念(ES6之前),单例模式的实现方式略有不同。我们可以通过对象字面量或者闭包来模拟单例。
// 使用对象字面量实现单例
let Singleton = {
    data: '初始数据',
    method: function() {
        console.log('执行单例方法');
    }
};
// 全局访问点
console.log(Singleton.data);
Singleton.method();
- **工厂模式**:用于创建对象的一种设计模式,它将对象的创建和使用分离。在JavaScript中,我们可以通过函数来实现工厂模式。
function createUser(name, age) {
    return {
        name: name,
        age: age,
        sayHello: function() {
            console.log(`你好,我是${this.name},今年${this.age}岁。`);
        }
    };
}
let user1 = createUser('张三', 25);
user1.sayHello();
  1. 结构型设计模式
    • 代理模式:为其他对象提供一种代理以控制对这个对象的访问。在JavaScript中,代理模式常用于懒加载、缓存等场景。例如,我们可以通过代理对象来延迟加载图片。
function ImageLoader(url) {
    this.url = url;
    this.load = function() {
        console.log(`正在加载图片:${this.url}`);
    };
}
function ImageProxy(imageLoader) {
    this.imageLoader = imageLoader;
    this.isLoaded = false;
    this.show = function() {
        if (!this.isLoaded) {
            this.imageLoader.load();
            this.isLoaded = true;
        }
        console.log('显示图片');
    };
}
let imageLoader = new ImageLoader('https://example.com/image.jpg');
let imageProxy = new ImageProxy(imageLoader);
imageProxy.show();
- **装饰器模式**:动态地给一个对象添加一些额外的职责。在JavaScript中,可以通过函数或者类的方式来实现装饰器模式。
function Logger(target) {
    let originalMethod = target.sayHello;
    target.sayHello = function() {
        console.log('开始执行sayHello方法');
        originalMethod.apply(this, arguments);
        console.log('sayHello方法执行完毕');
    };
    return target;
}
function Person(name) {
    this.name = name;
    this.sayHello = function() {
        console.log(`你好,我是${this.name}`);
    };
}
let person = new Person('李四');
let loggedPerson = Logger(person);
loggedPerson.sayHello();
  1. 行为型设计模式
    • 观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并被自动更新。在JavaScript中,我们可以通过自定义事件来实现观察者模式。
class Subject {
    constructor() {
        this.observers = [];
    }
    attach(observer) {
        this.observers.push(observer);
    }
    detach(observer) {
        this.observers = this.observers.filter(obs => obs!== observer);
    }
    notify() {
        this.observers.forEach(observer => observer.update());
    }
}
class Observer {
    constructor(name) {
        this.name = name;
    }
    update() {
        console.log(`${this.name}收到通知`);
    }
}
let subject = new Subject();
let observer1 = new Observer('观察者1');
let observer2 = new Observer('观察者2');
subject.attach(observer1);
subject.attach(observer2);
subject.notify();
- **策略模式**:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。在JavaScript中,策略模式常用于表单验证等场景。
const strategies = {
    isNonEmpty: function(value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    }
};
class Validator {
    constructor() {
        this.cache = [];
    }
    add(value, strategy, ...args) {
        let errorMsg = args.pop();
        this.cache.push(() => {
            return strategies[strategy].apply(this, [value, ...args, errorMsg]);
        });
    }
    start() {
        for (let validator of this.cache) {
            let errorMsg = validator();
            if (errorMsg) {
                return errorMsg;
            }
        }
    }
}
let validator = new Validator();
validator.add('用户名', 'isNonEmpty', '用户名不能为空');
validator.add('密码','minLength', 6, '密码长度不能小于6位');
let error = validator.start();
console.log(error);

JavaScript架构思想

  1. 模块化架构思想
    • 模块化的概念:将一个大型的JavaScript应用拆分成多个小的、相互独立的模块,每个模块只负责特定的功能。这样可以提高代码的可维护性和复用性。在JavaScript中,早期并没有官方的模块化标准,开发者们通过各种方式来模拟模块化,比如使用立即执行函数表达式(IIFE)。
// 使用IIFE模拟模块化
let module1 = (function() {
    let privateVariable = '私有变量';
    function privateFunction() {
        console.log('私有函数');
    }
    return {
        publicMethod: function() {
            console.log(privateVariable);
            privateFunction();
        }
    };
})();
module1.publicMethod();
- **ES6模块**:ES6引入了官方的模块化标准,使用`import`和`export`关键字来实现模块的导入和导出。
// 导出模块
export const name = '模块导出的变量';
export function sayHello() {
    console.log('模块导出的函数');
}
// 导入模块
import { name, sayHello } from './module.js';
console.log(name);
sayHello();
  1. MVC架构思想
    • MVC概述:MVC即Model - View - Controller,是一种软件架构模式。在JavaScript应用中,Model负责处理数据和业务逻辑,View负责展示数据,Controller负责接收用户输入,协调Model和View之间的交互。以一个简单的待办事项应用为例:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF - 8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MVC示例</title>
</head>

<body>
    <input type="text" id="newTodoInput">
    <button id="addTodoButton">添加待办事项</button>
    <ul id="todoList"></ul>
    <script>
        // Model
        const todoModel = {
            todos: [],
            addTodo: function(todo) {
                this.todos.push(todo);
            },
            getTodos: function() {
                return this.todos;
            }
        };
        // View
        const todoView = {
            displayTodos: function(todos) {
                const todoList = document.getElementById('todoList');
                todoList.innerHTML = '';
                todos.forEach((todo, index) => {
                    const li = document.createElement('li');
                    li.textContent = todo;
                    todoList.appendChild(li);
                });
            }
        };
        // Controller
        const todoController = {
            init: function() {
                const addTodoButton = document.getElementById('addTodoButton');
                const newTodoInput = document.getElementById('newTodoInput');
                addTodoButton.addEventListener('click', () => {
                    const newTodo = newTodoInput.value;
                    if (newTodo) {
                        todoModel.addTodo(newTodo);
                        const todos = todoModel.getTodos();
                        todoView.displayTodos(todos);
                        newTodoInput.value = '';
                    }
                });
            }
        };
        todoController.init();
    </script>
</body>

</html>
  1. MVVM架构思想
    • MVVM概述:MVVM即Model - View - ViewModel,它是在MVC基础上发展而来的。ViewModel作为View和Model之间的桥梁,实现了数据的双向绑定。在JavaScript中,Vue.js是典型的MVVM框架。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF - 8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>MVVM示例</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">
        <input v-model="newTodo">
        <button @click="addTodo">添加待办事项</button>
        <ul>
            <li v - for="(todo, index) in todos" :key="index">{{todo}}</li>
        </ul>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data: {
                newTodo: '',
                todos: []
            },
            methods: {
                addTodo: function() {
                    if (this.newTodo) {
                        this.todos.push(this.newTodo);
                        this.newTodo = '';
                    }
                }
            }
        });
    </script>
</body>

</html>

在上述Vue.js的示例中,data中的数据就是Model,模板部分就是View,Vue实例本身相当于ViewModel。通过v - model指令实现了数据的双向绑定,用户在输入框中输入内容,会自动更新到newTodo变量中,而newTodo变量的变化又会实时反映在输入框中。点击按钮添加待办事项时,todos数组更新,视图也会自动更新。

设计模式与架构思想的结合应用

  1. 在模块化架构中应用设计模式 在模块化开发中,单例模式可以用于确保某些模块只被实例化一次,避免重复创建导致的资源浪费。例如,一个全局的配置模块可以使用单例模式实现。
// 配置模块
let configSingleton = (function() {
    let instance;
    function createInstance() {
        let config = {
            apiUrl: 'https://example.com/api',
            defaultLanguage: 'zh - CN'
        };
        return config;
    }
    return {
        getInstance: function() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();
let config1 = configSingleton.getInstance();
let config2 = configSingleton.getInstance();
console.log(config1 === config2); // true
  1. 在MVC架构中应用设计模式 在MVC架构中,观察者模式可以用于实现Model和View之间的解耦。当Model中的数据发生变化时,通过观察者模式通知View进行更新。
// 继续以上面的待办事项应用为例
// 改进Model,添加观察者模式相关代码
class TodoModel {
    constructor() {
        this.todos = [];
        this.observers = [];
    }
    addTodo(todo) {
        this.todos.push(todo);
        this.notify();
    }
    getTodos() {
        return this.todos;
    }
    attach(observer) {
        this.observers.push(observer);
    }
    detach(observer) {
        this.observers = this.observers.filter(obs => obs!== observer);
    }
    notify() {
        this.observers.forEach(observer => observer.update());
    }
}
// 改进View,实现update方法
class TodoView {
    constructor(model) {
        this.model = model;
        this.model.attach(this);
    }
    update() {
        const todoList = document.getElementById('todoList');
        todoList.innerHTML = '';
        const todos = this.model.getTodos();
        todos.forEach((todo, index) => {
            const li = document.createElement('li');
            li.textContent = todo;
            todoList.appendChild(li);
        });
    }
}
// Controller保持不变
class TodoController {
    constructor(model, view) {
        this.model = model;
        this.view = view;
        this.init();
    }
    init() {
        const addTodoButton = document.getElementById('addTodoButton');
        const newTodoInput = document.getElementById('newTodoInput');
        addTodoButton.addEventListener('click', () => {
            const newTodo = newTodoInput.value;
            if (newTodo) {
                this.model.addTodo(newTodo);
                newTodoInput.value = '';
            }
        });
    }
}
let model = new TodoModel();
let view = new TodoView(model);
let controller = new TodoController(model, view);
  1. 在MVVM架构中应用设计模式 在MVVM架构中,装饰器模式可以用于对ViewModel中的方法进行增强。例如,在Vue.js的项目中,我们可以通过装饰器来添加日志功能。
// 假设我们使用Babel插件来支持装饰器语法
function Logger(target, name, descriptor) {
    let originalMethod = descriptor.value;
    descriptor.value = function() {
        console.log(`开始执行${name}方法`);
        originalMethod.apply(this, arguments);
        console.log(`${name}方法执行完毕`);
    };
    return descriptor;
}
export default {
    data() {
        return {
            newTodo: '',
            todos: []
        };
    },
    methods: {
        @Logger
        addTodo() {
            if (this.newTodo) {
                this.todos.push(this.newTodo);
                this.newTodo = '';
            }
        }
    }
};

通过上述方式,我们可以将设计模式与不同的架构思想相结合,充分发挥它们的优势,构建出更加健壮、灵活和可维护的JavaScript应用程序。无论是小型的网页应用还是大型的单页应用(SPA),合理运用设计模式和架构思想都是提升开发效率和代码质量的关键。在实际开发中,开发者需要根据项目的具体需求和规模,选择合适的设计模式和架构方案,以达到最佳的开发效果。同时,不断学习和实践新的设计模式和架构思想,也是跟上技术发展潮流,提升自身技术能力的重要途径。

在JavaScript的生态系统中,还有许多其他相关的概念和技术与设计模式和架构思想紧密相连。例如,面向对象编程(OOP)的特性,如封装、继承和多态,与设计模式的实现和架构的构建有着千丝万缕的关系。在实现某些设计模式时,需要利用OOP的特性来确保代码的合理性和高效性。

另外,随着JavaScript框架和库的不断发展,如React、Angular等,它们在架构设计上也都融入了各种设计模式和架构思想。React使用了组件化的思想,这与模块化架构以及一些设计模式(如组合模式)有着相似之处。通过将页面拆分成多个小组件,每个组件都有自己独立的状态和逻辑,实现了代码的复用和可维护性。Angular则采用了依赖注入(Dependency Injection)等设计模式相关的概念,来管理组件之间的依赖关系,提高代码的可测试性和可扩展性。

在实际项目中,还需要考虑性能优化与设计模式和架构思想的关系。某些设计模式,如代理模式,可以用于实现懒加载,从而提升应用的性能。在架构层面,合理的分层和模块划分也有助于减少不必要的计算和数据传输,提高整体性能。例如,在一个前后端分离的项目中,通过合理设计API接口和前端数据请求的架构,可以避免过多的数据冗余和无效请求,提升用户体验。

同时,在团队协作开发中,统一的设计模式和架构规范尤为重要。它可以使得团队成员之间的代码风格更加一致,便于理解和维护彼此的代码。例如,规定在特定场景下使用某种设计模式,或者统一采用某种架构风格(如MVC或MVVM),可以减少沟通成本,提高开发效率。并且,当项目进行扩展或重构时,遵循统一规范的代码更容易进行修改和升级。

此外,随着JavaScript在服务端(Node.js)的广泛应用,设计模式和架构思想在服务端开发中也同样重要。在构建大型的Node.js应用时,如Web服务器、微服务等,需要运用各种设计模式来解决诸如资源管理、请求处理、模块通信等问题。例如,在处理高并发请求时,可以使用策略模式来选择不同的请求处理策略,以优化服务器性能。

在JavaScript的测试领域,设计模式和架构思想也有着应用场景。例如,在单元测试中,通过模拟对象(Mock Object)来实现对依赖的隔离,这与代理模式的思想类似。通过创建模拟对象来代替真实的依赖对象,可以使得测试更加独立和可控,从而提高测试的准确性和可靠性。

总之,JavaScript中的设计模式与架构思想贯穿于整个开发过程,从项目的初始架构设计到具体功能的实现,再到代码的维护和优化,都离不开它们的支持。开发者需要深入理解各种设计模式和架构思想的本质,结合项目的实际情况,灵活运用,才能构建出高质量、高性能且易于维护的JavaScript应用程序。在不断学习和实践的过程中,随着对这些概念理解的深入,开发者将能够更好地应对各种复杂的开发需求,提升自己在JavaScript开发领域的技术水平。同时,随着JavaScript技术的不断发展,新的设计模式和架构思想也可能会不断涌现,开发者需要保持学习的热情,紧跟技术前沿,以适应不断变化的开发环境。