探索前端设计模式:优化代码结构与可维护性

201 阅读7分钟

Hi,大家好,我是拾七,今天和大家浅聊前端开发设计模式。

在前端开发领域,设计模式是一种重要的思维工具,它能够帮助开发者优化代码结构、提高可维护性,并促进团队合作。设计模式是经过长期实践和总结,解决特定问题的经验,它们为我们提供了一种在软件设计中解决常见问题的方式。本文将介绍几种常见的前端设计模式,帮助大家更好滴理解并应用它们。

MVC模式

MVC是一种经典的设计模式,它将应用程序分为三个核心部分:模型(Model)、视图(View)、控制器(Controller)。模型负责管理应用程序的数据和业务逻辑,视图负责展示数据给用户,而控制器则负责处理用户的输入并做出相应的反应。在前端开发中,通常将模型标识为数据对象或服务,视图表示为页面的HTML结构,而控制器则可以是JavaScript函数或对象。

实现一个简单的MVC模式的应用,当用户在输入框中输入内容并点击添加按钮时,数据将被添加到模型中,然后视图会自动更新以展示最新的数据。

// --定义模型
const Model = {
  data: [], // 存储数据的数组
  // 添加数据的方法
  addData: function(item) {
    this.data.push(item);
    // 添加数据后触发更新视图的方法
    View.render();
  },
  // 获取数据的方法
  getData: function() {
    return this.data
  }
}

// --定义视图
const View = {
//渲染视图的方法
  render: function() {
    const dataList = Model.getData();
    const listContainer = document.getElementById('list-container');
    listContainer.innerHTML = ''; // 清空列表容器
    dataList.forEach(item => {
      const listItem = document.createElement('li');
      listItem.textContent = item;
      listContainer.appendChild(listItem);
    })
  }
}

// --定义控制器
const Controller = {
  init: function() {
    this.setupEventListener();
    View.render(); // 初始化时渲染视图
  },

  // 设置事件监听的方法
  setupEventListener: function() {
    const addButton = document.getElementById('add-button');
    addButton.addEventListener('click', function() {
      const inputText = document.getElementById('input-text').value;
      Model.addData(inputText); // 用户点击添加按钮时向模型添加数据
    });
  }
};
// 初始化控制器
Controller.init();


// --HTML结构
<div>
  <input type="text" id="input-text">
  <button id="add-button">Add</button>
</div>
<ul id="list-container"></ul>

观察者模式

观察者模式是一种对象行为模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知并自动更新。在前端开发中,观察者模式常被用于实现事件监听和订阅-发布模式。例如,当用户点击按钮时,所有注册了点击事件的观察者将收到通知并执行相应的操作。

image.png 如上图可知,观察者模式就是观察者和被观察者之间的通信。代码实现如下,观察者主动申请加入被观察者列表,被观察者主动将观察者加入列表:



// ===观察者模式
class Observer {
  constructor(name, subject) {
    this.name = name;
    if(subject) {
      subject.addObserver(this)
    }
  }
  notified(message) {
    console.log(this.name, 'got message', message)
  }
}

// ===被观察者模式
class Subject {
  constructor() {
    this.observerList = [];
  }
  addObserver(observer) {
    this.observerList.push(observer);
  }
  removeObserver(observer) {
    const index = this.observerList.findIndex(o => o.name === observer.name);
    this.observerList.splice(index, 1);
  }
  notifyObservers(message) {
    const observers = this.observerList;
    observers.forEach(o => o.notified(message))
  }
}
// ==使用代码如下
const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello');

发布者和订阅者需要通过发布订阅中心进行关联,发布者的发布动作和订阅者的订阅动作相互独立,无需关注对方,消息派发由发布订阅中心负责。

const TYPE_A = 'music';
const TYPE_B = 'comic';
const TYPE_C = 'story';
const pubsub = new PubSub();
const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, "we are young");
publisherA.publish(TYPE_B, "the silicon valley");
const subscriberA = new Subscriber("subscriberA", pubsub);
subscriberA.subscribe(TYPE_A, res => {
  console.log("subscriberA received", res)
})
pubsub.notify(TYPE_A);

在观察者模式中,观察者是知道Subject的,subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在,它们只有通过消息代理进行通信。

单例模式

单例模式是一种创建模式,它确保某个类只有一个实例,并提供了一个全局访问点。在前端开发中,单例模式常被用于管理全局状态、缓存数据或创建唯一的资源管理器。例如,一个应用程序只需要一个全局的配置对象来保存配置信息,这时就可以使用单例模式确保该对象只被实例化一次。在javascript中,实现一个单例模式可以用一个变量来标识当前的类已经创建过对象,如果下次获取当前类的实例时,直接返回之前创建的对象即可,如下:


// 定义一个类
function Singleton(name) {
    this.name = name;
    this.instance = null;
  }
  
  // 原型扩展类的一个方法getName
  Singleton.prototype.getName = function() {
    console.log(this.name);
  }
  // 获取类的实例
  Singleton.getInstance = function(name) {
    if(!this.instance) {
      this.instance = new Singleton(name);
    }
    return this.instance;
  }
  
  // 获取对象
  const a = Singleton.getInstance('a');
  const b = Singleton.getInstance('b');
  console.log(a === b); // true

通过闭包也可以实现,如下:


// 单例构造函数
function CreateSingleton(name) {
    this.name = name;
    this.getName();
  }
  
  // 获取实例的名称
  CreateSingleton.prototype.getName = function() {
    console.log(this.name)
  }
  // 单例对象
  const Singleton = (function() {
    var instance;
    return function(name) {
      if(!instance) {
        instance = new CreateSingleton(name);
      }
      return instance;
    }
  })();
  // 创建实例对象
  const a = new Singleton('a');
  const b = new Singleton('b');
  console.log(a === b); // true

在前端中,很多情况都是用到单例模式,例如页面存在一个模态框的时候,只有用户点击的时候才会创建,而不是加载完成之后再创建弹窗和隐藏,并且保证弹窗全局只有一个。

// === 创建一个获取对象的方法
const getSingle = function(fn) {
    let result;
    return function() {
      return result || (result = fn.apply(this, arguments));
    }
  }
  // === 创建弹窗
  const createLoginLayer = function() {
    var div = document.createElement('div');
    div.innerHTML = '我是浮窗';
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
  }
  const createSingleLoginLayer = getSingle(createLoginLayer);
  document.getElementById('loginBtn').onClick = function() {
    var loginLayer = createSingleLoginLayer();
    loginLayer.style.display = 'block'; 
  }
  

工厂模式

工厂模式是一种创建模式,它定义了一个用于创建对象的接口,但将具体的实现延迟到子类中。在前端开发中,工厂模式常被用于创建复杂的对象组件,从而将对象的创建和使用解耦。例如,一个UI组件可以使用工厂模式来创建不同类型的按钮或表单控件,而不必暴露具体的实现细节。工厂模式根据抽象程度不同可以分为:简单工厂模式、工厂方法模式、抽象工厂模式。

简单工厂模式也叫静态工厂模式,用一个工厂对象创建同一类对象类的实例,假设我们要开发一个公司岗位及其工作内容的录入信息,不同岗位的工作内容不一致,代码如下:

function Factory(job){
    function User(job, work) {
      this.job = job;
      this.work = work;
    }
    let work;
    switch(job) {
      case 'coder': 
        work = ['写代码', '修BUG'];
        return new User(job, work);
        break;
      case 'boss':
        work = ['喝茶', '开会'];
        return new User(job, work);
        break;
    }
  }
  let coder = new Factory('coder');
  console.log(coder);
  let boss = new Factoty('boss');
  console.info(boss);

Factory就是一个简单的工厂。当我们调用工厂函数时,只需要传递name、age就可以获取到包含用户工作内容的实例对象。

工厂方法模式跟简单工厂模式差不多,但是把具体的产品放到了工厂函数的prototype中,这样以来,扩展产品种类就不必修改工厂函数了,核心类就变成抽象类,也可以随时重写某种具体的产品。


// 工厂方法
function Factory(job) {
    if(this instanceof Factory) {
      var a = new this[job]();
      return a;
    }
    return new Factory(job);
  }
  Factory.prototype = {
    'coder': function() {
      this.jobName = '码农';
      this.work = ['写代码', '写BUG'];
    },
    'boss': function() {
      this.jobName = '老板';
      this.work = ['喝茶', '开会'];
    }
  }
  let coder = new Factory('coder');
  console.log(coder);
  let boss = new Factory('boss');
  console.info(boss);

简单工厂和工厂方法模式时生产产品,那么抽象工厂模式的工作就是生产工厂的,由于javascript中并没有抽象类的概念,只能模拟、可以分成四部分:用于创建抽象类的函数、抽象类、具体类、实例化具体类。

let JobAbstractFactory = function(subType, superType) {
    if(typeof JobAbstractFactory[superType] === 'function') {
      function F() {};
      F.prototype = new JobAbstractFactory[superType]();
      subType.constructor = subType;
      subType.prototype = new F();
    } else {
      throw new Error('抽象类不存在')
    }
  }                                 

总结

设计模式是前端开发中的重要工具,它们可以帮助我们更好地组织和管理代码,并提高代码的可维护性和可扩展性。然而,在使用设计模式时需要注意不要过度设计,应根据具体的场景和需求选择合适的模式。

PS:如有改进处,欢迎在评论区留言讨论。