Skip to content

所有设计模式的实现都遵循一条原则:找出程序中变化的部分,并将变化封装起来。

分辨模式的关键是目标(应用场景、解决什么问题)

原型模式

JS 中所有对象都是从另一个对象克隆得到的。所以 JS 中必须有一个根对象(Object.prototype),从这个根对象开始克隆生成其他对象。

拆分变化和不变的部分。复用不变的部分,可以是行为、结构

闭包和高阶函数

闭包的形成与变量的作用域和变量的生存周期密切相关。

  • 变量的作用域:变量的有效范围,分为全局作用域、函数作用域和块级作用域(仅letconst)
  • 生存周期:从创建到被回收

高阶函数

  • 分时函数
  • 惰性加载函数

柯里化

作用:将一个函数拆分为多个函数,将多参形式转为单参形式,预先设置函数的参数;

形式:函数的封装,一个函数返回另一个函数;

  • 判断数据类型
javascript
const checkType = type => {
  return data => Object.prototype.toString.call(data) === `[object ${type}]`;
}
let types = ['Number', 'String', 'Boolean'];
let utils = {};
types.forEach( type => utils[`is${type}`]=checkType(type) );

// 使用
utils.isNumber(12);

单例模式

定义:一个类只有一个实例

策略模式

定义:定义一系列算法,并把每个算法封装起来,使他们可以相互替换。

策略模式可以消除分支较多的 if 语句。

代理模式

常见的代理模式有:

  • 缓存代理:缓存计算的结果,如果下次的输入相同直接返回缓存的结果,demo:
js
function generateProxy(fn){
  let cache = {};
  return (...args)=>{
    let k = args.join(',');
    if( k in cache ){
      return cache[k];
    }
    return cache[k] = fn(...args);
  }
}
  • 导出组件内部的数据(状态),比如一个表单,在表单组件的外层获取到所有表单字段的值
tsx
// use-form-context.tsx
export default function useFormContext(config = {}) {
  const [formContext, setFormContext] = useState(Object.assign({}, initState, config));
  // 增删改查
  const operate = {
    setValue: (key, value) => {
      let newState = {
        ...formContext,
        [key]: value,
      };
      setFormContext(newState);
    },
    getValue: (key) => {
      return formContext[key];
    },
    addState: (key, value = '') => {
      let newState;
      if (Array.isArray(key)) {
        let obj = {};
        key.forEach((k, idx) => (obj[k] = value[idx]));
        newState = { ...formContext, ...obj };
      } else {
        newState = { ...formContext, [key]: value[idx] };
      }
      setFormContext(newState);
    },
  };
  // 导出所有的值
  _setAllValues(formContext);
  return [formContext, operate, validateRule];
}

const _setAllValues = (function () {
  let data = {};
  return (value = null) => {
    value !== null && (data = value);
    return () => data;
  };
})();

export const getAllValues = () => {
  return _setAllValues()();
};

// index.tsx
import {getAllValues} from 'use-form-context';
console.log( getAllValues() );
  • 写时复制代理:Vue3.0 中通过代理实现数据绑定

发布订阅模式 | 观察者

作用:解耦,避免使用硬编码。

JavaScript 中使用事件模型代替传统的发布订阅模式。

利用消息队列实现组件通信:改变数据的组件发送一个消息,使用数据的组件监听这个消息并在响应函数中触发setState来改变组件状态。本质上这是观察者模式的实现

名词解释

主题对象、目标对象、发布者:发送消息的一方 订阅者、观察者:接收消息的一方

发布订阅和观察者模式的区别

发布订阅模式和观察者模式都是用于处理对象间的通信,但有一些区别:

  • 发布订阅模式中有三种角色:发布者、订阅者、调度中心,观察者模式中只有两种角色:目标对象、观察者。

  • 在观察者模式中,观察者对象直接订阅主题对象,主题对象维护一个观察者列表,并在状态发生改变时通知所有观察者。而在发布订阅模式中,发布者和订阅者之间通过一个中介者(消息队列或者事件总线)进行通信,发布者不直接通知订阅者。

  • 观察者模式是同步的,当主题对象状态改变时,会立即通知所有观察者。而在发布订阅模式中,发布者发布事件后不关心订阅者是否接收到,订阅者也不需要立即处理事件。

  • 观察者模式中,观察者与主题对象之间通常是一对多的关系,一个主题对象可以有多个观察者。而在发布订阅模式中,发布者和订阅者之间通常是多对多的关系,一个发布者可以有多个订阅者,一个订阅者也可以订阅多个发布者。

综上所述,发布订阅模式更加灵活,但需要引入中介者,而观察者模式更加简单,但适用场景有限。

发布订阅和观察者模式

中介者模式

使对象和对象之间解耦,用中介者和多个对象的一对多关系取代了对象和对象之间的多对多关系。

装饰者模式

装饰者模式可以动态给某个对象添加功能(职责)。装饰者也称为包装器。

高阶组件是一种特殊的函数,该函数接收一个组件作为参数并且返回一个新的组件。高阶组件的实现本质上是装饰者设计模式

  • demo 使用高阶函数传递 context 给组件,避免层层传递 props,部分代码如下,完整代码请查看
tsx
// context.tsx
export const FormContext = React.createContext({});
export const HOCWithFormContext = (Component: any) => {
  return (
    <FormContext.Consumer>
      {(context) => <Component {...context}></Component>}
    </FormContext.Consumer>
  );
};

// App.tsx
const selectorWithContext = HOCWithFormContext(SelectorWrap);
function ParentWrap(props: IParentWrap) {
  return (
    <div className="flex-left">
      {selectorWithContext}
    </div>
  );
}