• js中的设计模式


    首先了解一下设计原则:

    单一职责原则(SRP):一个对象或一个方法只做一件事情。如果一个方法承担了过多的事情,那么在需求更改的时候,需要改写这个方法的可能性就越大。应该把对象或方法划分为更小的粒度。

    最少知识原则(LKP):一个软件实体,应该尽可能少的与其他实体发生相互作用。应当尽量减少两个对象之间的交互,如果不是必要的直接关系,最好通过第三方进行处理。

    开发-封闭原则(OCP):软件实体(类、函数、模块),只能为其扩展,不能更改。当需要改变一个程序的功能或为其增添新的功能时,可以通过增加代码的方式,尽量避免修改源代码,防止影响原系统的稳定。

    如promise中,每一个then中做一件事,当有新的需求时,在后面添加更多的then,而不是修改之前的代码

    下面介绍一些常用的设计模式

    一、单例模式

    确保一个类,仅有一个实例,且提供了一个全局访问点

    如:有一个manager类,即使多次调用构造函数也仅创建一个manager

    // 构造函数
    function
    setManager(name) { this.manager = name; }

    // 向原型上添加方法 setManager.prototype.getName
    = function () { console.log(this.manager); }

    // 创建单例manager的方法,仅当manager不存在时,创建新的manager,最后返回
    var singletonSetManager = (function () { var manager = null; return function (name) { if (!manager) { manager = new setManager(name); } return manager; }; })();

    然而,以上的方法仅能实现manager单例的需求,如果此时需要实现一个hr单例需求呢?

     因此,将单例的实现方法进行抽取

    // 将创建单例的方法当作参数传入,单例不存在时,通过apply调用。最后将单例返回
    function
    singletonSetInstance(fn) { var instance = null; return function () { if (!instance) { instance = fn.apply(this, arguments); } return instance; }; }

    之后,对于想要创建单例的类,仅需调用以上方法,传入函数,得到关于该类的单例

    function setManager(name) {
            this.manager = name;
    }
    setManager.prototype.getName = function () {
            console.log(this.manager);
    };
    
    var getSingleManager = singletonSetInstance(function (name) {
            var manager = new setManager(name);
            return manager;
    });
    function setHr(name) {
            this.hr = name;
    }
    
    setHr.prototype.getName = function () {
            console.log(this.hr);
    };
    
    var getSingleHr = singletonSetInstance(function (name) {
            var hr = new setHr(name);
            return hr;
    });
    
    getSingleManager("m1").getName(); // m1
    getSingleManager("m2").getName(); // m1
    
    getSingleHr("h1").getName(); // h1
    getSingleHr("h2").getName(); // h1

    二、策略模式

    策略模式将一系列算法汇总到策略集中,根据不同情况进行调用。将算法的实现和使用分隔开来

    一个基于策略模式的程序至少由两部分组成:

    第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。

    第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context 中要维持对某个策略对象的引用

    例如需要根据学生的成绩等级,对应分数进行加权

    var levelMap = {
            S: 10,
            A: 8,
            B: 6,
            C: 4,
          };
    
    var setScore = {
            basicScore: 80,
            S: function () {
              return this.basicScore + levelMap["S"];
            },
            A: function () {
              return this.basicScore + levelMap["A"];
            },
            B: function () {
              return this.basicScore + levelMap["B"];
            },
            C: function () {
              return this.basicScore + levelMap["C"];
            },
    };
    
    function getScore(level) {
            return setScore[level] ? setScore[level]() : 0;
    }
    
    console.log(getScore("S")); // 90
    console.log(getScore("A")); // 88
    console.log(getScore("B")); // 86
    console.log(getScore("C")); // 84

    策略模式经常用在对表单的验证中:

    <script>
          var errMsgs = {
            default: "输入数据格式不正确",
            minLength: "输入数据长度不足",
            isNumber: "请输入数字",
            required: "内容不能为空",
          };
    
          var rules = {
            minLength: function (value, length, errMsg) {
              if (value.length < length) {
                return errMsg || errMsgs["minLength"];
              }
            },
            isNumber: function (value, errMsg) {
              if (!/^d+$/.test(value)) {
                return errMsg || errMsgs["isNumber"];
              }
            },
            required: function (value, errMsg) {
              if (value === "") {
                return errMsg || errMsgs["required"];
              }
            },
          };
    
          function Validators() {
            this.items = [];
          }
    
          Validators.prototype = {
            constructor: Validators,
            add: function (value, rule, errMsg) {
              var arg = [value];
              if (rule.indexOf("minLength") != -1) {
                var temp = rule.split(":");
                arg.push(temp[1]);
                rule = temp[0];
              }
              arg.push(errMsg);
              this.items.push(function () {
                return rules[rule].apply(this, arg);
              });
            },
            start: function () {
              for (var i = 0; i < this.items.length; ++i) {
                var ret = this.items[i]();
                if (ret) {
                  console.log(ret);
                }
              }
            },
          };
    
          var validate = new Validators();
          validate.add("111s", "isNumber", "输入内容只能是数字");
          validate.add("1", "minLength:5");
          validate.add("", "required");
          validate.start();
        </script>

    三、代理模式

    当客户不方便直接访问一个 对象或者不满足需要的时候,提供一个替身对象 来控制对这个对象的访问,客户实际上访问的是 替身对象。

    替身对象对请求做出一些处理之后, 再把请求转交给本体对象

    代理模式主要有三种:保护代理、虚拟代理、缓存代理

    保护代理主要实现了访问主体的限制行为,以过滤字符作为简单的例子

    // 主体,发送消息
    function sendMsg(msg) {
        console.log(msg);
    }
    
    // 代理,对消息进行过滤
    function proxySendMsg(msg) {
        // 无消息则直接返回
        if (typeof msg === 'undefined') {
            console.log('deny');
            return;
        }
        
        // 有消息则进行过滤
        msg = ('' + msg).replace(/泥s*煤/g, '');
    
        sendMsg(msg);
    }
    
    
    sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀
    proxySendMsg('泥煤呀泥 煤'); //
    proxySendMsg(); // deny

    它的意图很明显,在访问主体之前进行控制,没有消息的时候直接在代理中返回了,拒绝访问主体,这数据保护代理的形式

    有消息的时候对敏感字符进行了处理,这属于虚拟代理的模式

    虚拟代理在控制对主体的访问时,加入了一些额外的操作

    在滚动事件触发的时候,也许不需要频繁触发,我们可以引入函数节流,这是一种虚拟代理的实现

    // 函数防抖,频繁操作中不处理,直到操作完成之后(再过 delay 的时间)才一次性处理
    function debounce(fn, delay) {
        delay = delay || 200;
        
        var timer = null;
    
        return function() {
            var arg = arguments;
              
            // 每次操作时,清除上次的定时器
            clearTimeout(timer);
            timer = null;
            
            // 定义新的定时器,一段时间后进行操作
            timer = setTimeout(function() {
                fn.apply(this, arg);
            }, delay);
        }
    };
    
    var count = 0;
    
    // 主体
    function scrollHandle(e) {
        console.log(e.type, ++count); // scroll
    }
    
    // 代理
    var proxyScrollHandle = (function() {
        return debounce(scrollHandle, 500);
    })();
    
    window.onscroll = proxyScrollHandle;

    缓存代理可以为一些开销大的运算结果提供暂时的缓存,提升效率

    来个栗子,缓存加法操作

    // 主体
    function add() {
        var arg = [].slice.call(arguments);
    
        return arg.reduce(function(a, b) {
            return a + b;
        });
    }
    
    // 代理
    var proxyAdd = (function() {
        var cache = [];
    
        return function() {
            var arg = [].slice.call(arguments).join(',');
            
            // 如果有,则直接从缓存返回
            if (cache[arg]) {
                return cache[arg];
            } else {
                var ret = add.apply(this, arguments);
                return ret;
            }
        };
    })();
    
    console.log(
        add(1, 2, 3, 4),
        add(1, 2, 3, 4),
    
        proxyAdd(10, 20, 30, 40),
        proxyAdd(10, 20, 30, 40)
    ); // 10 10 100 100

    四、迭代器模式

    1. 定义

    迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。

    2. 核心

    在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素

    3. 实现

    JS中数组的map forEach 已经内置了迭代器

    [1, 2, 3].forEach(function(item, index, arr) {
        console.log(item, index, arr);
    });

    不过对于对象的遍历,往往不能与数组一样使用同一的遍历代码

    我们可以封装一下

    function each(obj, cb) {
        var value;
    
        if (Array.isArray(obj)) {
            for (var i = 0; i < obj.length; ++i) {
                value = cb.call(obj[i], i, obj[i]);
    
                if (value === false) {
                    break;
                }
            }
        } else {
            for (var i in obj) {
                value = cb.call(obj[i], i, obj[i]);
    
                if (value === false) {
                    break;
                }
            }
        }
    }
    
    each([1, 2, 3], function(index, value) {
        console.log(index, value);
    });
    
    each({a: 1, b: 2}, function(index, value) {
        console.log(index, value);
    });
    
    // 0 1
    // 1 2
    // 2 3
    
    // a 1
    // b 2
    复制代码

    再来看一个例子,强行地使用迭代器,来了解一下迭代器也可以替换频繁的条件语句

    虽然例子不太好,但在其他负责的分支判断情况下,也是值得考虑的

    复制代码
    function getManager() {
        var year = new Date().getFullYear();
    
        if (year <= 2000) {
            console.log('A');
        } else if (year >= 2100) {
            console.log('C');
        } else {
            console.log('B');
        }
    }
    
    getManager(); // B
    复制代码

    将每个条件语句拆分出逻辑函数,放入迭代器中迭代

    function year2000() {
        var year = new Date().getFullYear();
    
        if (year <= 2000) {
            console.log('A');
        }
    
        return false;
    }
    
    function year2100() {
        var year = new Date().getFullYear();
    
        if (year >= 2100) {
            console.log('C');
        }
    
        return false;
    }
    
    function year() {
        var year = new Date().getFullYear();
    
        if (year > 2000 && year < 2100) {
            console.log('B');
        }
    
        return false;
    }
    
    function iteratorYear() {
        for (var i = 0; i < arguments.length; ++i) {
            var ret = arguments[i]();
    
            if (ret !== false) {
                return ret;
            }
        }
    }
    
    var manager = iteratorYear(year2000, year2100, year); // B

    五、发布-订阅模式

    也称作观察者模式,定义了对象间的一种一对多的依赖关系,当一个对象的状态发 生改变时,所有依赖于它的对象都将得到通知

    与传统的发布-订阅模式实现方式(将订阅者自身当成引用传入发布者)不同,在JS中通常使用注册回调函数的形式来订阅

    小A在公司C完成了笔试及面试,小B也在公司C完成了笔试。他们焦急地等待结果,每隔半天就电话询问公司C,导致公司C很不耐烦。

    一种解决办法是 AB直接把联系方式留给C,有结果的话C自然会通知AB

    这里的“询问”属于显示调用,“留给”属于订阅,“通知”属于发布

    <script>
          var observer = {
            // 订阅集合
            subscribes: [],
    
            // 订阅
            subscribe: function (type, fn) {
              // 如果不存在处理此类型事件的订阅数组,进行初始化
              if (!this.subscribes[type]) {
                this.subscribes[type] = [];
              }
              typeof fn === "function" && this.subscribes[type].push(fn);
            },
    
            // 发布
            publish: function () {
              var type = [].shift.call(arguments);
              var fns = this.subscribes[type];
              // 如果不存在该类型的处理函数
              if (!fns || !fns.length) {
                return;
              }
    
              // 对于该类型的事件分别调用
              for (var i = 0; i < fns.length; ++i) {
                fns[i].apply(this, arguments);
              }
            },
    
            remove: function (type, fn) {
              // 删除全部
              if (typeof type === "undefined") {
                this.subscribes = [];
                return;
              }
    
              var fns = this.subscribes[type];
    
              if (!fns || !fns.length) {
                return;
              }
              //删除所有该类型的处理函数
              if (typeof fn === "undefined") {
                this.subscribes[type] = [];
                return;
              }
              for (var i = 0; i < fns.length; ++i) {
                if (fns[i] === fn) {
                  fns.splice(i, 1);
                }
              }
            },
          };
    
          function jobA(jobs) {
            console.log("jobList for A", jobs);
          }
    
          function jobB(jobs) {
            console.log("jobList for B", jobs);
          }
    
          observer.subscribe("job", jobA);
          observer.subscribe("job", jobB);
          observer.subscribe("examA", function () {
            console.log("100");
          });
          observer.subscribe("examB", function () {
            console.log("99");
          });
          observer.subscribe("interviewA", function () {
            console.log("通过");
          });
    
          observer.publish("job", ["前端开发", "设计师", "产品经理"]);
          observer.publish("examA");
          observer.publish("examB");
          observer.publish("interviewA");
          observer.remove("job", jobA);
          observer.publish("job", ["咖啡师", "前台", "店长"]);
        </script>

    六、组合模式

    1. 定义

    是用小的子对象来构建更大的 对象,而这些小的子对象本身也许是由更小 的“孙对象”构成的。

    2. 核心

    可以用树形结构来表示这种“部分- 整体”的层次结构。

    调用组合对象 的execute方法,程序会递归调用组合对象 下面的叶对象的execute方法

    但要注意的是,组合模式不是父子关系,它是一种HAS-A(聚合)的关系,将请求委托给 它所包含的所有叶对象。基于这种委托,就需要保证组合对象和叶对象拥有相同的 接口

    此外,也要保证用一致的方式对待 列表中的每个叶对象,即叶对象属于同一类,不需要过多特殊的额外操作

    3. 实现

    使用组合模式来实现扫描文件夹中的文件

    <script>
          function Folder(name) {
            this.name = name;
            this.files = [];
            this.parent = null;
          }
    
          Folder.prototype = {
            constructor: Folder,
            add: function (file) {
          // 返回this,实现链式调用 file.parent
    = this; this.files.push(file); return this; }, scan: function () {
          // 传给叶子执行
    for (var i = 0; i < this.files.length; ++i) { this.files[i].scan(); } }, remove(file) { // 删除全部 if (typeof file === "undefined") { this.files = []; return; } for (var i = 0; i < this.files.length; ++i) { if (this.files[i] === file) { this.files.splice(i, 1); } } }, }; function File(name) { this.name = name; this.parent = null; } File.prototype = { constructor: File, add: function () { console.log("文件中不能添加文件"); }, scan: function () { var name = [this.name]; var parent = this.parent; while (parent) { name.unshift(parent.name); parent = parent.parent; } console.log(name.join(" / ")); }, }; var projects = new Folder("projects"); var mainweb = new Folder("mainweb"); var center = new Folder("center"); var src = new Folder("src"); var index = new File("index.html"); var app = new File("app.js"); var main = new File("main.js"); var home = new File("home.html"); var utils = new File("utils.js"); projects.add(mainweb).add(center); // projects / center / src / index.html center.add(src); // projects / center / src / utils.js src.add(index).add(utils); // projects / center / app.js center.add(app).add(main); // projects / center / main.js projects.add(home); // projects / home.html projects.scan(); </script>

     七、命令模式

    命令模式,就是将一系列命令添加到类中,通过包装实例对象的命令为对象,就可以随意通过实例对象发出命令,并按情况将命令压入栈中。通常命令模式都有Redo(重做)、undo(撤销)和execute(执行)三种命令

    以下代码示例,实现了自增命令,包含撤销和重做

    <script>
          function Increment() {
            // 自加栈为空
            this.stack = [];
            // 初始时,指针指向-1
            this.stackPosition = -1;
            // 初始值为0
            this.val = 0;
          }
    
          Increment.prototype = {
            // 执行命令
            execute: function () {
              this._cleanUedo();
    
              // 定义自加命令
              var command = function () {
                this.val += 2;
              }.bind(this);
              // 执行
              command();
              // 缓存
              this.stack.push(command);
              // 指针后移
              this.stackPosition++;
              this.getValue();
            },
            // 判断是否可以重做
            canRedo: function () {
              return this.stackPosition < this.stack.length - 1;
            },
            canUndo: function () {
              return this.stackPosition >= 0;
            },
            redo: function () {
              if (!this.canRedo()) {
                return;
              }
              // 执行当前指针后一位的命令
              this.stack[++this.stackPosition]();
              this.getValue();
            },
            undo: function () {
              if (!this.canUndo()) {
                return;
              }
              var command = function () {
                this.val -= 2;
              }.bind(this);
              command();
              // 撤销命令不需缓存,指针向前移一位,自加命令依然在栈中
              this.stackPosition--;
              this.getValue();
            },
            getValue() {
              console.log(this.val);
            },
            _cleanUedo() {
              // 撤销的命令不再执行
              this.stack = this.stack.slice(0, this.stackPosition + 1);
            },
          };
    
          var increment = new Increment();
          var eventTriggle = {
            execute: function () {
              increment.execute();
            },
            undo: function () {
              increment.undo();
            },
            redo: function () {
              increment.redo();
            },
          };
    
          eventTriggle.execute(); // 2
          eventTriggle.execute(); // 4
          eventTriggle.execute(); // 6
          eventTriggle.execute(); // 8
          eventTriggle.undo(); // 6
          eventTriggle.undo(); // 4
          eventTriggle.undo();// 2 
          eventTriggle.undo(); // 0
          eventTriggle.undo(); // 无输出
          eventTriggle.redo(); // 2
          eventTriggle.redo(); // 4
          eventTriggle.redo(); // 6
          eventTriggle.redo(); // 8
          eventTriggle.redo();// 无输出
        </script>

    当然,以上只针对自加命令做了处理,当命令增多时,可以将命令抽取,调用execute时,将命令当作参数传入,压入栈中。如下:

    <script>
          var MacroCommand = {
            commands: [],
            add: function (command) {
              this.commands.push(command);
              return this;
            },
            remove: function (command) {
              // 当不不传参时,删除所有命令
              if (!command) {
                this.commands = [];
                return;
              }
              for (var i = 0; i < this.commands.length; i++) {
                if (this.commands[i] === command) {
                  this.commands.splice(i, 1);
                }
              }
            },
            execute: function () {
              if (this.commands.length === 0) {
                return;
              }
              for (var i = 0; i < this.commands.length; i++) {
                this.commands[i].execute();
              }
            },
          };
    
          var showName = {
            execute: function () {
              console.log("ashen");
            },
          };
          var showGendle = {
            execute: function () {
              console.log("female");
            },
          };
    
          MacroCommand.add(showName).add(showGendle);
          MacroCommand.execute();
    </script>

    八、模板方法模式

    模板方法模式由抽象父类和具体的实现子类组成

    在抽象父类中封装子类的算法框架,它的 init方法可作为一个算法的模板,指导子类以何种顺序去执行哪些方法。

    由父类分离出公共部分,要求子类重写某些父类的(易变化的)抽象方法

    模板方法模式一般的实现方式为继承

    以运动为例,如下

    <script>
          function Sport() {}
    
          Sport.prototype = {
            init: function () {
              this.strech();
              this.jog();
              this.deepBreath();
              this.start();
              this.free = this.end();
              if (this.free) {
                this.strech();
              }
            },
    
            strech: function () {
              console.log("先拉伸一下肌肉");
            },
            jog: function () {
              console.log("再慢跑一会,热热身");
            },
            deepBreath: function () {
              console.log("跑完深呼吸~");
            },
            start: function () {
              throw new Error("子类必须改写此方法");
            },
            end: function () {
              console.log("运动结束");
            },
          };
    
          function Run() {}
          Run.prototype = new Sport();
          Run.prototype.start = function () {
            console.log("每天跑个半小时");
          };
          Run.prototype.end = function () {
            console.log("跑完要回去虐腹,先走啦!");
            return false;
          };
    
          function Zumba() {}
          Zumba.prototype = new Sport();
    
          var run = new Run();
          var zumba = new Zumba();
          run.init();
          zumba.init();
        </script>

    执行结果如下:

    九、享元模式

    享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在javascript中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情。

    十、职责链模式

    使多个对象都有机会处理请求,从而避免请求的发送者和请求的接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

    请求发送者只需要知道链中的第一个节点,弱化发送者和一组接收者之间的强联系,可以便捷地在职责链中增加或删除一个节点,同样地,指定谁是第一个节点也很便捷。

    如下,实现了一个简单的判断数据类型的职责链

     <script>
          function ChainItem(fn) {
            this.fn = fn;
            this.next = null;
          }
    
          ChainItem.prototype = {
            constructor: ChainItem,
            setNext: function (next) {
              this.next = next;
              return next;
            },
            start: function () {
              this.fn.apply(this, arguments);
            },
            toNext: function () {
              if (this.next) {
                this.start.apply(this.next, arguments);
              } else {
                console.log("无匹配的执行项目");
              }
            },
          };
    
          function showNumber(num) {
            if (typeof num === "number") {
              console.log("number", num);
            } else {
              this.toNext(num);
            }
          }
    
          function showString(str) {
            if (typeof str === "string") {
              console.log("string", str);
            } else {
              this.toNext(str);
            }
          }
    
          function showObject(obj) {
            if (typeof obj === "object") {
              console.log("object", obj);
            } else {
              this.toNext(obj);
            }
          }
    
          var numberItem = new ChainItem(showNumber);
          var stringItem = new ChainItem(showString);
          var objectItem = new ChainItem(showObject);
    
          numberItem.setNext(stringItem).setNext(objectItem);
          numberItem.start({ name: "ashen" }); // object {name: "ashen"}
        objectItem.start("str"); // 无匹配的执行项目
    </script>

    当需要向其中加入判断是否undefined也很容易,如下

    function showUndefined(un) {
            if (typeof un === "undefined") {
              console.log("undefined", un);
            } else {
              this.toNext(un);
            }
    }
    
    var undefinedItem = new ChainItem(showUndefined);
    objectItem.setNext(undefinedItem); // 可以添加到任意一个节点后
    numberItem.start(); // 可以从任意节点开始 undefined undefined

    十一、中介者模式

    <script>
          var A = {
            score: 100,
            changeTo: function (score) {
              this.score = score;
              this.getRank();
            },
            getRank: function () {
              var scores = [this.score, B.score, C.score].sort((a, b) => {
                return a < b;
              });
              console.log(scores.indexOf(this.score) + 1);
            },
          };
    
          var B = {
            score: 90,
            changeTo: function (score) {
              this.score = score;
              rankMediator(B);
            },
          };
    
          var C = {
            score: 80,
            changeTo: function (score) {
              this.score = score;
              rankMediator(C);
            },
          };
    
          function rankMediator(person) {
            var scores = [A.score, B.score, C.score].sort();
            console.log(scores);
            console.log(scores.indexOf(person.score) + 1);
          }
    
          A.changeTo(120);
          B.changeTo(150);
          C.changeTo(130);
        </script>

    以上例子中,A 通过自身的函数,拿到B、C的成绩进行排名,而B和C通过中介者rankMediator进行排名,减少了多对象间的相互引用

    十二、装饰者模式

    以动态地给某个对象添加一些额外的职责,而不会影响从这个类中派生的其他对象。
    是一种“即用即付”的方式,能够在不改变对 象自身的基础上,在程序运行期间给对象动态地 添加职责
    是为对象动态加入行为,经过多重包装,可以形成一条装饰链
    最简单的装饰者模式。就是重写对象的属性
    var person = {
        name: 'ashen',
    }
    
    function decorator(){
        console.log(person.name + '1999');
    }

    还可以通过传统的面向对象实现

    function Person() {}
          Person.prototype.skill = function () {
            console.log("唱歌");
          };
    
          function CodeDecorator(person) {
            this.person = person;
          }
    
          CodeDecorator.prototype.skill = function () {
            this.person.skill();
            console.log("敲代码");
          };
    
          function DanceDecorator(person) {
            this.person = person;
          }
    
          DanceDecorator.prototype.skill = function () {
            this.person.skill();
            console.log("跳舞");
          };
    
          var person = new Person();
          var person1 = new Person();
    
          person1 = new CodeDecorator(person1);
          person1 = new DanceDecorator(person1);
          person1.skill(); // 唱歌 敲代码 跳舞

    在JS中,函数为一等对象,所以我们也可以使用更通用的装饰函数

    function decorateBefore(fn, beforeFn) {
            return function () {
              var ret = beforeFn.apply(this, arguments);
              if (ret !== false) {
                fn.apply(this, arguments);
              }
            };
          }
    
          function skill() {
            console.log("说话");
          }
    
          function skillEat() {
            console.log("吃饭");
          }
    
          function skillSleep() {
            console.log("睡觉");
          }
    
          var getSkill = decorateBefore(skill, skillEat);
          getSkill = decorateBefore(getSkill, skillSleep);
          getSkill(); // 睡觉 吃饭 说话

    十三、适配器模式

    是解决两个软件实体间的接口不兼容的问题,对不兼容的部分进行适配。

    例如下面的数据类型转换的适配器

    <script>
          // 限制只能传入数组
          function render(data) {
            data.forEach((item) => {
              console.log(item);
            });
          }
    
          //   数据格式适配器
          function adapter(data) {
            if (typeof data !== "object") {
              // 数据不可迭代
              return [];
            }
            // 如果是数组,直接返回
            if (Object.prototype.toString.call(data) === "[Object Array]") {
              return data;
            }
            // 如果是对象,进行迭代,转换为数组
            var temp = [];
            for (var item in data) {
              if (data.hasOwnProperty(item)) {
                temp.push(data[item]);
              }
            }
            return temp;
          }
    
          var data = {
            name: "ashen",
            age: 21,
            gender: "female",
          };
    
          var str = "asharren";
          var arr = ["一小", "三中", "一中"];
    
          render(adapter(data));
          render(adapter(str));
          render(adapter(arr));
        </script>
  • 相关阅读:
    原型设计工具比较及实践
    原型设计工具比较及实践
    原型设计工具比较及实践
    原型设计工具比较及实践
    原型设计工具比较及实践
    原型设计工具比较及实践
    原型设计工具比较及实践
    软件工程基础大作业感想
    博客园里留下你的成长足迹
    软件工程培训—粗读《构建之法》
  • 原文地址:https://www.cnblogs.com/ashen1999/p/12942696.html
Copyright © 2020-2023  润新知