likes
comments
collection
share

前端设计模式之代理模式(九)

作者站长头像
站长
· 阅读数 12
  • 针对对象设置代理访问,不得直接访问
  • 直接访问对象,在远程面向对象系统中,可能对象创建开销很大,或者某些操作需要安全控制,我们添加一个访问层访问该对象
  • 例如,你通过房产中介买房子,中介就是一个代理,而非真正的房主
  • 再例如,明星都有经纪人,经纪人就是一个代理

前端设计模式之代理模式(九)

实现代理模式

前端设计模式之代理模式(九)

class RealImg {
  fileName: string;
  constructor(fileName: string) {
    this.fileName = fileName;
    this.loadFromDist();
  }
  display() {
    console.log("display...", this.fileName);
  }
  private loadFromDist() {
    console.log("loading...", this.fileName);
  }
}

class ProxyImg {
  readImg: RealImg;
  constructor(fileName: string) {
    this.readImg = new RealImg(fileName);
  }
  display() {
    this.readImg.display();
  }
}

const proxImg = new ProxyImg("xxx.png"); // 使用代理
proxImg.display();

是否符合设计原则?

  • 代理和目标分离,解耦
  • 代理可自行扩展逻辑
  • 目标也可自行扩展逻辑

符合开放封闭原则

场景

DOM 事件代理

<div id="div1">
  <a href="#">a1</a>
  <a href="#">a2</a>
  <a href="#">a3</a>
  <a href="#">a4</a>
</div>

<script>
  document.getElementById("div1").addEventListener("click", function (e) {
    const target = e.target;
    if (target.nodeName === "A") {
      alert(target.innerHTML);
    }
  });
</script>

虚拟代理

  • virtualImage 代替真实 DOM 像图片发送了请求,却未曾渲染
class PreLoadImage {
  constructor(imgNode) {
    this.imgNode = imgNode;
  }
  setSrc(imgUrl) {
    this.imgNode.src = imgUrl;
  }
}

class ProxyImage {
  // 占位图的url地址
  static LOADING_URL = "xxxxxx";

  constructor(targetImage) {
    this.targetImage = targetImage;
  }
  // 该方法主要操作虚拟Image,完成加载
  setSrc(targetUrl) {
    // 真实img节点初始化时展示的是一个占位图
    this.targetImage.setSrc(ProxyImage.LOADING_URL);
    // 创建一个帮我们加载图片的虚拟Image实例
    const virtualImage = new Image();
    // 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
    virtualImage.onload = () => {
      this.targetImage.setSrc(targetUrl);
    };
    // 设置src属性,虚拟Image实例开始加载图片
    virtualImage.src = targetUrl;
  }
}

缓存代理

  • 空间换时间
  • 通过代理计算的同时对计算结果进行缓存
// addAll方法会对你传入的所有参数做求和操作
const addAll = function () {
  console.log("进行了一次新计算");
  let result = 0;
  const len = arguments.length;
  for (let i = 0; i < len; i++) {
    result += arguments[i];
  }
  return result;
};

// 为求和方法创建代理
const proxyAddAll = (function () {
  // 求和结果的缓存池
  const resultCache = {};
  return function () {
    // 将入参转化为一个唯一的入参字符串
    const args = Array.prototype.join.call(arguments, ",");

    // 检查本次入参是否有对应的计算结果
    if (args in resultCache) {
      // 如果有,则返回缓存池里现成的结果
      return resultCache[args];
    }
    return (resultCache[args] = addAll(...arguments));
  };
})();
function cacheProxy(fn) {
  const cache = Object.create(null);

  return (...args) => {
    const key = args.map(arg => JSON.stringify(arg)).join("__");
    if (cache[key]) {
      console.log("命中缓存");
      return cache[key];
    }

    const res = fn.apply(this, args);
    cache[key] = res;
    return res;
  };
}

function sum(n1, n2) {
  return n1 + n2;
}

const proxySum = cacheProxy(sum);
console.log(proxySum(3, 5)); // 8
console.log(proxySum(3, 5)); // 命中缓存 8

webpack devServer 正向(客户端)代理

参考 webpack.docschina.org/configurati…

// webpack.config.js
module.exports = {
  // 其他配置...
  devServer: {
    proxy: {
      "/api": "http://localhost:8081",
    },
  },
};

nginx 反向(服务端)代理

nginx 配置文件可参考 www.runoob.com/w3cnote/ngi…

server {
    listen   8000;
    location / {
        proxy_pass http://localhost:8001;
    }
    location /api/ {
        proxy_pass http://localhost:8002;
        proxy_set_header Host $host;
    }
}

Proxy

跟踪属性访问

const user = {
  name: "张三",
};
const proxy = new Proxy(user, {
  get(target, key) {
    console.log("get...");
    return Reflect.get(target, key);
  },
  set(target, key, val) {
    console.log("set", val);
    return Reflect.set(target, key, val);
  },
});

proxy.name = "李四"; // set 李四
console.log(proxy.name); // get... 李四

隐藏属性

const user = {
  name: "张三",
  age: 25,
  girlfriend: "小红",
};

const hiddenProps = ["girlfriend"]; // 要隐藏的属性 key
const proxy = new Proxy(user, {
  get(target, key) {
    if (hiddenProps.includes(key as string)) return undefined;
    return Reflect.get(target, key);
  },
  has(target, key) {
    if (hiddenProps.includes(key as string)) return false;
    return Reflect.has(target, key);
  },
  set(target, key, val) {
    if (hiddenProps.includes(key as string)) return false;
    console.log("set...", val);
    return Reflect.set(target, key, val);
  },
});

console.log(proxy.girlfriend); // undefined
console.log(proxy.girlfriend); // undefined
console.log("girlfriend" in proxy); // false

验证属性

const user = {
  name: "张三",
  age: 25,
};

const proxy = new Proxy(user, {
  get(target, key) {
    return Reflect.get(target, key);
  },
  set(target, key, val) {
    if (key === "age") {
      if (typeof val !== "number") return false; // 验证 age 类型
    }
    return Reflect.set(target, key, val);
  },
});

proxy.age = "a";
console.log(proxy.age); // 25
function sum(n1, n2) {
  return n1 + n2;
}

const proxySum = new Proxy(sum, {
  apply(target, thisArg, argumentsList) {
    const [num1, num2] = argumentsList;
    const isNumber = n => typeof n === "number";

    if (!isNumber(num1) || !isNumber(num2)) {
      throw new TypeError("args is must be number");
    }
    return Reflect.apply(target, thisArg, argumentsList);
  }
});

proxySum(3, 6); // 必须传入数字

记录实例

const userList = new WeakSet(); // 每次初始化 user ,都记录到这里

class User {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

const ProxyUser = new Proxy(User, {
  construct(...args) {
    const user = Reflect.construct(...args);
    userList.add(user); // 记录 user 对象
    return user;
  },
});

const user1 = new ProxyUser("张三");
const user2 = new ProxyUser("李四");
console.log("userList", userList);

注意事项

捕获器不变式
  • 捕获器即 get ,不变式即不能因为 Proxy 而改变对象本身的描述符特性
const obj = { x: 100, y: 0 };
Object.defineProperty(obj, "y", {
  value: 200,
  writable: false,
  configurable: false,
});

const proxy = new Proxy(obj, {
  get() {
    return "abc";
  },
});

console.log(proxy.x);
console.log(proxy.y); // y 属性描述符被修改,proxy 不能修改它的值
this
  • 函数里的 this 是由执行时确认的,而非定义时
const user = {
  name: "张三",
  getName() {
    console.log("this...", this);
    return this.name;
  },
};

const proxy = new Proxy(user, {});

user.getName(); // 执行时 this 是 user
proxy.getName(); // 执行时 this 是 proxy

装饰器模式 对比 代理模式

  • 装饰器模式:层层装饰,增强其能力
  • 代理模式:控制其访问能力,隐藏底层具体的操作