代理模式,适配器模式,外观模式

740 阅读1分钟

代理模式

介绍:它为目标对象创造了一个代理对象,以控制对目标对象的访问。现实中房产中介,明星经纪人,律师等都是代理模式。

实现房产中介场景

房东把房子代理给中介管理,一个中介管理着多套房源,找中介租房就变的容易。

//房子类
class House {
  constructor(name) {
    //名称
    this.name = name;
    //出租中状态
    this.state = "renting";
  }
  //出租
  rentOut() {
    this.state = "rented";
  }
  //是否已经出租
  hasRent() {
    return this.state === "rented";
  }
}

//房产中介
class Agent {
  constructor() {
    //房源集合
    this.weakMap = new WeakMap();
  }
  //添加房源
  addHouse(house) {
    this.weakMap.set(house, house);
  }
  //查找房源
  hasHouse(house) {
    return this.weakMap.has(house);
  }
  //出租房源
  rentHouse(house) {
    if (this.hasHouse(house)) {
      if (house.hasRent()) {
        console.log(house.name, "房源已出租");
      } else {
        console.log(house.name, "可以看房");
      }
    } else {
      console.log(house.name, "没有此房源");
    }
  }
}

const houseA = new House("houseA");
const houseB = new House("houseB");
const houseC = new House("houseC");
const agent = new Agent();
agent.addHouse(houseA);
agent.addHouse(houseB);

//B房源出租
houseB.rentOut();
agent.rentHouse(houseA); //houseA 可以看房
agent.rentHouse(houseB); //houseB 房源已出租
agent.rentHouse(houseC); //houseC 没有此房源

变量代理

其实变量也是一种代理模式,代码中我们将 a = 1 , a 就拥有数字 1 的代理权限,后续如果我们需要把 1 赋给其他变量,比如 b = 1 , 就可以使用代理a,直接 b = a

const obj = {
  name: "xiaoming",
  sayName() {
    //把变量this代理给_this,访问_this就是访问this
    const _this = this;
    function say() {
      console.log(`我是${_this.name}`);
    }
    //js中this会指向它的调用者,默认指向window,严格模式下为undefined
    say();
  },
};
obj.sayName();

DOM 元素的事件委托

开发中我们经常会给同级的相同子元素(如:li)添加事件,但是子元素过多的话,都绑定事件就会有一定的性能开销,所以我们利用 dom 元素事件先捕捉后冒泡的机制,先给父元素绑定事件,然后捕获或冒泡到子元素。

<ul id="ul">
    <li data-index="1">1</li>
    <li data-index="2">2</li>
    <li data-index="3">3</li>
    <li data-index="4">4</li>
    <li data-index="5">5</li>
</ul>

捕获

ul.addEventListener(
  "click",
  function (e) {
    const dom = e.target;
    if (dom.tagName === "LI") {
      const index = dom.getAttribute("data-index");
      console.log(`第${index}个li元素`);
    }
  },
  true
);

冒泡

ul.addEventListener("click", function (e) {
  const dom = e.target;
  if (dom.tagName === "LI") {
    const index = dom.getAttribute("data-index");
    console.log(`第${index}个li元素`);
  }
});

proxy 实现响应式数据

vue2 中使用 Object.defineProperty 对对象属性的 get,set 的方法的重写,实现依赖收集和派发更新,vue3 则使用 proxy 代理对象,实现响应式数据。

<div class="wrap">
  <input type="text" id="input" />
  <div id="container"></div>
</div>
// 正在执行的effect函数
let active;

//响应式方法
function reactive(target) {
  const weakMap = new WeakMap();
  return new Proxy(target, {
    get(target, name, value, receiver) {
      //收集依赖      { target : {key: [effect1,effect2 ]} }
      let map;
      if (!weakMap.has(target)) {
        weakMap.set(target, (map = new Map()));
      } else {
        map = weakMap.get(target);
      }
      let set;
      if (!map.has(name)) {
        map.set(name, (set = new Set()));
      } else {
        set = new Set();
      }
      set.add(active);
      return Reflect.get(target, name, receiver);
    },
    set(target, name, value, receiver) {
      if (target[name] !== value) {
        Reflect.set(target, name, value, receiver);
        //派发更新
        if (weakMap.has(target)) {
          const map = weakMap.get(target);
          if (map.has(name)) {
            const set = map.get(name);
            set.forEach(effect);
          }
        }
      }
    },
  });
}

//目标对象
const form = {
  input: "",
};
//目标对象变为响应式数据
const proxy = reactive(form);

//vue2中的Watcher, 在渲染effect函数中执行render函数渲染dom时会访问到响应式数据,会对effect进行收集,响应式数据变化后重新触发更新
function effect(fn) {
  active = fn;
  fn();
}

//render函数,渲染dom
function render() {
  container.innerText = proxy.input;
}

//render函数加入到effect中执行
effect(render);

//input输入框修改响应式的值
input.addEventListener("input", function (e) {
  proxy.input = e.target.value;
});

适配器模式

将一个类(对象)的接口(方法、属性)转化为用户需要的另一个接口,解决类(对象)之间接口不兼容的问题。生活中多接口手机充电器,转接头,翻译官等都是适配器模式。

实现多接口手机充电器

//多个接口的充电器
class ChargerAdapter {
  charge(phone) {
    if (phone instanceof Apple) {
      console.log(`给苹果手机充电`);
    } else if (phone instanceof Android) {
      console.log(`给安卓手机充电`);
    }
  }
}

class Apple {}

class Android {}

const chargerAdapter = new ChargerAdapter();
const apple = new Apple();
const android = new Android();

chargerAdapter.charge(apple);
chargerAdapter.charge(android);

Axios 兼容 web 和 node 端

axios 支持在 web 和 node 端发送请求

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== "undefined") {
    // For browsers use XHR adapter
    adapter = require("./adapters/xhr");
  } else if (
    typeof process !== "undefined" &&
    Object.prototype.toString.call(process) === "[object process]"
  ) {
    // For node use HTTP adapter
    adapter = require("./adapters/http");
  }
  return adapter;
}

同时,也支持用户自定义实现适配器

var adapter = config.adapter || defaults.adapter;

重构 ajax 适配 axios

老的项目是用 ajax 发送请求,如果想在不改变原调用方式的情况下底层使用 aixos 发送请求,需要实现一个适配器。

//模拟发送请求
function axios(options) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(options);
    }, 1000);
  });
}

//适配器
function ajaxAdapter(options) {
  const { success, error, ...axiosOptions } = options;
  return axios(axiosOptions).then(success, error);
}

//保证原使用方式不变,重写ajax方法
function ajax(options) {
  return ajaxAdapter(options);
}

ajax({
  url: "/demo-url",
  method: "post",
  data: {
    name: "张三",
  },
  success: function (data) {
    console.log("success", data);
  },
  error: function (err) {
    console.log("error", err);
  },
});

promisify

将 node 中api使用callback实现异步,使用适配器模式让它兼容 promise写法

let { readFile } = require("fs");

function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      const callback = (err, data) => {
        if (err) return reject(err);
        resolve(data);
      };
      fn(...args, callback);
    });
  };
}

readFile = promisify(readFile);

readFile("./demo1.html").then((data) => {
  console.log(data.toString());
});

vue 跨平台实现 canvas 绘画

像 vue,react 这种框架实现了虚拟 node 中间层,可以提供通用的操作虚拟 node 的 api 实现跨平台运行。

下面简单实现利用虚拟 node 实现在 canvas 中绘制图形。

template=>vnode=>canvas绘制图形需要的映射对象=>canvas 绘制

//创建自定义渲染器的方法
import { createRenderer } from "@vue/runtime-core";
import App from "./app";
const canvas = document.querySelector("#canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

const drawApi = {
  //canvas上画圆
  circle: (ctx, props) => {
    const { x, y, r, c } = props;
    ctx.fillStyle = c;
    ctx.arc(x, y, r, 0, 2 * Math.PI);
    ctx.fill();
  },
  //canvas上画矩形
  reat: (ctx, props) => {
    const { x, y, w, h, c } = props;
    ctx.fillStyle = c;
    ctx.fillRect(x, y, w, h);
  },
};

//操作虚拟node的通用api
const nodeOps = {
  // 插入元素
  insert: (child, parent, anchor) => {
    console.log(child, parent);
    // 判断插入的父节点是canvas容器,说明已经是最外层了,开始绘画
    if (parent === canvas && child) {
      const ctx = canvas.getContext("2d");
      const { tag, props } = child;
      drawApi[tag]?.(ctx, props);
    }
  },
  remove: () => {},
  // 创建元素
  createElement: (tag, isSVG, is, props) => {
    return {
      tag,
      props,
    };
  },
  createText: () => {},
  createComment: () => {},
  setText: () => {},
  setElementText: () => {},
  parentNode: () => {},
  nextSibling: () => {},
  querySelector: () => {},
  setScopeId: () => {},
  cloneNode: () => {},
  insertStaticContent: () => {},
  // 添加属性
  patchProp: (vnode, key, oldProp, newProp) => {
    vnode[key] = newProp;
  },
};

function createApp() {
  const app = createRenderer(nodeOps).createApp(App);
  app.mount(canvas);
}
createApp();

对应的模板

export default function app() {
  return (
    <>
      <circle x="100" y="100" r="100" c="red"></circle>
      <reat x="300" y="100" w="200" h="100" c="green"></reat>
    </>
  );
}

外观模式

外观模式就是把一些复杂的流程封装成一个接口供外部用户更简单的使用,使用者不需要关注内部的实现,提供简单的接口供用户使用。生活中的家用电器,遥控器,保姆等都是外观模式。

模拟电脑开机

打开电脑,不需要关注内部怎么运作

//零件
//CPU
class CPU {
  start() {
    console.log("打开cpu");
  }
}
//内存
class Memory {
  start() {
    console.log("打开内存");
  }
}
//硬盘
class Disk {
  start() {
    console.log("打开硬盘");
  }
}
//电脑
class Computer {
  constructor() {
    this.cpu = new CPU();
    this.memory = new Memory();
    this.disk = new Disk();
  }
  start() {
    this.cpu.start();
    this.memory.start();
    this.disk.start();
  }
}

const computer = new Computer();
//开机
computer.start();

代码库

开发中我们用到的第三方库,类似jquery这种对外提供 $ 去调用内部丰富的api也是一种外观模式

 const $ = {
   css(){},
   style(){},
   animate(){},
   each(){},
   ajax(){}
   ...
 }