设计模式
2020-04-16 11:10:11 0 举报
AI智能生成
张容铭《JavaScript 设计模式》阅读笔记
作者其他创作
大纲/内容
设计原则
开放-封闭
在<b>面向对象的程序设计中</b>,开放-封闭原则(OCP)是最重要的一条原则
<font color="#924517">软件实体(类、模块、函数)等应该是</font><font color="#f1753f">可以扩展、但是不可修改的。</font>
<b><font color="#16884a">在不修改原始代码的情况下满足新需求</font></b><br>例如:给 window.onload 函数添加新的功能
<font color="#924517">修改之前的代码,很可能会导致</font><font color="#f1753f">“改好一个 bug,引发其他 bug”</font>
使用<font color="#924517">装饰者模式</font>
window.onload = function() {<br> console.log("之前的window.onload回调");<br>};<br><br>Function.prototype.after = function(afterfn) {<br> var __self = this; //这里的this是调用该after方法的函数对象<br> return function() {<br> // 这里的this是window对象<br> // 当函数被用作事件处理函数时,它的this指向触发事件的元素<br> //(一些浏览器在使用非addEventListener的函数动态添加监听函数时不遵守这个约定)。<br> // console.log(this);<br> var ret = __self.apply(this, arguments);<br> afterfn.apply(this, arguments);<br> return ret;<br> };<br>};<br><br>window.onload = (window.onload || function() {}).after(function() {<br> console.log(document.getElementsByTagName("*").length);<br>});<br>
<b>通过<font color="#16884a">动态装饰函数</font>的方式,我们<font color="#924517">完全不用理会从前 window.onload 函数的内部实现,</font></b><br>无论它的实现优雅或是丑陋。<br>就算我们作为维护者,<b><font color="#c41230">拿到的是一份混淆压缩过的代码也没有关系。</font></b><br><b><font color="#924517">只要它从前是个稳定运行的函数</font>,那么以后也不会因为我们的新增需求而产生错误。</b><br><font color="#5c5c5c"><b>新增的代码和原有的代码可以井水不犯河水</b>。</font><br>
找出变化的地方
<b><font color="#924517">把程序中</font><font color="#f1753f">不变的部分隔离出来</font><font color="#924517">,<br>然后</font><font color="#f1753f">把可变的部分封装起来,</font></b><br>这样一来程序就具有了可扩展性。<br>
方式
用对象的多态性消除条件分支<br><b>对象的多态性不好理解,<br>这里就理解成<font color="#924517">将一组类似对象可变的地方封装到函数中</font></b><br>
<b><font color="#924517">过多的条件分支语句是造成程序违反开放-封闭原则的一个常见原因。</font><br><font color="#f15a23">每当需要增加一个新 的 if 语句时,都要被迫改动原函数。</font></b><br>实际上,<font color="#16884a">每当我们看到一大片的 if 或者 swtich-case 语句时,<br>第一时间就应该考虑,能否利用对象的多态性来重构它们</font>。<br><br><b><font color="#5c5c5c">利用对象的多态性来让程序遵守开放-封闭原则,是一个常用的技巧。</font></b><br>
<font color="#924517">// 不变部分隔离</font><br><b>var makeSound = function( animal ){ </b><br><b> animal.sound();</b><br><b>};</b><br>var Duck = function(){};<br><font color="#924517">// 可变部分封装</font><br><b>Duck.prototype.sound = function(){ console.log( '嘎嘎嘎' );</b><br><b>};</b><br>var Chicken = function(){};<br><b>Chicken.prototype.sound = function(){ console.log( '咯咯咯' );</b><br><b>};</b><br>makeSound( new Duck() ); // 嘎嘎嘎<br>makeSound( new Chicken() ); // 咯咯咯<br>/********* <b><font color="#924517">增加动物狗,不用改动原有的 makeSound 函数</font></b> ****************/<br>var Dog = function(){}; Dog.prototype.sound = function(){<br>console.log( '汪汪汪' ); };<br>makeSound( new Dog() ); // 汪汪汪<br>
<font color="#924517">放置挂钩</font>
<b>放置挂钩(hook)也是分离变化的一种方式</b>。<br>我们<font color="#924517">在程序有可能发生变化的地方放置一个挂钩,挂钩的返回结果决定了程序的下一步走向</font>。<br>这样一来,原本的代码执行路径上就出现了一个 分叉路口,程序未来的执行方向被预埋下多种可能性。<br><br><font color="#924517">在</font><b><font color="#f15a23">模版方式模式</font></b><font color="#924517">中,由于子类的数量是无限制的,</font><font color="#f15a23"><b>总会有一些“个性化”的子类迫使我们不得不去改变已经封装好的算法骨架。</b></font><br><b>于是我们<font color="#16884a">可以在父类中的某个容易变化的地方放置挂钩,挂钩的返回结果由具体子类决定。</font></b><br>这样一来,程序就拥有了变化的可能。<br>
<font color="#924517">使用回调函数</font>
<b><font color="#924517">回调函数是一种特殊的挂钩。</font></b><br>我们可以<b><font color="#16884a">把一部分易于变化的逻辑封装在回调函数里,然后把回调函数当作参数传入一个稳定和封闭的函数中。</font></b><br><b>当回调函数被执行的时候,程序就可以因为回 调函数的内部逻辑不同,而产生不同的结果。</b><br>
比如,我们通过 ajax 异步请求用户信息之后要做一些事情,<br>请求用户信息的过程是不变的, 而获取到用户信息之后要做什么事情,则是可能变化的:<br><br><b> var getUserInfo = function( <font color="#924517">callback</font> ){<br> $.ajax( 'http:// xxx.com/getUserInfo', <font color="#924517">callback</font> );<br>};</b><br>getUserInfo( function( data ){ console.log( data.userName );<br>});<br>getUserInfo( function( data ){ console.log( data.userId );<br> });<br>
行为型
用于不同对象之间职责划分或算法抽象。<br>不仅涉及类和对象,<br>还涉及类或对象之间的交流模式并加以实现。<br>
模版方法
模板方法模式<b><font color="#16884a">定义了一个算法的步骤</font>,并<font color="#16884a">允许子类别为一个或多个步骤提供其实践方式</font></b><font color="#16884a">。</font><br><font color="#924517"><b>让子类在不改变算法架构的情况下,重新定义算法中的某些步骤</b>。</font><br>
弹窗示例
模版类
class Alert {<br> constructor(data) {<br> if (!data) return;<br> this.content = data.content;<br> this.panel = document.createElement("div");<br> //...<br> }<br><b> init() {}<br> bindEvent() {}<br> show() {}<br> hide() {}</b><br>}<br>
子类
<b>class RightAlert extends Alert</b> {<br> constructor(data) {<br> <b><font color="#f1753f">super(data);</font><br> this.confirmBtn.className += " right";</b><br> }<br>}<br><br><b>class TitleAlert extends Alert</b> {<br> constructor(data) {<br> <font color="#924517"> </font><b><font color="#f1753f">super(data);</font><br> this.title = data.title;</b><br> }<br>}<br><br>let c = new TitleAlert({ title: "hh", content: "alert" });<br>console.log(c);<br>
<b>class CancelAlert extends TitleAlert</b>{<br> constructor(data){<br><b><font color="#f1753f"> super(data)</font></b><br> this.cancel=data.cancel<br> this.cancelBtn=document.createElement('span')<br> //...<br> }<br><b> // <font color="#924517">修改 init 方法</font></b><br> init(){<br>// <b>先调用父类 init 方法</b><br><b><font color="#f1753f"> super.init()</font></b><br> this.panel.appendChild(this.cancelBtn)<br> }<br>}<br>
钩子方法
<b>在模板方法模式中,我们在父类中封装了子类的算法框架。<br><font color="#924517">这些算法框架在正常状态下是适用于大多数子类的,</font><font color="#f15a23">但对于一些特别“个性”的子类,框架可能就不适用了。</font><br></b><br>比如我们在饮料类 Beverage 中封装了 饮料的冲泡顺序:<br>(1) 把水煮沸<br>(2) 用沸水冲泡饮料<br> (3) 把饮料倒进杯子 <br>(4) 加调料<br>这 4 个冲泡饮料的步骤适用于咖啡和茶,在我们的饮料店里,<br>根据这 4 个步骤制作出来的咖 啡和茶,一直顺利地提供给绝大部分客人享用。<br>但有一些客人喝咖啡是不加调料(糖和牛奶)的。 <br>既然 Beverage 作为父类,已经规定好了冲泡饮料的 4 个步骤,那么有什么办法可以让子类不受这个约束呢?<br><br>钩子方法(hook)可以用来解决这个问题,<b><font color="#16884a">放置钩子是隔离变化的一种常见手段</font></b>。<br><b>我们<font color="#924517">在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要“挂钩”,这由子类自行决定。<br>钩子方法的返回结果决定了模板方法后面部分的执行步骤</font>,也就是程序接下来的走向。</b><br>
模板类
<b>class Beverage {</b><br> boilWater() {<br> console.log("把水煮沸");<br> }<br> brew() {<br> throw new Error("子类必须重写 brew 方法");<br> }<br> pourInCup() {<br> throw new Error("子类必须重写 pourInCup 方法");<br> }<br> addCondiments() {<br> <b> throw new Error("子类必须重写 addCondiments 方法");</b><br> }<br><font color="#924517"> customerWantsCondiments() {<br> <b>return true; // 默认需要调料</b><br> }</font><br><b> init() {</b><br> this.boilWater();<br> this.brew();<br> this.pourInCup();<br><b><font color="#c41230"> if (this.customerWantsCondiments()) {<br> this.addCondiments();<br> }</font></b><br> }<br>}<br>
子类
<b>class Coffee extends Beverage {</b><br> brew() {<br> console.log("用沸水冲泡咖啡");<br> }<br> pourInCup() {<br> console.log("把咖啡倒进咖啡杯");<br> }<br> addCondiments() {<br> console.log("加糖和牛奶");<br> }<br><b><font color="#924517"> customerWantsCondiments() {<br> return window.confirm("请问需要调料吗?");<br> }</font></b><br>}<br><br><b>let coffee=new Coffee()<br>coffee.init()</b><br>
模版方法 vs 策略
发布订阅
测试:<br><br><b>let event = new Event();</b><br>// event.$on("event1", function(arg) {<br>// console.log("事件1", arg);<br>// });<br>// event.$on("event1", function(arg) {<br>// console.log("又一个事件1", arg);<br>// });<br>// event.$on("event2", function(arg) {<br>// console.log("事件2", arg);<br>// });<br><br>// event.$emit("event1", { name: "开课吧" });<br>// event.$emit("event2", { name: "全栈" });<br><br><b>event.$once("event-once", function(arg) {<br> console.log("事件 once", arg);<br>});</b><br><b>event.$emit("event-once", { name: "once1" });<br>setTimeout(() => {<br> console.log("setTimeout");<br> event.$emit("event-once", { name: "once2" });<br>}, 1000);</b><br>
class Event {<br> constructor() {<br> this.callbacks = {};<br> }<br><b>$on</b>(name, fn) {<br> <b>(this.callbacks[name] || (this.callbacks[name] = [])).push(fn);</b><br>}<br><b> $emit</b>(name, args) {<br> let cbs = this.callbacks[name];<br> if (cbs) {<br> cbs.forEach(c => {<br> <b><font color="#f1753f">c.call(this, args);</font></b><br> });<br> }<br> }<br><b>$off</b>(name, fn) {<br>let cbs = this.callbacks[name];<br>for (let i = 0, len = cbs.length; i < len; i++) {<br>if (cbs[i] === fn) {<br>cbs.splice(i, 1);<br>}<br>}<br>}<br><b> $once</b>(name, fn) {<br> // 我自己想到的是加个 once 的标记,这样会需要改动数据结构<br> // vue 源码里的做法:<br>// <b>利用发布订阅对象的 $on 与 $off 方法<br> // 给此事件用<font color="#924517"> $on 注册的回调函数</font>中<font color="#924517">先调用发布订阅对象的 $off 方法 </font><font color="#5c5c5c">移除自定义事件监听器</font><br> // 然后再利用<font color="#924517"> apply</font> 方法,将 on 方法接收到的 emit 过来的参数给 fn 执行</b><br><font color="#9f8759" style="font-weight: bold;"> let that = this; // </font><font color="#5c5c5c">其实on函数执行时函数里面自己的 this 本来就等同于外面的 this<b>,</b></font> 因为$emit里面是 c.call(this, ...args);<b><br><font color="#924517"> function on() {<br> </font><font color="#c41230">that.$off(name, on);</font><br><font color="#924517"> fn.apply(that, arguments);</font><br><font color="#924517"> }</font><br><font color="#c41230"> that.$on(name, on);</font></b><br> }<br>}<br>
发布订阅 vs 观察者
在观察者模式中,<b><font color="#16884a">被观察者 </font><font color="#5c5c5c">维护着一个</font><font color="#16884a"> 它的观察者列表</font></b>;<br>在发布订阅模式中,<font color="#16884a"><b>发布者和订阅者不知道对方的存在</b></font>,它们只能<b>通过<font color="#16884a">消息代理</font></b>进行通信。<br><br><b>观察者模式是强耦合的,而发布订阅模式是松散耦合的。</b><br>
参考
https://blog.csdn.net/Firvana_Mutex/article/details/82696406<br>
状态模式
类的行为是基于它的状态改变的
状态模式的关键是区分事物内部的状态
最终目的是简化分支判断流程
<b>将每个分支转化为一种状态独立出来</b>,<br>方便每种状态的管理又不至于每次执行时遍历所有分支<br>
超级玛丽示例
var m=new superMary()<br><b><font color="#924517">m.putAction(['run','shot'])<br>m.run()</font></b><br>
// <b><font color="#16884a">将 if else 判断变成对象上的状态</font></b><br>function superMary(action){<br><b> this.state=[]</b><br><font color="#924517"> this.action={<br> run:function(){<br> },<br> jump:function(){<br> },<br> shot:function(){<br> }<br> }</font><br>}<br><br>superMary.<b>prototype.putAction</b>=function(action){<br><b> if(typeof action==='string'){<br> this.state.push(action)<br> }else{<br> this.state=action<br> }</b><br>}<br><br>superMary.<b>prototype.run</b>=function(){<br> <font color="#924517"> this.state.forEach((action)=>{<br> this.action[action]()<br> })</font><br>}<br>
策略模式
<b>将定义的一组算法封装起来</b>,使其相互之间可以替换
<b><font color="#16884a">表单验证</font></b>示例
<b>const InputStrategy = <font color="#f1753f">(function() {</font></b><br><b><font color="#924517"> let Strategy = {</font></b><br><b> notNull(val) {</b><br> // \s 匹配一个空白字符,包括空格、制表符、换页符和换行符。<br> return /\s+/.test(val) ? "请输入内容" : "";<br> },<br><b> number(val) {</b><br> return /^[0-9]+(\.[0-9]+)?$/.test(val) ? "" : "请输入数字";<br> }<br> };<br> return {<br><b> check(type, val) {</b><br> val = String(val).trim();<br> return Strategy[type]<br> ? <b><font color="#924517">Strategy[type](val)</font></b><br> : `没有 ${type} 类型的检测方法`;<br> },<br><b> addStrategy(type, fn) {</b><br> if (Strategy[type]) {<br> throw new Error(`已有 ${type} 类型的检测方法`);<br> } else {<br> Strategy[type] = fn;<br> }<br> }<br> };<br><b><font color="#f1753f">})();</font></b><br><br><b>InputStrategy.check</b>("number", "hh");<br><b>InputStrategy.addStrategy</b>('number',()=>{})<br>
策略 vs 模版方法
<b>模板方法模式基于<font color="#f15a23">继承</font>的思想</b>,而<b>策略模式则偏重于<font color="#f15a23">组合和委托</font></b><br>
职责链模式
<b>解决请求的发送者与接受者之间的耦合</b>。<br><b>通过职责链上多个对象分解请求流程,实现请求在多个对象之间的传递,直到最后一个对象完成请求的处理</b>。<br>
步骤
<ol><li><b>分解需求流程</b><br></li><li>每个对象只做分内的事<br></li><li>无关的事情传到下一个对象中做,直到需求完成<br></li></ol>
优点
<font color="#16884a">即便项目经理某一模块需求不确定,也不影响开发其他模块</font>
方便进行单元测试
<b><font color="#924517">请求发送者只需要知道链中的第一个节点</font></b>,<br>从而弱化了发送者和接收者之间的强联系。<br>
封装 axios,<br><b><font color="#16884a">方便给请求前请求后增加功能</font></b><br>
var a = new<b> myAxios</b>({<br> baseURL: "xxx"<br>});<br>a.send({<br> url: "xxx",<br> data: {},<br> <font color="#924517"> <b>beforeSend: function() {}</b></font><br>});<br>
function <b><font color="#16884a">myAxios</font></b>(axiosConfig) {<br> this.axios = <b>axiosBuild</b>(axiosConfig);<br>}<br>myAxios.prototype.send = function(config) {<br><b> beforeSend</b>.call(this, { <font color="#924517">cb: config.beforeSend</font>, url: config.url });<br> var sendPromise = <b>send</b>.call(this, config);<br> var afterData = <b>afterSend</b>.call(this, {<br> promise: sendPromise,<br> cb: config.afterCb<br> });<br> return afterData;<br>};<br>
function axiosBuild(config) {<br> const service = axios.create(config);<br> return service;<br>}<br>function<b> beforeSend</b>(config) {<br> this.axios.get("xxxurl" + config.url);<br> return <font color="#924517">config.cb.apply(this, arguments)</font>;<br>}<br>function send(config) {<br> var arg;<br> var state = {<br> get: function() {<br> var parse = qs(config.data);<br> return [config.url + parse];<br> },<br> post: function() {<br> return [config.url, config.data];<br> }<br> };<br> arg = state[config.type]();<br> return this.axios[config.type].apply(this, arg);<br>}<br>
命令模式
<font color="#16884a">对命令进行封装,将发出命令的责任和执行命令的责任分割开</font>
canvas 绘图命令<br>示例<br>
<b>使用 canvas 绘图时需要不停使用 canvas 元素的上下文引用,这在多人项目中耦合度比较高。</b><br><b><font color="#924517">通常会将上下文引用对象安全地封装在一个命令对象内部</font></b>,如果他人想绘图,就通过命令对象书写一条命令。
CanvasCommand.excute(<font color="#16884a">[</font><br> {<br> command: "fillStyle",<br> param: "red"<br> },<br> {<br> command: "fillRect",<br> param: [20, 20, 100, 100]<br> }<br><font color="#16884a">]</font>);<br>
const <b>CanvasCommand</b> = <b><font color="#9f8759">(function() {</font></b><br> let canvas = document.getElementById("canvas");<br><b> let ctx = canvas.getContext("2d");</b><br><b><font color="#c41230"> const Action = {</font></b><br> fillStyle(c) {<br> ctx.fillStyle = c;<br> },<br> fillRect(x, y, w, h) {<br> ctx.fillRect(x, y, w, h);<br> },<br> fill() {<br> ctx.fill();<br> },<br> stroke() {<br> ctx.stroke();<br> }<br> //...<br><b><font color="#c41230"> };</font></b><br> return {<br><b> excute: function(msg) {</b><br> if (!msg) return;<br> if (msg.length) {<br> <font color="#9f8759"> </font><font color="#924517">// 遍历执行多个命令<br> <b>for (let i = 0, len = msg.length; i < len; i++) {<br> CanvasCommand.excute(msg[i]);<br> }</b></font><br> } else {<br> // <b>如果 msg.param 不是数组就将其转化为数组,然后再调用 apply 方法</b><br> msg.param =<br> Object.prototype.toString.call(msg.param) ==="[object Array]"<br> ? msg.param<br> : [msg.param];<br> <font color="#f1753f"> </font><font color="#c41230"> <b>Action[msg.command].apply(Action, msg.param);</b></font><br> }<br> }<br> };<br><b><font color="#9f8759">})();</font></b><br>
访问者模式
它使你<b>可以在不改变各元素类的前提下定义作用于这些元素的新操作</b>
不常用,书中的例子目前看着没有用处
中介者模式
解除对象与对象之间的紧耦合关系,<br>降低多个对象和类之间的通信复杂性<br>
增加一个中介者对象后,<b>所有的相关对象都通过中介者对象来通信,而不是互相引用。</b><br>当一个对象发生改变时,只需要通知中介者对象即可。<br>中介者使各对象之间耦合松散,而且可以独立地改变它们之间的交互。<br><b>中介者模式使网状的多对多关系变成了相对简单的一对多关系。</b><br>
在中介者模式里,对象之间几乎不知道彼此的存在,它们只能通过中介者对象来互相影响对方
示例
泡泡堂游戏
<ul><li><span style="font-size: inherit;">玩家可以组队pk</span><br></li><li><span style="font-size: inherit;">玩家可以换组</span><br></li><li><span style="font-size: inherit;">玩家可能会死亡</span><br></li><li><span style="font-size: inherit;">一队人全部失败时其所在的组失败</span><br></li><li><span style="font-size: inherit;">...</span><br></li></ul><br>
中介者 playerDirector 对象
玩家加入、换组、死亡等时只用通知中介者,中介者来负责做通知其他人等的操作
<font color="#f1753f">可以用发布-订阅模式实现中介者</font>
将 playerDirector 实现为订阅者,各 player 作为发布者;<br><br>一旦 player 的状态发生改变,便推送消息给 playerDirector,<br>playerDirector 处理消息后将反馈发送给其他 player。<br>
缺点
系统中会新增一个中介者对象
中介者对象要占去一部分内存
它了解整个链条中的所有关系,中介者对象自身往往是一个难以维护的对象
备忘录模式
保存一个对象的某个状态,以便在适当的时候恢复对象
JS 编程中常用此模式来对服务端数据做缓存备份
重复数据反复请求不仅增加了服务器端的压力,<br>请求数据的等待过程也会影响客户端的用户体验
在备忘录模式中,<b><font color="#924517">数据常常存储在备忘录对象的缓存器中</font></b>,<br>对于数据的读取要通过调用备忘录提供的方法,<br>因此<b><font color="#f1753f">备忘录对象也是数据缓存器的一次保护性封装</font></b>,防止外界的直接访问。<br>
迭代器模式
<font color="#16884a">在不暴露对象内部结构的同时,可以顺序访问聚合对象内的元素</font>
就像银行里的点钞机,可以降低点钞成本,安全而可靠
优点
在开发中极大简化了代码中的循环语句,使代码结构清晰紧凑;<br>这些简化了的循环语句实质上隐形地移到了迭代器中。
使用迭代器时,不用关心对象的内部结构,<b>解决了对象的使用者和对象内部结构之间的耦合</b>
提供了操作对象的统一接口
示例:<b><font color="#16884a">Vue 根据形如 <br>{{school.name.firstName}} 的表达式 <br>从 vm.$data 中取值、设置值<br></font></b><br>let vm = {<br> $data: {<br> a: {<br> b: [],<br> c: {<br> d: 11<br> },<br> s:'s'<br> }<br> }<br>};<br>
getVmVal('a')<br>getVmVal('a.b')<br>getVmVal('a.b.v')<br>
function getVmVal(expr) {<br> if(!expr) return<br> let keys = expr.split(".");<br> let result = vm.$data;<br> for (let i = 0, len = keys.length; i < len; i++) {<br> if (result[keys[i]] === undefined) {<br> return undefined;<br> } else {<br> result=result[keys[i]]<br> }<br> }<br> return result<br>}<br>
setVmVal("k", 1);<br>setVmVal("k.g", 1);<br>setVmVal("a.s.l", 1);<br>
function setVmVal(expr, val) {<br> if (!expr) return;<br> let keys = expr.split(".");<br> let len = keys.length,i=0;<br> let result = vm.$data;<br> for (; i < len; i++) {<br><b><font color="#f1753f"> if(i===len-1){<br> return result[keys[i]]=val;<br> }<br></font></b><br><b><font color="#f1753f"> if (result[keys[i]] === undefined) {<br> result[keys[i]] = {};</font></b><br> } else if (!(result[keys[i]] instanceof Object)) {<br> throw new Error(`vm.$data.${keys.splice(0, i + 1)} is not Object`);<br> }<br><b><font color="#16884a"> result = result[keys[i]];</font></b><br> }<br>}<br>
解释器模式
提供了评估语言的语法或表达式的方式。<br>这种模式实现了一个表达式接口,该接口<b>解释一个特定的上下文</b>。<br>这种模式被用在 SQL 解析、符号处理引擎等。<br>
获取元素的 XPath<br>(元素在页面中所处的位置路径)<br>
// 没有完全弄清楚,以后需要实现这种需求时再来研究<br><br>const Interpreter = (function() {<br> return function(node, wrap) {<br> let path = [];<br> wrap = wrap || document;<br> if (node === wrap) {<br> if (wrap.nodeType == 1) {<br> path.push(wrap.nodeName.toUpperCase());<br> }<br> return path;<br> }<br> if (node.parentNode !== wrap) {<br> // 当前节点的父元素节点不等于容器节点<br> path = Interpreter(node.parentNode, wrap);<br> } else {<br> // 当前节点的父元素节点与容器节点相等<br> if (wrap.nodeType == 1) {<br> path.push(wrap.nodeName.toUpperCase());<br> }<br> }<br><br> if (node.nodeType == 1) {<br> path.push(node.nodeName.toUpperCase());<br> }<br><br> return path;<br> };<br>})();<br><br>let path=Interpreter(document.getElementById('span7'))<br>
技巧型
通过一些特定的技巧来解决组件某方面的问题,一般通过实践经验总结得到
链式调用模式
JS 中链式调用是通过在对象每个方法调用执行完毕后返回当前对象 this 来实现的
委托模式
多个对象接收并处理同一请求时,将请求委托给另一个对象统一处理
将子元素的事件委托给父元素
// 这里可以使用<b><font color="#924517">惰性模式</font></b>章节中的 <br>// <b>A.on(dom, type, fn)</b> 方法<br>ul.onclick=function(e){<br> let <font color="#f384ae"><b>e=e||window.event,<br>tar=e.target||e.srcElement;</b><br></font><br> if(tar.nodeName.toLowerCase()==='li'){<br> tar.style.backgroundColor='grey'<br> }<br>}<br>
优点
<ul><li>优化页面中事件的数量<br></li><li>可以给未来的子元素间接绑定事件<br></li></ul>
数据访问对象(Data access object DAO)模式
抽象和封装对数据源的访问与存储
localStorage
使用:<br><b>const LJ = new BaseLocalStorage("LJ_");</b><br><br>LJ.set("name", "lucy", new Date(), function() {<br>console.log(1)<br>}); //<b><font color="#16884a"> LJ_name : 1585287966510|-|lucy</font></b><br><br>定义:<br>class BaseLocalStorage {<br> constructor(<b><font color="#924517">prefix, seperator = "|-|"</font></b>) {<br> this.prefix = prefix;<br> this.seperator = seperator;<br><b><font color="#16884a"> this.status = {<br> SUCCESS: 0,<br> FAILURE: 1,<br> OVERFLOW: 2,<br> TIMEOUT: 3<br> };</font></b><br> this.storage = localStorage || window.localStorage;<br> }<br> getKey(key) {<br> return this.prefix + key;<br> }<br>}<br><br><br>
set(key, val, time, cb) {<br> let status = this.status.SUCCESS;<br> key = this.getKey(key);<br><b> try {<br> time = new Date(time).getTime() || time.getTime();<br> } catch (e) {<br> <font color="#f1753f">// 传入时间参数有误,默认时间一个月</font><br> time = new Date().getTime() + 1000 * 60 * 60 * 24 * 31;<br> }</b><br><b> try {<br> // 向数据库添加数据<br> this.storage.setItem(key, time + this.seperator + val);<br> } catch (e) {<br><font color="#f1753f"> // 溢出失败<br> status = this.status.OVERFLOW;</font><br> }</b><br> cb && cb.call(this, status, key, val);<br> }<br>
get(key, cb) {<br> let status = this.status.SUCCESS,<br> value = null,<br> seperatorLen = this.seperator.length,<br> that = this,<br> index = 0,<br> time,<br> result;<br><br> let fullkey = this.getKey(key);<br><br><b> try {<br> value = that.storage.getItem(fullkey);<br> } catch (e) {<br> result = {<br> status: that.status.FAILURE,<br> value: null<br> };<br> cb && cb.call(this, result.status, result.value);<br> return result;<br> }</b><br> if (value) {<br> index = value.indexOf(that.seperator);<br> time = +value.slice(0, index);<br> //判断时间是否过期<br> if (new Date(time).getTime() > new Date().getTime() || time == 0) {<br> value = value.slice(index + seperatorLen);<br> } else {<br> <b> <font color="#924517">// 过期<br> value = null;<br> status = that.status.TIMEOUT;<br> that.remove(key);</font></b><br> }<br> } else {<br> status = that.status.FAILURE;<br> }<br> result = {<br> status: status,<br> value: value<br> };<br> cb && cb.call(this, result.status, result.value);<br> return result;<br> }<br>
remove(key, cb) {<br> let status = this.status.FAILURE,<br> value = null;<br> key = this.getKey(key);<br> try {<br> value = this.storage.getItem(key);<br> } catch (e) {}<br> if (value) {<br> this.storage.removeItem(key);<br> }<br> cb &&<br> cb.call(<br> this,<br> status,<br> status > 0<br> ? null<br> : value.slice(<br> value.indexOf(this.seperator) + this.seperator.length<br> )<br> );<br> }<br>
MongoDB
<b>var book=DB('book')</b><br>book.insert({title:'js 设计模式',type:'js'})<br>
config.js
module.exports = {<br> DB: {<br> name: "demo",<br> host: "localhost",<br> port: 27017<br> }<br>};<br>
db.js
var mongodb = require("mongodb");<br>var config = require("./config").DB;<br>var d = new mongodb.Db(<br> config.name,<br> new mongodb.Server(config.host, config.port, { auto_reconnect: true }),<br> { safe: true }<br>);<br><br>function connect(col, fn) {<br> d.open(function(err, db) {<br> if (err) {<br> throw err;<br> } else {<br> db.collection(col, function(err, col) {<br> if (err) {<br> throw err;<br> } else {<br> fn && fn(col, db);<br> }<br> });<br> }<br> });<br>}<br><b>exports.DB = function(col) {<br> return {<br> insert: function() {},<br> remove: function() {},<br> update: function() {},<br> find: function() {}<br> };<br>};</b><br>
节流模式
// window.onscroll=aa<br>window.onscroll = throttle(aa);<br><br>function throttle(fn, <b>interval = 500</b>) {<br> let prev = Date.now(),<br> cur;<br> return function() {<br> cur=Date.now()<br> if (cur - prev >= interval) {<br> fn();<br> prev=cur<br> }<br> };<br>}<br>
涉及技巧:<br><br><ul><li>高阶函数(这里的返回值是函数)<br></li><li>闭包<br></li></ul>
字符串模板模式
<b><font color="#924517">用正则匹配方式去格式化字符串</font></b>拼凑出视图避免创建试图时的大量节点操作
主要包括三部分
字符串模版库
格式化方法
字符串拼接操作
结合策略模式
A.init({<br> type: "listPart",<br> data: {<br> h2: "hello",<br> p: "paragraph",<br> li: [<br> {<br> strong: "strong"<br> }<br> ]<br> }<br>});<br>
const A = (function() {<br>// 这里的代码移到了左边👈<br><br> const Strategy = {<br> listPart(data) {<br> let $container = document.createElement("div"),<br> ulHtml = "",<br> liData = data.data.li || [];<br><br>// <b><font color="#924517">创建字符串模板</font></b><br> // <b>这种创建字符串模板的方式的<font color="#924517">弊端是其内部的结构没有直接被呈现出来<br></font></b>// 开发肯定需要先将 html 结构搭出来再来抽象,<br>// <b><font color="#924517">既然都需要先搭建 html 结构,再这么抽象一次感觉是增加了其复杂程度</font></b><br><b><font color="#c41230"> let tpl = view(["h2", "p", "ul"]),<br> liTpl = formatString(view("li"), {<br> li: view(["strong", "span"])<br> })</font><font color="#f1753f">;</font></b><br><br> data.id && ($container.id = data.id);<br> for (let i = 0, len = liData.length; i < len; i++) {<br> if (liData[i].strong || liData[i].span) {<br> ulHtml += formatString(liTpl, liData[i]);<br> }<br> }<br> data.data.ul = ulHtml;<br> $container.innerHTML = formatString(tpl, data.data);<br> root.appendChild($container);<br> },<br>// 其他策略函数<br> };<br><br> return {<br><b><font color="#924517"> init(data) {<br> Strategy[data.type](data);<br> }</font></b><br> };<br>})();<br>
const root = document.getElementById("container");<br><b>// <font color="#924517">字符串格式化方法</font></b><br><b>function<font color="#16884a"> formatString(str, data) </font>{<br> let replaced = str.replace(/\{#(\w+)#\}/g, function(match, key) {<br> //<font color="#f1753f"> 注意这里要用字符串 'undefined',因为 typeof undefined ==> 'undefined'</font><br> <font color="#924517">return typeof data[key] === "undefined" ? "" : data[key];</font><br> });<br> return replaced;<br>}</b><br>// <b><font color="#924517">模板生成器</font></b><br>function<b> <font color="#c41230">view(name)</font></b> {<br>const <b>V = {<br> code: "<pre><code>{#code#}</code></pre>",<br> img: '<img src="{#src#}" alt="{#alt#}">'<br>};</b><br><b>if (Object.prototype.toString.call(name) === "[object Array]") {<br> let tpl = "";<br> for (let i = 0, len = name.length; i < len; i++) {<br> tpl += view(name[i]);<br> }<br> return tpl;<br>} else {<br> return <font color="#924517">V[name] ? V[name] : `<${name}>{#${name}#}</${name}>`;</font><br>}</b><br>}<br>
惰性模式
<b><font color="#924517">通过对对象重定义</font></b>来屏蔽原对象中的分支判断,<b>去掉每次代码执行时重复的特性判断</b>
两种实现方式
在文件加载进来时<br><font color="#924517">通过闭包执行该方法对其进行重新定义</font><br>
<font color="#f1753f">页面加载时会占用一定资源</font>
let A={}<br><b><font color="#f1753f">A.on = (function() {</font></b><br> if (document.addEventListener) {<br><b> return function(dom, type, fn) {<br> dom.addEventListener(type, fn, false);<br> };</b><br> } else if (document.attachEvent) {<br><b> return function(dom, type, fn) {<br> dom.attachEvent("on" + type, fn);<br> };</b><br> } else {<br><b> return function(dom, type, fn) {<br> dom["on" + type] = fn;<br> };</b><br> }<br><b><font color="#f1753f">})();</font></b><br>
在函数第一次调用的时候对其重定义
<font color="#16884a">减少文件加载时的资源消耗</font>
<b>A.on=function(dom,type,fn)</b>{<br> if (document.addEventListener) {<br><b> A.on= function(dom, type, fn) </b>{<br> dom.addEventListener(type, fn, false);<br> };<br> } else if (document.attachEvent) {<br><b> A.on= function(dom, type, fn) </b>{<br> dom.attachEvent("on" + type, fn);<br> };<br> } else {<br><b> A.on= function(dom, type, fn)</b> {<br> dom["on" + type] = fn;<br> };<br> }<br><b><font color="#924517"> A.on(dom,type,fn)</font></b><br>}<br>// <b>文件加载后 A.on 还没有被重新定义,<br>// 需要等某一元素绑定事件时 A.on 才被重新定义</b><br>A.on(document.body,'click',function(){<br> alert(1)<br>})<br>
等待者模式
Promise.all()
MyPromise.all = function(values) {<br> // <b><font color="#f1753f">1. 要返回一个新的 Promise 实例</font></b><br><b> return new MyPromise((resolve, reject) </b>=> {<br> let result = [];<br><br> // 使用计数器解决多个异步的并发问题<br> let count = 0;<br> //<b><font color="#f1753f"> 2. 返回值按照参数内的 promise 顺序排列</font></b><br><b> function processData(index, value)</b> {<br> result[index] = value;<br> count++;<br><br> // 3. 不能直接判断 result.length 与 values.length 是否相等<br> // 因为可能会给 result 后面的项目先赋值,这样前面异步的项目会变为 <empty>,<br> // 这时虽然前面有异步结果没有返回,但是 result.length 可能就已经和 values.length 相同了<br> if (count === values.length) {<br> resolve(result);<br> }<br> }<br><br> for (let i = 0; i < values.length; i++) {<br> const current = values[i];<br> // <b><font color="#f1753f">4. 利用 Promise.resolve 方法返回一个以给定值解析后的Promise 对象</font></b><br> // MyPromise.resolve(current).then(data => {<br> // processData(i, data);<br> // }, reject); //这里直接写 reject 也能达到效果,<br> //但为啥这样简写也可以不清楚,先按照下面这种常见的写法写<br> MyPromise.resolve(current).then(<br> data => {<br> processData(i, data);<br> },<br> err => {<br> reject(err);<br> }<br> );<br> }<br> });<br>};<br>
<font color="#c41230">Promise.defer <br>解决封装嵌套的问题,<br>意思是不用在外层包装一个 new Promise(function(resolve,reject){<br><br>})</font><br>(这个静态方法js本身是没有的)<br>
let fs =require('fs')<br>function read(url){<br><b><font color="#f1753f"> let dfd=MyPromise.defer()</font></b><br> fs.readFile(url,'utf8',function(err,data){<br> <b><font color="#f1753f"> if(err) dfd.reject(err)<br> dfd.resolve(data)</font></b><br> })<br><b><font color="#f1753f"> return dfd.myPromise;</font></b><br>}<br>read('./name.txt').then(data=>{<br> console.log(data)<br>})<br>
MyPromise.defer = MyPromise.deferred = function() {<br> <font color="#f1753f"> </font><b><font color="#f1753f">let dfd = {};<br> dfd.myPromise = new MyPromise((resolve, reject) => {<br> dfd.resolve = resolve;<br> dfd.reject = reject;</font><br><font color="#924517"> </font><font color="#9f8759"> });<br> </font><font color="#924517"> return dfd</font></b><br>};<br><br>目前的理解:<br><font color="#924517">为了在使用 defer 方法时不用包一层 new Promise((resolve,reject)=>{}),<br>defer 方法内部自己用 new Promise((resolve,reject)=>{}) 包了一层,<br>并将日后要使用到 promise 实例与其 excutor 函数中的 resolve 和 reject 参数<br>都赋值给了 dfd 的相关属性。</font><br>
架构型
模块化
模块管理器
创建
调度
同步模块调度
异步模块调度
同步模块
异步模块
这一章的代码没有跑通,<br>如果需要了解,考虑去看实现了 AMD 规范的 require.js 的源码分析<br>
require.js 源码分析
define
require
1、检查依赖的模块,<font color="#924517">根据配置文件,获取js文件的实际路径</font><br>2、根据js文件实际路径,<font color="#924517">在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知</font><br>3、<font color="#924517">依赖script全部加载完成后,调用回调函数</font><br>
学源码流程
源码如何组织->debug理清调用链->参考别人分析->demo校验->总结
参考
https://blog.csdn.net/zqjflash/article/details/43373957
MV*
合适模式的意义
好代码
功能
灵活<br>高效<br>可扩展
结构
结构清晰
技术准备
为函数自身/函数原型添加方法
函数式调用
methods.checkEmail().checkName();
Function.prototype.addMethod = function(name, fn) {<br> <b>this[name] = fn;</b><br> <b>return this;</b><br>};<br><b>var methods = function() {}</b>;<br>methods.addMethod("checkName", function() {<br> console.log("验证姓名");<br> return this;<br>}).addMethod("checkEmail", function() {<br> console.log("验证邮箱");<br> return this;<br>});<br>
类式调用
var <b>m=new Methods()</b><br>m.checkEmail().checkName()<br>
Function.prototype.addMethod=function(name,fn){<br> <b>this<font color="#f1753f">.prototype</font>[name]=fn</b><br> <b> return this</b><br>}<br><b>var Methods=function(){}</b><br>Methods.addMethod('checkName',function(){<br> console.log('验证姓名')<br> return this<br>}).addMethod('checkEmail',function(){<br> console.log("验证邮箱");<br> return this<br>})<br>
注意事项
链式调用,注意返回自身
<b><font color="#924517">使用类继承时将方法定义在父类的 prototype 上</font></b>;<br>如果放在函数内部通过 this 定义,每 new 一次就要复制一下它自己本身的方法,会造成浪费<br>
<font color="#16884a">实例化类的安全模式</font><br>(忘记使用 new 创建时<br>抛出错误或者处理成正确的结果)<br>
es6
<b>const book=Book('js') //<font color="#924517"> </font><font color="#16884a">es6 语法会自动检测并报错</font></b><br>// const book=new Book('js') <br>console.log(book)<br>
class Book {<br> constructor(title) {<br> this.title = title;<br> }<br>}<br>
es5
var book=Book('js')<br>// var book=new Book('js')<br>console.log(book)<br>
var Book=function(title){<br> <font color="#924517"><b>if(this instanceof Book)</b>{<br> <b>this.title=title<br> }else{<br> return new Book(title)<br> }</b></font><br>}<br>
创建型
处理对象创建的设计模式,通过某种方式控制对象的创建 来避免 基本对象创建时可能导致的设计上问题或复杂度的增加
简单工厂
又叫静态工厂方法,由一个工厂对象决定创建某一种产品对象类的实例。<br>主要用来创建同一类对象。
百科定义
通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
像一个魔术师,<br>告诉他要什么就能变什么<br>
const userNameAlert = createPop("alert", "用户名不能超过26个字母");
function createPop(type, text) {<br><b> const o = new Object();</b><br> o.content = text;<br> o.show = function() {};<br><font color="#924517"> if (type === "alert") {<br> // 警示框差异部分<br> }</font><br> if (type === "prompt") {<br> //...<br> }<br> if (type === "confirm") {<br> //...<br> }<br><b> return o</b>;<br>}<br>
工厂方法
定义一个创建产品对象的工厂接口,<br><b>将实际创建工作推迟到子类当中</b><br>
let uiObj = new Factory("UI", "UI course", "blue");
const Factory = function(type, content,color) {<br><b> if (this instanceof Factory) {<br> <font color="#924517"> let o = new this[type](content);</font><br> this.show(content,color);<br> <font color="#924517">return o;</font><br> } else {<br> return new Factory(type, content);<br> }</b><br>};<br>Factory.prototype = {<br><b> UI: function(content) {<br> this.content = content;<br> },</b><br>Java: function(content) {<br> //...<br> },<br>...<br> show(content,color) {<br> let div = document.createElement("div");<br> div.innerHTML = content;<br> div.style.border = "1px solid " + color;<br> document.getElementById("container").appendChild(div);<br> }<br>};<br>
用 es6 的 class 语法,在父类的 constructor 里调子类,子类里又调 super() 会导致栈溢出
抽象工厂
一般用它作为父类来创建一些子类
建造者模式
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
相比之前的工厂模式,<font color="#924517">建造者模式在创建对象时更加复杂一些</font>,更关心创建这个对象的整个过程,甚至每个细节。
<b><font color="#924517">在建造过程中,通常将创建对象的类模块化</font></b>,使得每一个模块都可以得到灵活的运用与高质量的复用。
class Person {<br> constructor(name, work) {<br> <b> let <font color="#924517">_person = new Human(name)</font></b><font color="#924517">;<br> <b> _person.work = new Work(work)</b>;</font><br> <b> return _person;</b><br> }<br>}<br><br>let p=new Person('lu','code')<br>console.log(p)<br>
class <b>Human</b> {<br> constructor(name) {<br> this.name = name;<br> }<br> getName() {<br> return this.name;<br> }<br>}<br>
class <b>Work</b> {<br> constructor(work) {<br> this.work = work;<br> this.addDes();<br> }<br> addDes() {<br> switch (this.work) {<br> case "code":<br> this.title = "工程师";<br> this.des = "沉醉于编程";<br> }<br> }<br> changeWork(work){<br> this.work=work<br> }<br>}<br>
原型模式
让多个对象分享同一个原型对象的属性与方法
将可复用、共享、耗时大的方法与属性放在原型中,子类继承此原型,将子类中需要重写的方法进行重写
const fadeImg=new FadeLoopImg(<br>['1.jgp','2.jpg'],'slide')<br><br>fadeImg.createImage()<br>fadeImg.changeImage(1)<br>
<b>// 上下滑动切换</b><br>class SlideLoopImg extends LoopImages{<br> changeImage(i){<br> console.log("SlideLoopImg changeImage function");<br> }<br>}<br><b>// 渐隐切换</b><br><b>class FadeLoopImg extends LoopImages</b>{<br><b><font color="#f1753f">// 重写方法<br> changeImage(i)</font></b>{<br> console.log(i)<br> console.log("FadeLoopImg changeImage function");<br> }<br>}<br>
class LoopImages {<br> constructor(imgArr, container) {<br> this.imgArr = imgArr;<br> this.container = container;<br> }<br> createImage() {<br> console.log("LoopImages createImage function");<br> }<br> changeImage(i) {<br> console.log("LoopImages changeImage function");<br> }<br>}<br>
单例模式
只允许实例化一次的对象类
单例对象的延迟创建
let a = lazySingle();<br>let b = lazySingle();<br>console.log(a === b); //true<br>
const lazySingle = (function() {<br> let instance = null;<br> function single() {<br> // 这里定义私有属性与方法<br><br> // 返回公共属性与方法<br> return {<br> publicMethod: function() {},<br> publicProperty: "1.0"<br> };<br> }<br><b> return function() {<br> <font color="#924517">if (!instance) {<br> instance = single();<br> }<br> return instance;</font><br> };</b><br>})();<br>
涉及到的技巧点:<br><ul><li>立即执行函数<br></li><li>高阶函数(这里立即执行函数的返回值是函数)<br></li><li>闭包<br></li></ul>
结构型
关注如何将类或对象组合成更大、更复杂的结构以简化设计
外观模式
向现有的系统添加一个接口,来隐藏系统的复杂性
接口的二次封装一般就是外观模式
dom 事件兼容
function addEvent(dom, type, fn) {<br><b> if (dom.addEventListener)</b> {<br><font color="#924517"> dom.addEventListener(type, fn, false);</font><br> }<b> else if (dom.attachEvent) </b>{<br> // <ie9<br><font color="#924517"> dom.attachEvent("on" + type, fn)</font>;<br> } else {<br><font color="#924517"> dom["on" + type] = fn;</font><br> }<br>}<br>
// 获取事件对象<br>function getEvent(e) {<br><font color="#924517"> // IE 下为 window.event<br> return e || window.e;</font><br>}<br>function getTarget(e) {<br> let e = getEvent(e);<br><font color="#924517"> // IE 下为 event.srcElement<br> return e.target || e.srcElement;</font><br>}<br>function preventDefault(e) {<br> let e = getEvent(e);<br> if (e.preventDefault) {<br><font color="#924517"> e.preventDefault();</font><br> } else {<br><font color="#924517"> e.returnValue = false;</font><br> }<br>}<br>
<font color="#f1753f">可以使用惰性模式,去掉每次执行函数时的特性检测</font>
适配器
将一个类的接口适配成用户所期待的
代理模式
代理模式的关键是,<b>当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问</b>,<br>客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。<br>
虚拟代理实现图片预加载
常见的做法是先用一张 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里
var<b> myImage = (function(){</b><br>var imgNode = document.createElement( 'img' ); <br>document.body.appendChild( imgNode );<br>return {<br> <b> setSrc: function( src ){</b><br> imgNode.src = src; <br> }<br>}<br><b> })();</b><br><br>var <b>proxyImage = (function(){ </b><br><font color="#924517">var img = new Image; </font><br><font color="#f15a23" style="font-weight: bold;">img.onload = function(){<br> </font><font color="#924517">myImage.setSrc( this.src ); // 这里的this指 img 对象</font><br><font color="#f15a23" style="font-weight: bold;">}</font><br>return {<br>setSrc: function( src ){<br> <font color="#924517"> myImage.setSrc( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' );</font><br><b> </b><b><font color="#f15a23"> img.src = src; </font></b><br>}<br>} <br><b>})();</b><br><br><b>proxyImage.setSr</b>c( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );<br>
装饰者模式
在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法),使原有对象可以满足用户更加复杂的需求
<font color="#c41230">AOP 面向切片编程<br></font>Aspect Oriented <br>Programming<br>
主要作用
把一些和核心业务逻辑模块无关的功能抽离出来,<br><font color="#16884a">给函数增加一层,不用管内部实现</font><br>
一般会将原始方法保存起来,在运行原始方法的前后进行一些其他操作
示例
<font color="#924517">给原始函数加方法</font>
<b>Function.<font color="#0076b3">prototype</font>.before = function(beforeFunc)</b> {<br> <b>// 这里的 this 是调用 before 的函数</b> <br> // 在 prototype 上添加方法,可以通过 this 获取原始函数,<br> // 就不用保存原始函数在一个变量里了<br> <br> // <b>剩余参数,将所有参数合成一个数组</b><br> <b>return (...args) => {<br> beforeFunc();<br> <br> // 展开运算符<br> this(...args)<br> };</b><br>};<br><br>function say(who) {<br> //todo<br> console.log(who+"说话");<br>}<br><br>let <b>newFn = say.before(function() {<br> console.log("说话前");<br>});</b><br>newFn('我');<br>
<b>Vue 2.0 函数劫持<br>重写原生方法</b><br>
<b>// 这里要<font color="#924517">先将原函数保存在一个变量里</font></b><br>let oldPush=Array.prototype.push<br>function push(...args){<br> console.log('数据更新')<br> oldPush.call(this,...args)<br>}<br>let arr=[1,2,3]<br>push.call(arr,4,5,6)<br>console.log(arr)<br>
源码
// vue-2.6/src/core/observer/array.js<br><br>methodsToPatch.forEach(function(method) {<br><b> // cache original method</b><br><b> const original = arrayProto[method];</b><br> def(arrayMethods, method, function mutator(...args) {<br><b> const result = original.apply(this, args);</b><br> const ob = this.__ob__;<br> let inserted;<br> switch (method) {<br> case "push":<br> case "unshift":<br> inserted = args;<br> break;<br> case "splice":<br> inserted = args.slice(2);<br> break;<br> }<br> if (inserted) ob.observeArray(inserted);<br> // notify change<br><b> ob.dep.notify();<br> return result;</b><br> });<br>});<br>
<b>react 事务</b><br>(<b>这里需要了解 wrapper 的内部结构,<br>在其结构中间插入其他功能</b>,就像给饼干加了夹心,<br>所以<b><font color="#924517">这是AOP,但不属于装饰者模式</font></b>)<br>
<b>function perform(anyMethod, wrappers) {<br> return function() {<br> wrappers.forEach(wrapper => <font color="#924517">wrapper.initialize()</font>);<br> <font color="#924517"> anyMethod();</font><br> wrappers.forEach(wrapper => <font color="#924517">wrapper.close()</font>);<br> };<br>}</b><br><br>let newFn = perform(<br> function() {<br> console.log("say");<br> },<br> [<br> {<br> // wrapper1<br> initialize() {<br> console.log("wrapper1 beforesay");<br> },<br> close() {<br> console.log("wrapper1 close");<br> }<br> },<br> {<br> // wrapper2<br> initialize() {<br> console.log("wrapper2 beforesay");<br> },<br> close() {<br> console.log("wrapper2 close");<br> }<br> }<br> ]<br>);<br><br>newFn();<br>
桥接模式
用于将抽象化与实现化解耦分离,使得二者可以独立变化。<br>通过<b>提供抽象化和实现化之间的桥接结构,来实现二者的解耦</b>。<br>
组合模式
<font color="#924517">将对象组合成树形结构</font><font color="#9f8759">以表示"部分-整体"的层次结构</font>
用小的子对象来构建更大的对象,而这些小的子对象本身也许是由更小的“孙对象”构成的。
作用/何时使用
<font color="#16884a">用来表示树形结构/部分-整体结构</font><br>
组合模式<b>可以方便地构造一棵树来表示对象的部分-整体结构。</b><br><b><font color="#924517">在开发期间不确定这棵树到底存在多少层次的时候组合模式尤其适用</font></b>。<br>在树的构造最终完成之后,<b>只需要通过请求树的最顶层对象,便能对整棵树做统一的操作</b>。<br>在组合模式中增加和删除树的节点非常方便,并且符合开放-封闭原则。<br>
通过对象的多态性表现,<br>使得<font color="#16884a">用户对单个对象和组合对象的使用具有一致性</font><br>
<b><font color="#924517">客户希望统一对待树中的所有对象时可以考虑使用此模式。</font></b><br>组合模式使客户可以忽略组合对象和叶对象的区别, <br><b>客户在面对这棵树的时候,不用关心当前正在处理的对象是组合对象还是叶对象,也就不用写一堆 if、else 语句来分别处理它们。</b><br><b><font color="#9f8759">组合对象和叶对象会各自做自己正确的事情</font>, 这是组合模式最重要的能力。</b><br>
请求在树中传递的过程
请求从树最顶端的对象往下传递;<br>如果当前处理请求的对象是叶对象,叶对象自身会对请求作出相应的处理;<br>如果当前处理请求的对象是组合对象, 组合对象则会遍历它属下的子节点,将请求继续传递给这些子节点。<br>
<b>组合对象可以拥有子节点,叶对象下面就没有子节点。</b><br>因为用户对单个对象和组合对象的使用具有一致性,所以我们也许会发生一些误操作, 比如试图往叶对象中添加子节点。<br>解决方案通常是<b><font color="#f1753f">给叶对象也增加 add 方法,并且在调用这个方法时,抛出一个异常来及时提醒客户</font></b><br>
示例:扫描文件夹
文件夹里既可以包含文件,又可以包含其他文件夹,最终可能组合成一棵树
组合模式在文件夹的应用中的好处
我在同事的移动硬盘里找到了一些电子书,想把它们复制到 F 盘中的学习资料文 件夹。在复制这些电子书的时候,我并不需要考虑这批文件的类型,不管它们是单独的 电子书还是被放在了文件夹中。组合模式让 Ctrl+V、Ctrl+C 成为了一个统一的操作。
当我用杀毒软件扫描该文件夹时,往往不会关心里面有多少文件和子文件夹,组合模式 使得我们只需要操作最外层的文件夹进行扫描。
Folder 类
class Folder {<br> constructor(name) {<br> this.name = name;<br> this.files = [];<br> }<br><b><font color="#924517"> add(file) {<br> this.files.push(file);<br> }</font></b><br><b> scan()</b> {<br> console.log("开始扫描文件夹: " + this.name);<br> <b> for (let i = 0, file, files = this.files; (file = files[i]); i++) {<br> file.scan();<br> }</b><br> }<br>}<br>
File 类
class File {<br> constructor(name) {<br> this.name = name;<br> }<br><b><font color="#f1753f"> add() {<br> throw new Error("文件夹下面不能再添加文件");<br> }</font></b><br><b> scan()</b> {<br> console.log("开始扫描文件: " + this.name);<br> }<br>}<br>
创建一些文件夹和文件对象,<br> 并且让它们组合成一棵树<br>
let folder = new Folder("学习资料");<br>let folder1 = new Folder("JavaScript");<br>let folder2 = new Folder("jQuery");<br><br>let file1 = new File("JavaScript 设计模式与开发实践");<br>let file2 = new File("精通 jQuery");<br>let file3 = new File("重构与模式");<br><br>folder1.add(file1);<br>folder2.add(file2);<br><br>folder.add(folder1);<br>folder.add(folder2);<br>folder.add(file3);<br>
把移动硬盘里的文件和文件夹<br>都复制到这棵树<br>
移动硬盘中的文件对象:
let folder3 = new Folder("Nodejs");<br>let file4 = new File("深入浅出 Node.js");<br>folder3.add(file4);<br>let file5 = new File("JavaScript 语言精髓与编程实践");<br>
把这些文件添加<br>到原有树中:<br>
<font color="#924517">folder.add( folder3 ); <br>folder.add( file5 );</font><br>
<b>在添加一批文件的操作过程中,客户不用分辨它们到底是文件还是文件夹</b>。<br>新增加的文件和文件夹<b>能够很容易地添加到原来的树结构中,和树里已有的对象一起工作。</b><br>
运用了组合模式之后,<b>扫描整个文件夹的操作也是轻而易举的</b>,<br>我们<b>只需要操作树的最顶端对象</b><br>
folder.scan();
⚠️ 注意
组合对象把请求委托给它所包含的所有叶对象,<b>它们能够合作的<font color="#f1753f">关键是拥有相同的接口</font>。</b><br><b><font color="#924517">如文件夹例子里的 add 和 scan 接口</font></b>。
享元模式
运用共享技术有效支持大量细粒度的对象,避免对象间拥有相同内容造成多余的开销
该模式中<b>会将可以共享的单元缓存起来</b>,<br>以后要用到此类单元时,先检查是缓存中否有已经建立好的单元,<br>有就直接返回缓存中的单元,没有就新建此特定类型的单元并缓存<br>
翻页优化
const <b><font color="#16884a">shareUnit</font></b> =<b> (function() {</b><br><b> const created = [];</b><br> let num=5;<br> function create() {<br> let dom = document.createElement("div");<br> document.getElementById("container").appendChild(dom);<br> <b>created.push(dom);</b><br> return dom;<br> }<br> return {<br><b><font color="#16884a"> getDiv</font></b>: function(i) {<br> <b><font color="#924517">if (created.length < num) {<br> return create();<br> } else {<br> return created[i]<br> }</font></b><br> }<br> };<br><b>})();</b><br>
// 初始化列表<br>const articles = [<br> "11",<br> "22",<br> "33",<br> "44",<br> "55",<br> "66",<br> "77",<br> "88",<br> "99",<br> "100",<br> "101",<br> "102"<br>];<br><br>let page = 0,<br> num = 5,<br> len = articles.length;<br>for (let i = 0; i < num; i++) {<br> if (articles[i]) {<br> <font color="#16884a"> </font><b><font color="#16884a">shareUnit.getDiv(i)</font>.innerHTML = articles[i];</b><br> }<br>}<br>
// 翻页监听<br>document.getElementById("next_page").addEventListener("click", function() {<br> let n = ++page * num,<br> j = 0;<br><br> if (!articles[n]) return;<br> for (; j < num; j++) {<br> if (articles[n + j]) {<br> <b><font color="#16884a">shareUnit.getDiv(j)</font>.innerHTML = articles[n + j];</b><br> } else {<br> shareUnit.getDiv(j).innerHTML = "";<br> }<br> }<br>});<br>
参考
JavaScript 设计模式 张容铭
JavaScript 设计模式与开发实践 曾探
0 条评论
下一页