技巧型设计模式(二)

本文介绍了四种技巧型设计模式:简单模版模式用于减少DOM操作,提高性能;惰性模式通过重定义对象减少重复分支判断;参与者模式通过Function.prototype.bind实现函数绑定;等待者模式用于监听多个异步任务,集中处理结果。通过实例展示了这些模式在实际中的应用。
摘要由CSDN通过智能技术生成

简单模版模式(SimpleTemplate)

核心
  • 定义:通过格式化字符串,拼凑出视图渲染到页面。减少dom操作,优化内存开销,简化代码。
  • 重点:封装一个模版生成器,简化模版的创建过程
  • 应用:大型框架(如MVC)创建视图操作
示例一:展示模版
  • 需求:使用简单模版模式,创建列表视图
  • 模版生成器实现
// 命名空间
var A = A || {};
// 模板生成器
A.view = function (name) {
  // 模版库
  const v = {
    // ...
  };
  // 数组(多个模版拼接)
  if (Object.prototype.toString.call(name) === "[object Array]") {
    // 模版字符串
    var tpl = "";
    for (var i = 0, len = name.length; i < len; i++) {
      tpl += arguments.callee(name[i]);
    }
    return tpl;
  }
  // 字符串,有对应模版则返回对应模版,没有返回默认模版
  else {
    return v[name] ? v[name] : `<${name}>{#${name}#}</${name}>`;
  }
};

// 使用模版生成器,生成标签的默认模版
console.log(A.view("li")); // <li>{#li#}</li>
console.log(A.view(["h2", "p", "ul"])); // <h2>{#h2#}</h2><p>{#p#}</p><ul>{#ul#}</ul>
  • 使用模版,生成列表数据
A.formateStr = function (str, data) {
  return str.replace(/\{#(\w+)#\}/g, function (match, key) {
    return data[key] || "";
  });
};

// 展示数据
var data = {
  lis: [
    {
      strong: "strong1",
      span: "(span1)",
    },
    {
      strong: "strong2",
      span: "(span2)",
    },
  ],
};
// 获取li元素模版
liTpl = A.formateStr(A.view("li"), {
  li: A.view(["strong", "span"]),
});
// <li><strong>{#strong#}</strong><span>{#span#}</span></li>
console.log(liTpl);
// 获取真正的li列表
const list = data.lis;
let ul = "";
for (var i = 0, len = list.length; i < len; i++) {
  if (list[i].em || list[i].span) {
    ul += A.formateStr(liTpl, list[i]);
  }
}
// <li><strong>strong1</strong><span>(span1)</span></li><li><strong>strong2</strong><span>(span2)</span></li>
console.log(ul);
  • 完整代码
// index.js
// 需求:使用简单模版模式,创建视图

// 命名空间
var A = A || {};
// 模板渲染方法
A.formateStr = function (str, data) {
  return str.replace(/\{#(\w+)#\}/g, function (match, key) {
    return data[key] || "";
  });
};
// 模板生成器
A.view = function (name) {
  // 模版库
  const v = {
    // ...
  };
  // 数组(多个模版拼接)
  if (Object.prototype.toString.call(name) === "[object Array]") {
    // 模版字符串
    var tpl = "";
    for (var i = 0, len = name.length; i < len; i++) {
      tpl += arguments.callee(name[i]);
    }
    return tpl;
  }
  // 字符串,有对应模版则返回对应模版,没有返回默认模版
  else {
    return v[name] ? v[name] : `<${name}>{#${name}#}</${name}>`;
  }
};
// 创建视图方法集合
A.strategy = {
  // 列表功能
  listPart(data) {
    var s = document.createElement("div"), // 模块容器
      ul = "", // ul列表字符串
      list = data.data.lis, // li列表数据
      // 展示的模版
      tpl = A.view(["h2", "p", "ul"]),
      // ul模版
      liTpl = A.formateStr(A.view("li"), {
        li: A.view(["strong", "span"]),
      });
    // 设置ID
    data.id && (s.id = data.id);
    // 获取liTpl模板展示字符串
    for (var i = 0, len = list.length; i < len; i++) {
      if (list[i].em || list[i].span) {
        ul += A.formateStr(liTpl, list[i]);
      }
    }
    // 装饰ul数据
    data.data.ul = ul;
    // 获取tpl展示字符串
    s.innerHTML = A.formateStr(tpl, data.data);
    document.getElementById(data.containerId).appendChild(s);
  },
  // 其他功能
};
// 初始化
A.init = function (data) {
  this.strategy[data.type](data);
};

if (typeof module === "object") {
  module.exports = A;
}
  • html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="container"></div>
  <script src="./index.js"></script>
  <script>
    A.init({
      containerId: "container",
      id: "3232",
      type: "listPart",
      data: {
        lis: [
          {
            strong: "strong1",
            span: "(span1)",
          },
          {
            strong: "strong2",
            span: "(span2)",
          },
        ],
        h2: "h2 text",
        p: "p text",
      },
    });

  </script>
</body>

</html>
  • 效果
    在这里插入图片描述

惰性模式(Layier)

核心
  • 定义:减少每次代码执行时的重复性分支判断
  • 重点:通过重定义对象,屏蔽分支判断
  • 应用:兼容性API的封装
示例一:点击事件的封装
  • 需求:封装点击事件,兼容各个浏览器。点击时,屏蔽分支判断
  • 加载即执行
// 加载即执行(立即执行A.on的初始化)
A.on = (function (dom, type, fn) {
  // 支持 addEventListener
  if (document.addEventListener) {
    return function (dom, type, fn) {
      dom.addEventListener(type, fn, false);
    };
  } else if (document.attachEvent) {
    return function (dom, type, fn) {
      dom.attachEvent("on" + type, fn);
    };
  } else {
    return function (dom, type, fn) {
      dom["on" + type] = fn;
    };
  }
})();
  • 惰性执行(第一次调用时,重写A.on方法,并执行)
// 惰性执行(第一次调用时,初始化A.on方法,并执行)
A.on = function (dom, type, fn) {
  // 支持 addEventListener
  if (document.addEventListener) {
    A.on = function (dom, type, fn) {
      dom.addEventListener(type, fn, false);
    };
  } else if (document.attachEvent) {
    A.on = function (dom, type, fn) {
      dom.attachEvent("on" + type, fn);
    };
  } else {
    A.on = function (dom, type, fn) {
      dom["on" + type] = fn;
    };
  }
  A.on(dom, type, on);
};

参与者模式(Participator)

核心
  • 定义:在特定的作用域中执行给定的函数,并将参数原封不动地传递(bind实现)
  • 重点:使用 apply 方法,实现 bind 方法
  • 应用:事件绑定,函数柯里化
示例一:函数原型bind方法实现
  • 需求:实现 Function.prototype.bind 方法
  • js
// index.js
// 函数原型bind方法实现
Function.prototype.bind1 = function (context) {
  // 当前函数对象
  const that = this;
  return function () {
    return that.apply(context, arguments);
  };
};

var obj = {
  a: 1,
  b: 2,
};
var f = function () {
  console.log(this);
};
f.bind(obj)(); // { a: 1, b: 2 }
f.bind1(obj)(); // { a: 1, b: 2 }
示例二:函数柯里化
  • 需求:借助柯里化伪造其他函数(多态或重载),根据不同的参数实现不同的功能(类似中间件)
  • js
// index2.js
// 函数柯里化(借助柯里化伪造其他函数(多态或重载),根据不同的参数实现不同的功能(类似中间件))

function curry(fn) {
  const slice = [].slice;
  // 获取fn 的参数(闭包)
  const args = slice.call(arguments, 1); // 类数组对象截取

  return function () {
    // 拼接参数,调用fn
    return fn.apply(null, [...args, ...arguments]);
  };
}

// 加法器
function add(a, b) {
  return a + b;
}

// 加n加法器
function addN(n) {
  return curry(add, n);
}

//加5加法器
var add5 = addN(5);
console.log(add5(9)); // 14
示例三:事件绑定
  • 需求:点击按钮时,传递数据,绑定this
  • js
// index3.js
// 点击按钮时,传递数据

Function.prototype.bind1 = function (context) {
  // 当前函数对象
  const that = this,
    args = [].slice.call(arguments, 1);
  return function () {
    return that.apply(context, [...args, ...arguments]);
  };
};

var callback = function () {
  console.log(this, arguments);
};
var data1 = {
  text: "第一组数据",
};
var data2 = {
  text: "第二组数据",
};

// 默认的bind方法
var btn = document.getElementsByTagName("button")[0];
var btnFn = callback.bind(btn, data2, data1);
btn.addEventListener("click", btnFn);

// 自定义bind1方法
var p = document.getElementsByTagName("p")[0];
var pFn = callback.bind1(p, data1, data2);
p.addEventListener("click", pFn);
// 5s后解绑事件
setTimeout(() => {
  p.removeEventListener("click", pFn);
}, 5000);
  • html
<!-- index3.html -->
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button>111</button>
  <p>222</p>
  <script src="./index3.js"></script>
</body>

</html>
  • 效果
    在这里插入图片描述
示例四:反柯里化函数
// index.js
// 反柯里化
Function.prototype.uncurry = function () {
  const that = this;
  return function () {
    // that 为 函数,arguments 包含 this, 参数
    console.log(that, arguments);
    return Function.prototype.call.apply(that, arguments);
  };
};

// 对象原型方法
var toString = Object.prototype.toString.uncurry();
console.log(toString(() => {})); // [object Function]
console.log(toString(null)); // [object Null]

// 数组原型方法
var push = [].push.uncurry();
var obj = {};
push(obj, "第一个", "第二个");
console.log(obj); // { '0': '第一个', '1': '第二个', length: 2 }

// 函数call 方法
Function.prototype.call;
// 给call方法绑定 this 和参数
Function.prototype.call.call(Math.max, 1, 3, 4); // 4
Function.prototype.call.apply(Math.max, [1, 3, 4]); // 4

等待者模式(Waiter)

核心
  • 定义:监听多个异步任务,触发未来发生的动作
  • 重点:需等待多个异步任务全部完成时,执行后续操作
  • 应用:接口拆分
示例一:模拟并发请求,集中处理其结果
  • 需求:多个异步或同步任务,全部成功后调用成功回调
  • 原理:某个异步任务完成后,判断是否所有的异步任务均已完成(类似于轮循)
// 等待者模式
// 原理:异步完成后,判断是否所有的异步任务均已完成(类似于轮循)
var Waiter = function () {
  // 变量
  var dfd = [], // 容器对象
    doneArr = [], // 成功回调队列
    failArr = [], // 失败回调队列
    slice = [].slice, // 数组原型slice方法
    that = this;

  // 实例成员
  // 实例化监控对象
  this.Deferred = function () {
    return new Promise();
  };
  // 监控异步方法
  this.when = function () {
    dfd = slice.call(arguments);
    for (var i = dfd.length - 1; i >= 0; i--) {
      // 如果监控对象不存在,或已结束,或不是Promise实例,则删除
      if (
        !dfd[i] ||
        dfd[i].status !== "pending" ||
        !dfd[i] instanceof Promise
      ) {
        dfd.splice(i, 1);
      }
    }
    return that;
  };
  // 添加成功回调
  this.done = function () {
    doneArr = [...doneArr, ...arguments];
    return that;
  };
  this.fail = function () {
    failArr = [...failArr, ...arguments];
    return that;
  };

  // 内部成员
  // 监控对象类
  var Promise = function () {
    // 状态(借用es6 Promise状态)
    this.status = "pending";
  };
  // 原型方法
  Promise.prototype = {
    // 变更状态方法
    resolve() {
      this.status = "resolved";
      // 没监控实例,返回
      if (!dfd.length) return;
      for (let i = dfd.length - 1; i >= 0; i--) {
        // 有一个异步任务未处理成功,则返回
        if (dfd[i] && dfd[i].status !== "resolved") {
          return;
        }
        // 清除监控对象
        dfd.splice(i, 1);
      }
      // 所有任务成功后执行
      _exec(doneArr);
    },
    reject() {
      this.status = "rejected";
      if (!dfd.length) return;
      // 清空所哟监控对象
      dfd = [];
      // 执行失败方法
      _exec(failArr);
    },
  };
  // 批量执行回调(私有方法)
  function _exec(arr) {
    let i = 0,
      len = arr.length;
    for (; i < len; i++) {
      try {
        arr[i] && arr[i]();
      } catch (e) {}
    }
  }
};

/**
 * 实现
 * 1. 生成异步对象(zero, first, second, third)
 * 2. when 方法 向容器中,添加正确的异步任务
 * 3. resolve和reject触发时,会遍历容器,清除已完成的监控对象
 * 4. 如果所有监控对象,都已resolved, 则调用成功回调
 * 5. 有一个reject,则走失败回调
 */

// 创建等待者对象
var waiter = new Waiter();

// 同步任务0
var zero = (function () {
  // 监控对象实例
  const p = waiter.Deferred();
  p.resolve();
  return p;
})();
// 模拟数据
zero.data = 0;
// 异步任务一
var first = (function () {
  // 监控对象实例
  const p = waiter.Deferred();
  setTimeout(function () {
    console.log("first done");
    // 模拟数据
    first.data = 1;
    p.resolve();
  }, 1000);
  return p;
})();
// 异步任务二
var second = (function () {
  // 监控对象实例
  const p = waiter.Deferred();
  setTimeout(function () {
    console.log("second done");
    // 模拟数据
    second.data = 2;
    p.resolve();
  }, 2000);
  return p;
})();
// 同步任务三
var third = (function () {
  // 监控对象实例
  const p = waiter.Deferred();
  p.resolve();
  return p;
})();
// 模拟数据
third.data = 3;
// 异步任务五
var four = (function () {
  // 监控对象实例
  const p = waiter.Deferred();
  setTimeout(function () {
    console.log("four fail");
    // 模拟数据
    four.data = 2;
    p.reject();
  }, 2000);
  return p;
})();
/**
 * 依次输出
 * first done
 * second done
 * success { status: 'resolved', data: 0 } { status: 'resolved', data: 1 } { status: 'resolved', data: 2 } { status: 'resolved', data: 3 }
 * four fail
 */
waiter
  .when(zero, first, second, third)
  .done(function () {
    console.log("success", zero, first, second, third);
  })
  .fail(function () {
    console.log("fail", zero, first, second, third);
  });
// waiter
//   .when(zero, first, four)
//   .done(function () {
//     console.log("success", zero, first, four);
//   })
//   .fail(function () {
//     console.log("fail", zero, first, four);
//   });
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值