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