33 实现工具函数

1 对象扁平化

    function objectFlat(obj = {}) {
      const res = {}
      function flat(item, preKey = '') {
        Object.entries(item).forEach(([key, val]) => {
          const newKey = preKey ? `${preKey}.${key}` : key
          if (val && typeof val === 'object') {
            flat(val, newKey)
          } else {
            res[newKey] = val
          }
        })
      }
      flat(obj)
      return res
    }
    
    // 测试
    const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
    console.log(objectFlat(source));

2 实现一个管理本地缓存过期的函数

封装一个可以设置过期时间的localStorage存储函数

    class Storage{
      constructor(name){
          this.name = 'storage';
      }
      //设置缓存
      setItem(params){
          let obj = {
              name:'', // 存入数据  属性
              value:'',// 属性值
              expires:"", // 过期时间
              startTime:new Date().getTime()//记录何时将值存入缓存,毫秒级
          }
          let options = {};
          //将obj和传进来的params合并
          Object.assign(options,obj,params);
          if(options.expires){
          //如果options.expires设置了的话
          //以options.name为key,options为值放进去
              localStorage.setItem(options.name,JSON.stringify(options));
          }else{
          //如果options.expires没有设置,就判断一下value的类型
              let type = Object.prototype.toString.call(options.value);
              //如果value是对象或者数组对象的类型,就先用JSON.stringify转一下,再存进去
              if(Object.prototype.toString.call(options.value) == '[object Object]'){
                  options.value = JSON.stringify(options.value);
              }
              if(Object.prototype.toString.call(options.value) == '[object Array]'){
                  options.value = JSON.stringify(options.value);
              }
              localStorage.setItem(options.name,options.value);
          }
      }
      //拿到缓存
      getItem(name){
          let item = localStorage.getItem(name);
          //先将拿到的试着进行json转为对象的形式
          try{
              item = JSON.parse(item);
          }catch(error){
          //如果不行就不是json的字符串,就直接返回
              item = item;
          }
          //如果有startTime的值,说明设置了失效时间
          if(item.startTime){
              let date = new Date().getTime();
              //何时将值取出减去刚存入的时间,与item.expires比较,如果大于就是过期了,如果小于或等于就还没过期
              if(date - item.startTime > item.expires){
              //缓存过期,清除缓存,返回false
                  localStorage.removeItem(name);
                  return false;
              }else{
              //缓存未过期,返回值
                  return item.value;
              }
          }else{
          //如果没有设置失效时间,直接返回值
              return item;
          }
      }
      //移出缓存
      removeItem(name){
          localStorage.removeItem(name);
      }
      //移出全部缓存
      clear(){
          localStorage.clear();
      }
    }

用法

    let storage = new Storage();
    storage.setItem({
      name:"name",
      value:"ppp"
    })

下面我把值取出来

    let value = storage.getItem('name');
    console.log('我是value',value);

设置5秒过期

    let storage = new Storage();
    storage.setItem({
      name:"name",
      value:"ppp",
      expires: 5000
    })
    // 过期后再取出来会变为 false
    let value = storage.getItem('name');
    console.log('我是value',value);

3 实现lodash的chunk方法--数组按指定长度拆分

题目

    /**
     * @param input
     * @param size
     * @returns {Array}
     */
    _.chunk(['a', 'b', 'c', 'd'], 2)
    // => [['a', 'b'], ['c', 'd']]
    
    _.chunk(['a', 'b', 'c', 'd'], 3)
    // => [['a', 'b', 'c'], ['d']]
    
    _.chunk(['a', 'b', 'c', 'd'], 5)
    // => [['a', 'b', 'c', 'd']]
    
    _.chunk(['a', 'b', 'c', 'd'], 0)
    // => []

实现

    function chunk(arr, length) {
      let newArr = [];
      for (let i = 0; i < arr.length; i += length) {
        newArr.push(arr.slice(i, i + length));
      }
      return newArr;
    }

4 手写深度比较isEqual

思路:深度比较两个对象,就是要深度比较对象的每一个元素。=> 递归

  • 递归退出条件:
    • 被比较的是两个值类型变量,直接用“===”判断
    • 被比较的两个变量之一为null,直接判断另一个元素是否也为null
  • 提前结束递推:
    • 两个变量keys数量不同
    • 传入的两个参数是同一个变量
  • 递推工作:深度比较每一个key
    function isEqual(obj1, obj2){
        //其中一个为值类型或null
        if(!isObject(obj1) || !isObject(obj2)){
            return obj1 === obj2;
        }
    
        //判断是否两个参数是同一个变量
        if(obj1 === obj2){
            return true;
        }
    
        //判断keys数是否相等
        const obj1Keys = Object.keys(obj1);
        const obj2Keys = Object.keys(obj2);
        if(obj1Keys.length !== obj2Keys.length){
            return false;
        }
    
        //深度比较每一个key
        for(let key in obj1){
            if(!isEqual(obj1[key], obj2[key])){
                return false;
            }
        }
    
        return true;
    }

5 实现一个JSON.stringify

    JSON.stringify(value[, replacer [, space]])
  • Boolean | Number| String类型会自动转换成对应的原始值。
  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
  • 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
    function jsonStringify(obj) {
        let type = typeof obj;
        if (type !== "object") {
            if (/string|undefined|function/.test(type)) {
                obj = '"' + obj + '"';
            }
            return String(obj);
        } else {
            let json = []
            let arr = Array.isArray(obj)
            for (let k in obj) {
                let v = obj[k];
                let type = typeof v;
                if (/string|undefined|function/.test(type)) {
                    v = '"' + v + '"';
                } else if (type === "object") {
                    v = jsonStringify(v);
                }
                json.push((arr ? "" : '"' + k + '":') + String(v));
            }
            return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
        }
    }
    jsonStringify({x : 5}) // "{"x":5}"
    jsonStringify([1, "false", false]) // "[1,"false",false]"
    jsonStringify({b: undefined}) // "{"b":"undefined"}"

6 实现一个JSON.parse

    JSON.parse(text[, reviver])

用来解析JSON字符串,构造由字符串描述的JavaScript值或对象。提供可选的reviver函数用以在返回之前对所得到的对象执行变换(操作)

第一种:直接调用 eval

    function jsonParse(opt) {
        return eval('(' + opt + ')');
    }
    jsonParse(jsonStringify({x : 5}))
    // Object { x: 5}
    jsonParse(jsonStringify([1, "false", false]))
    // [1, "false", falsr]
    jsonParse(jsonStringify({b: undefined}))
    // Object { b: "undefined"}

避免在不必要的情况下使用 evaleval() 是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行JS代码,有XSS漏洞。

如果你只想记这个方法,就得对参数json做校验。

    var rx_one = /^[\],:{}\s]*$/;
    var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
    var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
    var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
    if (
        rx_one.test(
            json
                .replace(rx_two, "@")
                .replace(rx_three, "]")
                .replace(rx_four, "")
        )
    ) {
        var obj = eval("(" +json + ")");
    }

第二种:Function

核心:Function与eval有相同的字符串参数特性

    var func = new Function(arg1, arg2, ..., functionBody);

在转换JSON的实际应用中,只需要这么做

    var jsonStr = '{ "age": 20, "name": "jack" }'
    var json = (new Function('return ' + jsonStr))();

evalFunction都有着动态编译js代码的作用,但是在实际的编程中并不推荐使用

7 解析 URL Params 为对象

    let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
    parseParam(url)
    /* 结果
    { user: 'anonymous',
      id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
      city: '北京', // 中文需解码
      enabled: true, // 未指定值得 key 约定为 true
    }
    */
    function parseParam(url) {
      const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
      const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
      let paramsObj = {};
      // 将 params 存到对象中
      paramsArr.forEach(param => {
        if (/=/.test(param)) { // 处理有 value 的参数
          let [key, val] = param.split('='); // 分割 key 和 value
          val = decodeURIComponent(val); // 解码
          val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
    
          if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
            paramsObj[key] = [].concat(paramsObj[key], val);
          } else { // 如果对象没有这个 key,创建 key 并设置值
            paramsObj[key] = val;
          }
        } else { // 处理没有 value 的参数
          paramsObj[param] = true;
        }
      })
    
      return paramsObj;
    }

8 转化为驼峰命名

    var s1 = "get-element-by-id"
    
    // 转化为 getElementById
    
    var f = function(s) {
        return s.replace(/-\w/g, function(x) {
            return x.slice(1).toUpperCase();
        })
    }

9 实现一个函数判断数据类型

    function getType(obj) {
       if (obj === null) return String(obj);
       return typeof obj === 'object' 
       ? Object.prototype.toString.call(obj).replace('[object ', '').replace(']', '').toLowerCase()
       : typeof obj;
    }
    
    // 调用
    getType(null); // -> null
    getType(undefined); // -> undefined
    getType({}); // -> object
    getType([]); // -> array
    getType(123); // -> number
    getType(true); // -> boolean
    getType('123'); // -> string
    getType(/123/); // -> regexp
    getType(new Date()); // -> date

10 对象数组列表转成树形结构(处理菜单)

    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]
    
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]

实现代码如下:

    function listToTree(data) {
      let temp = {};
      let treeData = [];
      for (let i = 0; i < data.length; i++) {
        temp[data[i].id] = data[i];
      }
      for (let i in temp) {
        if (+temp[i].parentId != 0) {
          if (!temp[temp[i].parentId].children) {
            temp[temp[i].parentId].children = [];
          }
          temp[temp[i].parentId].children.push(temp[i]);
        } else {
          treeData.push(temp[i]);
        }
      }
      return treeData;
    }

11 树形结构转成列表(处理菜单)

    [
        {
            id: 1,
            text: '节点1',
            parentId: 0,
            children: [
                {
                    id:2,
                    text: '节点1_1',
                    parentId:1
                }
            ]
        }
    ]
    转成
    [
        {
            id: 1,
            text: '节点1',
            parentId: 0 //这里用0表示为顶级节点
        },
        {
            id: 2,
            text: '节点1_1',
            parentId: 1 //通过这个字段来确定子父级
        }
        ...
    ]

实现代码如下:

    function treeToList(data) {
      let res = [];
      const dfs = (tree) => {
        tree.forEach((item) => {
          if (item.children) {
            dfs(item.children);
            delete item.children;
          }
          res.push(item);
        });
      };
      dfs(data);
      return res;
    }
Last Updated:
Contributors: leeguooooo