常见 JavaScript 设计模式 — 原来这么简单(一)

简介: 常见 JavaScript 设计模式 — 原来这么简单

image.png


设计模式

设计模式总共有 23 种,但在前端领域其实没必要全部都去学习,毕竟大部分的设计模式是在 JavaScript 中占的比重并不是那么大,本文会列举出一些 JavaScript 常见的、容易被忽视的设计模式,不过还是有必要先简单了解一下设计模式相关的概念.

设计模式是什么?

先举个形象的例子,比如现在正在考试而且恰好在考数学,实际上每道数学题目都对应着一种或多种解决公式(如和三角形相关的勾股定理),而这些解决公式是经过数学家研究、推导、总结好的,我们只需要把 题目已有公式 对应上就很容易解决问题,而 设计模式 也是如此,只不过是它是相对于 软件设计领域 而言的.

设计模式(Design pattern) 是一套被反复使用、经过分类、代码设计经验的总结,简单来说设计模式就是为了解决 软件设计领域 不同场景下相应问题的 解决方案.

设计原则(SOLID)

SOLID 实际上指的是五个基本原则,但在前端领域涉及到最多的是仍然是前面两条:

  • 单一功能原则(Single Responsibility Principle)
  • 开放封闭原则(Opened Closed Principle)
  • 里式替换原则(Liskov Substitution Principle)
  • 接口隔离原则(Interface Segregation Principle)
  • 依赖反转原则(Dependency Inversion Principle)

设计模式的类型

主要分为三个类型:

  • 创建型
  • 主要用于解耦 对象的实例化 过程,即用于创建对象,如对象实例化
  • 本文主要包含:简单工厂模式、抽象工厂模式、单例模式、原型模式
  • 行为型
  • 主要用于优化不同 对象接口 间的结构关系,如把 对象 结合在一起形成一个更大的结构
  • 本文主要包含:装饰器模式、适配器模式、代理模式
  • 结构型
  • 主要用于定义 对象 如何交互、划分责任、设计算法
  • 本文主要包含:策略模式、状态模式、观察者模式、发布订阅模式、迭代器模式

创建型设计模式

设计模式的核心是区分逻辑中的 可变部分不变部分,并使它们进行分离,从而达到使变化的部分易扩展、不变的部分稳定.

工厂模式

简单工厂模式

核心就是创建一个对象,这里的 可变部分参数不变部分共有属性.

举例:通过不同职级的员工创建员工相关信息,需要包含 name、age、position、job 等信息.

实现方式一:

  • 核心就是 可变部分 默认 参数化
function Staff(name, age, position, job) {
    this.name = name;
    this.age = age;
    this.position = position;
    this.job = job;
}
const developer = new Staff('zs', 18, 'develoment', ['写 bug', '改 bug', '摸鱼']);
const productManager = new Staff('ls', 30, 'manager', ['提需求', '改需求', '面向 PPT 开发']);
复制代码

实现方式二:

  • 实际上在实现方式一中的 job 部分是和 position 是相互关联的,可以认为 job 部分是 不变的,因此可以根据 position 内容的内容来自动匹配 job
function Staff(name, age, position, job) {
    this.name = name;
    this.age = age;
    this.position = position;
    this.job = job;
}
function StaffFactory(name, age, position){
    let job = []
    switch (position) {
        case 'develoment':
            job = ['写 bug', '改 bug', '摸鱼'];
            break;
        case 'manager':
            job = ['提需求', '改需求', '面向 PPT 开发'];
            break;
        ...
    }
    return new Staff(name, age, position, job);
}
const developer = StaffFactory('zs', 18, 'developer');
const productManager = StaffFactory('ls', 30, 'manager');
复制代码

抽象工厂模式

这个模式最显眼的就是 抽象 两个字了,在如 Java 语言当中存在所谓的 抽象类,这个抽象类里面的所有属性和方法都没有具体实现,只有单纯的定义,而继承这个抽象类的子类必须要实现其对应的抽象属性和抽象方法.

JavaScript 中没有这样的直接定义,不过根据上面的描述其实我们可以把它映射到 typescript 中的 interface 接口,理解到这其实让我联想到了 vue.js 中的 自定义渲染器,预留的自定义渲染器的各个方法目的就是实现跨平台的渲染方式

// 文件位置:packages\runtime-core\src\renderer.ts
export function createRenderer<
  HostNode = RendererNode,
  HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
  return baseCreateRenderer<HostNode, HostElement>(options)
}
// 文件位置:packages\runtime-core\src\renderer.ts
// RendererOptions 就是一个 Interface 接口
export interface RendererOptions<
  HostNode = RendererNode,
  HostElement = RendererElement
> {
  patchProp(
    el: HostElement,
    key: string,
    prevValue: any,
    nextValue: any,
    isSVG?: boolean,
    prevChildren?: VNode<HostNode, HostElement>[],
    parentComponent?: ComponentInternalInstance | null,
    parentSuspense?: SuspenseBoundary | null,
    unmountChildren?: UnmountChildrenFn
  ): void
  insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
  remove(el: HostNode): void
  createElement(
    type: string,
    isSVG?: boolean,
    isCustomizedBuiltIn?: string,
    vnodeProps?: (VNodeProps & { [key: string]: any }) | null
  ): HostElement
  createText(text: string): HostNode
  createComment(text: string): HostNode
  setText(node: HostNode, text: string): void
  setElementText(node: HostElement, text: string): void
  parentNode(node: HostNode): HostElement | null
  nextSibling(node: HostNode): HostNode | null
  querySelector?(selector: string): HostElement | null
  setScopeId?(el: HostElement, id: string): void
  cloneNode?(node: HostNode): HostNode
  insertStaticContent?(
    content: string,
    parent: HostElement,
    anchor: HostNode | null,
    isSVG: boolean,
    start?: HostNode | null,
    end?: HostNode | null
  ): [HostNode, HostNode]
}
复制代码

接下来我们将以上的 typescript 的形式转变成 JavaScript 形式的抽象模式:

// 抽象 Render 类
class Renderer {
  patchProp(
    el,
    key,
    prevValue,
    nextValue,
    isSVG,
    prevChildren,
    parentComponent,
    parentSuspense,
    unmountChildren
  ) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  insert(el, parent, anchor) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  remove(el) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  createElement(type, isSVG, isCustomizedBuiltIn, vnodeProps) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  createText(text) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  createComment(text) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  setText(node, text) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  setElementText(node, text) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  parentNode(node) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  nextSibling(node) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  querySelector(selector) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  setScopeId(el, id) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  cloneNode(node) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
  insertStaticContent(content, parent, anchor, isSVG, start, end) {
    throw Error('抽象工厂方法不能直接使用,你需要将我重写!!!');
  }
}
// 具体渲染函数的实现
class createRenderer extends Renderer{
    // 待实现的渲染器方法
    ...
}
复制代码

单例模式

核心就是通过多次 new 操作进行实例化时,能够保证创建 实例对象唯一性.

vuex 中的单例模式

其实,vuex 中就使用到了 单例模式,代码本身比较简单,当 install 方法被多次调用时,就会得到一个错误信息,并不会多次向 Vue 中混入 vuex 中自定义的内容:


image.png


实现一个单例模式

这里举个封装 localStorage 方法的例子,并提供给外部对应的创建方法,如下:

let storageInstance = null;
class Storage {
    getItem(key) {
        let value = localStorage.getItem(key);
        try {
            return JSON.parse(value);
        } catch (error) {
            return value;
        }
    }
    setItem(key, value) {
        try {
            localStorage.setItem(JSON.stringify(value));
        } catch (error) {
            // do something
            console.error(error);
        }
    }
}
// 单例模式
export default function createStorage(){
    if(!storageInstance){
        storageInstance = new Storage();
    }
    return storageInstance;
}
复制代码

原型模式

JavaScript 中原型模式是很常见的,JavaScript 中实现的 继承 或者叫 委托 也许更合适,因为它不等同于如 Java 等语言中的继承,毕竟 JavaScript继承 是基于原型(prototype)来实现.

class Person {
    say() {
        console.log(`hello, my name is ${this.name}!`);
    }
    eat(foodName) {
        console.log(`eating ${foodName}`);
    }
}
class Student extends Person {
    constructor(name) {
        super();
        this.name = name;
    }
}
const zs = new Student('zs');
const ls = new Student('ls');
console.log(zs.say === ls.say);// Java 中是不相等的, JavaScript 中是相等的
console.log(zs.eat === ls.eat);// Java 中是不相等的, JavaScript 中是相等的

vue2 中的原型模式

文件位置:\src\core\instance\lifecycle.js

image.png

目录
相关文章
|
4月前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 职责链模式
js设计模式【详解】—— 职责链模式
76 8
|
4月前
|
设计模式 JavaScript 前端开发
js设计模式【详解】—— 组合模式
js设计模式【详解】—— 组合模式
52 7
|
1月前
|
设计模式 JavaScript 前端开发
JavaScript设计模式--访问者模式
【10月更文挑战第1天】
30 3
|
3月前
|
设计模式 JavaScript 前端开发
从工厂到单例再到策略:Vue.js高效应用JavaScript设计模式
【8月更文挑战第30天】在现代Web开发中,结合使用JavaScript设计模式与框架如Vue.js能显著提升代码质量和项目的可维护性。本文探讨了常见JavaScript设计模式及其在Vue.js中的应用。通过具体示例介绍了工厂模式、单例模式和策略模式的应用场景及其实现方法。例如,工厂模式通过`NavFactory`根据用户角色动态创建不同的导航栏组件;单例模式则通过全局事件总线`eventBus`实现跨组件通信;策略模式用于处理不同的表单验证规则。这些设计模式的应用不仅提高了代码的复用性和灵活性,还增强了Vue应用的整体质量。
50 1
|
3月前
|
设计模式 JavaScript 前端开发
小白请看 JS大项目宝典:设计模式 教你如何追到心仪的女神
小白请看 JS大项目宝典:设计模式 教你如何追到心仪的女神
|
4月前
|
设计模式 JavaScript Go
js设计模式【详解】—— 状态模式
js设计模式【详解】—— 状态模式
79 7
|
4月前
|
设计模式 JavaScript
js设计模式【详解】—— 桥接模式
js设计模式【详解】—— 桥接模式
67 6
|
4月前
|
设计模式 JavaScript
js设计模式【详解】—— 原型模式
js设计模式【详解】—— 原型模式
49 6
|
4月前
|
设计模式 JavaScript 算法
js设计模式【详解】—— 模板方法模式
js设计模式【详解】—— 模板方法模式
47 6
|
4月前
|
设计模式 存储 JavaScript
js设计模式【详解】—— 享元模式
js设计模式【详解】—— 享元模式
64 6