• 重要: 代理模式、装饰器模式、适配器模式
  • 不重要: 桥接模式、外观模式、组合模式、享元模式

    装饰器模式

    装饰器模式,又名装饰者模式。它的定义是“在不改变原对象的基础上,通过对其进行包装拓展,使原有对象可以满足用户的更复杂需求”。

  1. // 定义打开按钮
  2. class OpenButton {
  3. // 点击后展示弹框(旧逻辑)
  4. onClick() {
  5. const modal = new Modal()
  6. modal.style.display = 'block'
  7. }
  8. }
  9. // 定义按钮对应的装饰器
  10. class Decorator {
  11. // 将按钮实例传入
  12. constructor(open_button) {
  13. this.open_button = open_button
  14. }
  15. onClick() {
  16. this.open_button.onClick()
  17. // “包装”了一层新逻辑
  18. this.changeButtonStatus()
  19. }
  20. changeButtonStatus() {
  21. this.changeButtonText()
  22. this.disableButton()
  23. }
  24. disableButton() {
  25. const btn = document.getElementById('open')
  26. btn.setAttribute("disabled", true)
  27. }
  28. changeButtonText() {
  29. const btn = document.getElementById('open')
  30. btn.innerText = '快去登录'
  31. }
  32. }
  33. const openButton = new OpenButton()
  34. const decorator = new Decorator(openButton)
  35. document.getElementById('open').addEventListener('click', function() {
  36. // openButton.onClick()
  37. // 此处可以分别尝试两个实例的onClick方法,验证装饰器是否生效
  38. decorator.onClick()
  39. })

ES7,需要babel转换

  1. // 具体的参数意义,在下个小节,这里大家先感知一下操作
  2. function funcDecorator(target, name, descriptor) {
  3. let originalMethod = descriptor.value
  4. descriptor.value = function() {
  5. console.log('我是Func的装饰器逻辑')
  6. return originalMethod.apply(this, arguments)
  7. }
  8. return descriptor
  9. }
  10. class Button {
  11. @funcDecorator
  12. onClick() {
  13. console.log('我是Func的原有逻辑')
  14. }
  15. }
  16. // 验证装饰器是否生效
  17. const button = new Button()
  18. button.onClick()

适配器模式

适配器模式通过把一个类的接口变换成客户端所期待的另一种接口,可以帮我们解决不兼容的问题```

  1. // Ajax适配器函数,入参与旧接口保持一致
  2. async function AjaxAdapter(type, url, data, success, failed) {
  3. const type = type.toUpperCase()
  4. let result
  5. try {
  6. // 实际的请求全部由新接口发起
  7. if(type === 'GET') {
  8. result = await HttpUtils.get(url) || {}
  9. } else if(type === 'POST') {
  10. result = await HttpUtils.post(url, data) || {}
  11. }
  12. // 假设请求成功对应的状态码是1
  13. result.statusCode === 1 && success ? success(result) : failed(result.statusCode)
  14. } catch(error) {
  15. // 捕捉网络错误
  16. if(failed){
  17. failed(error.statusCode);
  18. }
  19. }
  20. }
  21. // 用适配器适配旧的Ajax方法
  22. async function Ajax(type, url, data, success, failed) {
  23. await AjaxAdapter(type, url, data, success, failed)
  24. }

⭐代理模式

在某些情况下,出于种种考虑/限制,一个对象不能直接访问另一个对象,需要一个第三者(代理)牵线搭桥从而间接达到访问目的,这样的模式就是代理模式。

应用

事件委托代理

  • 事件捕获指的是从document到触发事件的那个节点,即自上而下的去触发事件
  • 事件冒泡是自下而上的去触发事件
  • 绑定事件方法的第三个参数,就是控制事件触发顺序是否为事件捕获。true为事件捕获;false为事件冒泡,默认false。

3. 结构型 - 图1

  1. <body>
  2. <ul id="list">
  3. <li>1</li>
  4. <li>2</li>
  5. <li>3</li>
  6. </ul>
  7. <script>
  8. let list = document.querySelector('#list');
  9. list.addEventListener('click',event=>{
  10. alert(event.target.innerHTML);
  11. });
  12. </script>
  13. </body>

图片预加载

预加载主要是为了避免网络不好、或者图片太大时,页面长时间给用户留白的尴尬。
常见的操作是先让这个 img 标签展示一个占位图,然后创建一个 Image 实例,让这个 Image 实例的 src 指向真实的目标图片地址、观察该 Image 实例的加载情况 —— 当其对应的真实图片加载完毕后,即已经有了该图片的缓存内容,再将 DOM 上的 img 元素的 src 指向真实的目标图片地址。

此时我们直接去取了目标图片的缓存,所以展示速度会非常快,从占位图到目标图片的时间差会非常小、小到用户注意不到,这样体验就会非常好了。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge" />
  7. <title>Document</title>
  8. <style>
  9. .bg-container {
  10. width: 600px;
  11. height: 400px;
  12. margin: 100px auto;
  13. }
  14. .bg-container #bg-image {
  15. width: 100%;
  16. height: 100%;
  17. }
  18. </style>
  19. </head>
  20. <body>
  21. <div id="background">
  22. <button
  23. data-src="https://images.pexels.com/photos/258112/pexels-photo-258112.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"
  24. >
  25. 背景1
  26. </button>
  27. <button
  28. data-src="https://images.pexels.com/photos/1025469/pexels-photo-1025469.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260"
  29. >
  30. 背景2
  31. </button>
  32. </div>
  33. <div class="bg-container">
  34. <img id="bg-image" src="/img1.jpg" />
  35. </div>
  36. <script>
  37. class PreLoadImage {
  38. constructor() {
  39. this.bgImage = document.querySelector("#bg-image");
  40. }
  41. setSrc(src) {
  42. this.bgImage.src = src;
  43. }
  44. }
  45. class ProxyImage {
  46. static LOADING_URL = `loading.gif`;
  47. constructor() {
  48. this.targetImage = new PreLoadImage();
  49. }
  50. setSrc(src) {
  51. this.targetImage.setSrc(ProxyImage.LOADING_URL);
  52. let virtualImage = new Image();
  53. // 监听目标图片加载的情况,完成时再将DOM上的真实img节点的src属性设置为目标图片的url
  54. virtualImage .onload = () => {
  55. this.targetImage.setSrc(src);
  56. };
  57. virtualImage .src = src;
  58. }
  59. }
  60. let proxyImage = new ProxyImage();
  61. let container = document.querySelector("#background");
  62. container.addEventListener("click", function (event) {
  63. let src = event.target.dataset.src;
  64. proxyImage.setSrc(src);
  65. });
  66. </script>
  67. </body>
  68. </html>

缓存代理

缓存代理比较好理解,它应用于一些计算量较大的场景里。在这种场景下,我们需要“用空间换时间”——当我们需要用到某个已经计算过的值的时候,不想再耗时进行二次计算,而是希望能从内存里去取出现成的计算结果。这种场景下,就需要一个代理来帮我们在进行计算的同时,进行计算结果的缓存了。

一个比较典型的例子,是对传入的参数进行求和:

  1. // addAll方法会对你传入的所有参数做求和操作
  2. const addAll = function() {
  3. console.log('进行了一次新计算')
  4. let result = 0
  5. const len = arguments.length
  6. for(let i = 0; i < len; i++) {
  7. result += arguments[i]
  8. }
  9. return result
  10. }
  11. // 为求和方法创建代理
  12. const proxyAddAll = (function(){
  13. // 求和结果的缓存池
  14. const resultCache = {}
  15. return function() {
  16. // 将入参转化为一个唯一的入参字符串
  17. const args = Array.prototype.join.call(arguments, ',')
  18. // 检查本次入参是否有对应的计算结果
  19. if(args in resultCache) {
  20. // 如果有,则返回缓存池里现成的结果
  21. return resultCache[args]
  22. }
  23. return resultCache[args] = addAll(...arguments)
  24. }
  25. })()

Proxy

  • Proxy 可以理解成,在目标对象之前架设一层拦截,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
  • Proxy 这个词的原意是代理,用在这里表示由它来代理某些操作,可以译为代理器
    1. let wang={
    2. name: 'wanglaoshi',
    3. age: 29,
    4. height:165
    5. }
    6. let wangMama=new Proxy(wang,{
    7. get(target,key) {
    8. if (key == 'age') {
    9. return wang.age-1;
    10. } else if (key == 'height') {
    11. return wang.height-5;
    12. }
    13. return target[key];
    14. },
    15. set(target,key,val) {
    16. if (key == 'boyfriend') {
    17. let boyfriend=val;
    18. if (boyfriend.age>40) {
    19. throw new Error('太老');
    20. } else if (boyfriend.salary<20000) {
    21. throw new Error('太穷');
    22. } else {
    23. target[key]=val;
    24. return true;
    25. }
    26. }
    27. }
    28. });
    29. console.log(wangMama.age);
    30. console.log(wangMama.height);
    31. wangMama.boyfriend={
    32. age: 41,
    33. salary:3000
    34. }
    • Vue2 中的变化侦测实现对 Object 及 Array 分别进行了不同的处理,Object 使用了 Object.defineProperty API,Array使用了拦截器对 Array 原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限 ,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在 Vue2 中经常会使用到 $set 这个方法对数据修改,以保证依赖更新。
    • Vue3 中使用了 es6 的 Proxy API对数据代理,没有像 Vue2 中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了 Vue2 中变化侦测的局限性,可以不使用 $set 新增的对象属性及通过下标修改数组都能被侦测到。