🐲手写代码问题

# 🐲手写代码问题

# 手写Underscore

Underscore.js中文网 (underscorejs.net) (opens new window)

源码分析参考 underscore-analysis/underscore-1.8.3-analysis.js at master · lessfish/underscore-analysis (github.com) (opens new window) 前人栽树,后人乘凉~

# 集合函数 (数组 或对象) (opens new window)

# 数组函数(Array Functions) (opens new window)

# 与函数有关的函数(Function (uh, ahem) Functions) (opens new window)

# 对象函数(Object Functions) (opens new window)

# 实用功能(Utility Functions) (opens new window)

# JavaScript API

巩固基础知识,手写并深入理解JS-API

# 1.Array.reduce() (opens new window)

# 语法

image-20220129193930143

# 手写reduce

Array.prototype._reduce = function(callback, initialValue){
    // 谁调用_reduce this就指向谁~ 为了语义化 这里把this赋值给arr
    let arr = this; 
    if (typeof callback !== "function") {
        throw "传入的回调函数捏?";
    }

    // 初始值有无——会导致 reduce归并操作中的累加器acc初始值 不同
    let index = 0
    if (!initialValue) {
        index = 1;
        acc = arr[0];
    } else {
        // 如果提供了初始值,则index从0开始~
        acc = initialValue;
    }

    for(; index < arr.length; index++){
        /**
          * Accumulator (acc) (累计器) —— 必须传入
          * Current Value (cur) (当前值) —— 必须传入
          * Current Index (idx) (当前索引)
          * Source Array (src) (源数组)
          */
        acc = callback(acc, arr[index], index, arr)
    }
    return acc
}
let arr = [1,2,3]
console.log(arr._reduce((pre, cur) => pre + cur));// 6
console.log(arr._reduce((pre, cur) => pre + cur, 60));// 66
console.log(arr._reduce(666));// Uncaught 传入的回调函数捏?

网上搜到的版本中还写了方法用来判断arr是否为数组,那步没啥意义 它抛出错误会显示为—— arr._reduce is not a function——毕竟是写在数组原型上的方法 不用数组调肯定是不行的 这个不用额外判断了~

# reduce的使用

  • 求和

  • 数组转为嵌套对象 ['a', 'b', 'c'] -> {a: {b: {c: {}}}}

    京东二面问题

    reduce的最佳实践之一!也许会搭配手写reduce考察哦~

    let arr = ['a', 'b', 'c'].reverse();
    const transform = function transformArrToObj() {
        return arr.reduce((acc, cur) => {
            console.log(cur, acc)
            return { [cur]: acc }
        }, {})
    }
    console.log("对象->JSON字符串 更好看", JSON.stringify(transform()));
    console.log("对象深拷贝", JSON.parse(JSON.stringify(transform())));
    

# 2.Object.create() (opens new window)

# 语法

**Object.create()**方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

image-20220206172351383

const object1 = Object.create(Foo)

可用于实现单继承

——下面这种“寄生式继承”也是简单继承中的最优解 见这篇:(建议收藏)原生JS灵魂之问, 请问你能接得住几个? (opens new window) 这篇还对面向对象的设计思路进行了剖析

继承的最大问题在于:无法决定继承哪些属性,所有属性都得继承。——代码的耦合性太高,维护性不好。 ——面向组合的设计方式。来解决这个问题

// Shape - 父类(superclass)
function Shape() {
  this.x = 0;
  this.y = 0;
}

// 父类的方法
Shape.prototype.move = function(x, y) {
  this.x += x;
  this.y += y;
  console.info('Shape moved.');
};

// Rectangle - 子类(subclass)
function Rectangle() {
  // 继承属性
  Shape.call(this); // call super constructor.
}

// 子类续承父类
Rectangle.prototype = Object.create(Shape.prototype); // Rectangle.prototype = new Shape(); 也是可以的

Rectangle.prototype.constructor = Rectangle;

var rect = new Rectangle();

console.log('Is rect an instance of Rectangle?',
  rect instanceof Rectangle); // true
console.log('Is rect an instance of Shape?',
  rect instanceof Shape); // true —— 继承成功
rect.move(1, 1); // Outputs, 'Shape moved.'

(如果你希望能继承到多个对象,则可以使用混入的方式。)

// 继承两个父类的属性
function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}

// 继承一个类
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do a thing
};

Object.assign (opens new window) 会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。Object.assign 是在 ES2015 引入的,且可用 polyfilled (opens new window)

# 手写create

思路:将传入的父类原型对象作为原型,返回继承了父类(方法)的子类的实例对象。

const create = function(objPrototype) {
  function Son() {};
  Son.prototype = objPrototype;
  return new Son();
}

验证一下:

function Dad() {
  this.attr = 0;
}

// 父类的方法
Dad.prototype.change = function(x, y) {
  this.attr += x;
  console.info(this.attr, 'attr changed.');
};

function Son() {
  // 继承属性
  Dad.call(this); // call super constructor.
}

/**
 * 第一行利用create方法达成子类续承父类的步骤👇
 * constructor属性的作用是,可以得知某个实例对象,到底是哪一个构造函数产生的。
 * 第二行为了纠正子类的构造函数指向正确
 * create方法中传入new Dad()实例对象也可以完成继承,但是为了更符合create方法,我们传入Dad.prototype
 */ 
Son.prototype = create(Dad.prototype);
Son.prototype.constructor = Son;

var son = new Son();

// 验证继承是否成功——父类的 属性 & 方法
console.log('Is son an instance of Son?',
 son instanceof Son); // true
console.log('Is son an instance of Dad?',
 son instanceof Dad); // true —— 继承成功
son.change(666); // 666, 'attr changed.'

# 3.instanceof (opens new window)

# 语法

f1 instanceof Foo

instanceof 运算符用于检测构造函数(Foo)的 prototype 属性是否出现在某个实例对象(f1)的原型链上。

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
const auto = new Car('Honda', 'Accord', 1998);

console.log(auto instanceof Car);
// expected output: true

console.log(auto instanceof Object);
// expected output: true

console.log(auto instanceof Function);
// expected output: false

由下图可知 f1的原型链上有f1的构造函数Foo、Object

f1.__proto__ = Foo.prototype;
f1.__proto__ = Object.prototype;

f1的构造函数Foo的原型链上有 Function、Object

image.png

# 手写instanceof

f1 instanceof Foo

1.获取类型的原型f1.__proto__

2.获取构造函数的prototype对象(显式原型对象)

3.顺着原型链往下找,循环判断

function myInstanceof(f1, Foo) {
  let proto = Object.getPrototypeOf(f1), // 获取对象的原型
      prototype = Foo.prototype; // 获取构造函数的 prototype 对象

  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (proto !== null) {
    if (proto === prototype) return true;

    // 返回对象(proto)的原型
    proto = Object.getPrototypeOf(proto);
  }
  // 走到原型链尽头 
  return false;
}

# Object.getPrototypeOf() (opens new window)

Object.getPrototypeOf() 方法返回指定对象的(显式)原型(内部[[Prototype]]属性的值, 也就是 __proto__ )。

image-20220207115538964

补充一个更完善的例子

image-20220215153004677

# 验证
var Foo = function() {}
const f1 = new Foo();

console.log(myInstanceof(f1, Foo)); // true
console.log(myInstanceof(f1, Object)); // true
console.log(myInstanceof(f1, Function)); // false

# 4.链式调用

image-20220312155003887

函数内容皆为 return this

# 5.Promise

# 数据处理问题

# 1.实现日期格式化函数

# 要求:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd'); // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日

# 实现

思路:

String.prototype.replace

Date类型的对象相关API .getDate()/getMonth()/getFullYear()

const dateFormat = function(dateInput, format) {
  let year = dateInput.getFullYear();
  let month = dateInput.getMonth() + 1;
  let day = dateInput.getDate();

  // 可以使用正则表达式作为replace方法的“模式”
  format = format.replace(/yyyy/, year);
  // 可以使用字符串作为replace方法的“模式“
  format = format.replace('MM', month);
  format = format.replace('dd', day);

  return format;
}

可以用上面“要求”中的内容进行测试

public timeFormat(rowDate: Date (opens new window) | String (opens new window) | number (opens new window), fmt: String (opens new window)): String (opens new window)source (opens new window)

format type by fmt

# Params:
Name Type Attribute Description
rowDate Date (opens new window) | String (opens new window) | number (opens new window) [date instance|date string|timestamp]
fmt String (opens new window) eg:YYYY-MM-DD HH:mm:ss
# Return:

String (opens new window) formated date

# Example:
 timeformat('2018-10-10 10:30:20', 'MM/DD HH:mm')
 // => 10/10 10:30

 const date = new Date('2018-10-10 10:30:20')
 timeformat(date, 'MM/DD HH:mm')
 // => 10/10 10:30

 const date = new Date('2018-10-10 10:30:20')
 timeformat(date.getTime(), 'MM/DD HH:mm')
 // => 10/10 10:30
# answer

Date.prototype.toISOString() (opens new window)

toISOString() 方法返回一个 ISO 格式的字符串: YYYY-MM-DDTHH:mm:ss.sssZ。时区总是UTC(协调世界时),加一个后缀“Z”标识。

image-20220208230253683

/**
 * format type by fmt
 * @param {Date|String|number}  rowDate [date instance|date string|timestamp]
 * @param {String}  fmt eg:YYYY-MM-DD HH:mm:ss
 * @return {String} formated date
 *
 * @example
 *  timeformat('2018-10-10 10:30:20', 'MM/DD HH:mm')
 *  // => 10/10 10:30
 *
 *  const date = new Date('2018-10-10 10:30:20')
 *  timeformat(date, 'MM/DD HH:mm')
 *  // => 10/10 10:30
 *
 *  const date = new Date('2018-10-10 10:30:20')
 *  timeformat(date.getTime(), 'MM/DD HH:mm')
 *  // => 10/10 10:30
 *
 */
const timeformat = (rowDate, fmt) => {
  if (!rowDate || !fmt) return fmt;
  // get correct date
  const date = new Date(rowDate);
  // console.log(date);

  if (date.toString() === 'Invalid Date') {
    return fmt;
  }

  // 输入的时间是中国时区的 UTC时间比中国时间提前八个小时 所以要打出来8小时的富余量~
  // 使用setHours方法
  date.setHours(date.getHours() + 8);
  // console.log(date);
  const [YYYY, MM, DD, HH, mm, ss] = date.toISOString().match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);
  // console.log(date.toISOString());
  return fmt.replace('YYYY', YYYY)
    .replace('YY', YYYY.slice(-2))
    .replace('MM', MM)
    .replace('DD', DD)
    .replace('HH', HH)
    .replace('mm', mm)
    .replace('ss', ss);
};

# 2. 交换a,b的值-异或

# 要求

不能使用临时变量

# 实现

  • 巧妙加减
let a = 1, b = 2;

a = a + b; // 1 + 2 = 3
b = a - b; // 3 - 2 = 1
a = a - b; // 3 - 1 = 2 
  • 利用异或运算
    • a ^ a = 0
    • 0 ^ a = a
let a = 1, b = 2;

a = a ^ b;
b = a ^ b; // (a ^ b) ^ b = a
a = a ^ b; // (a ^ b) ^ a = b

# 3.数组转为嵌套对象 ['a', 'b', 'c'] -> {a: {b: {c: {}}}}

京东二面问题

reduce的最佳实践!也许会搭配手写reduce考察哦~

let arr = ['a', 'b', 'c'].reverse();
const transform = function transformArrToObj() {
    return arr.reduce((acc, cur) => {
        console.log(cur, acc)
        return { [cur]: acc }
    }, {})
}
console.log("对象->JSON字符串 更好看", JSON.stringify(transform()));
console.log("对象深拷贝", JSON.parse(JSON.stringify(transform())));

# 4.数组扁平化

  • flat
let tempArr = [1,2,3,[4,5],6];
tempArr.flat(Infinity); // 无限层的扁平化
  • 转字符串
let tempArr = [1,2,3,[4,5],6];
let result = tempArr.join(',').split(',').map((str) => Number(str));
// 或者使用toString() 
let result = tempArr.toString().split(",").map((str) => Number(str));
  • reduce
    • arr第一项开始(所以要给acc累加器加上初始值{}
    • 如果遇到的值为数组 则扔到递归调用栈里进行递归遍历 , 否则直接接到acc的尾部(使用concat
const flatten = (arr) => {
  return arr.reduce((acc, cur) => {
    // 根据“cur是否为数组”进行递归~
    return acc.concat(Array.isArray(cur) ? flatten(cur) : cur);
  }, [])
}
  • 扩展运算符

    • [].concat(...arr)
    // 多维数组
    const flatten = (arr) => {
      while(arr.some(item => Array.isArray(item))) {
        arr = [].concat(...arr);
      }
      return arr;
    }
    
    • 上面的reduce也可以和扩展运算符结合~
    const flatten = (arr) => {
      return arr.reduce((acc, cur) => {
          return Array.isArray(cur) ? [...acc, ...cur] : [...acc, cur]; 
      }, [])
    }
    

# 5.超级全的数组去重

# 0.超级无敌万能方法

22/3/18美团一面遇到的 数组中元素比较复杂?没关系~

使用obj存储 数组中的每个元素 结合filter 如果碰到了重复的元素就删去

let arr=[{}, {}, NaN, NaN, {a: 1}, {a: 1}, {a: 1, b: 666}, {a: 1, b: 666}, [], [], 666, '666', '666', 666, false, false];

Array.prototype.uniq = function() {
  const uniqObj = {};
  return this.filter((item) => {
    let uniqItem = typeof item + JSON.stringify(item);
    return uniqObj.hasOwnProperty(uniqItem) ? false : uniqObj[uniqItem] = true; // obj[attr] = true 返回true obj[attr] = false 返回false 22/3/20的sfz并不知道这个知识点,,,
  })
}
console.log(arr.uniq()); // 测试成功~ 爽~~

# 1. 数组元素比较型

  • 双层for循环

    • // 双层for循环
      // 前一个跟之后所有进行比较 , 重复了删除掉
      function uniq(arr){
          for(let i = 0 ; i < arr.length - 1 ; i++){
              for(let j = i + 1 ; j < arr.length; j++){
                  if(arr[i] === arr[j]){
                      arr.splice(i , 1);
                      // 删除后下表移动到原位置
                      j--;
                  }
              }
          }
          return arr;
      }
      
  • 排序后 相邻位置进行比较

    • // 排序进行后进行相邻比较
      function sortQ(arr){
          // 排序后
          // 没参数 如果没有指明 compareFunction ,那么元素会按照转换为的字符串的诸个字符的Unicode位点进行排序。
          arr.sort();
          for(let i = 0 ; i < arr.length - 1 ; i++){
              if(arr[i] === arr[i + 1]){
                  arr.splice(i , 1);
                  i--;
              }
          }
      
      }
      

# 2.查找元素位置型

  • indexOf 查找元素并返回其第一个索引值

    • function uniq(arr){
          const res = [];
          for(let i = 0 ; i < arr.length ; i++){
              if(arr.indexOf(arr[i]) === i){
                  res.push(arr[i])
              }
          }
          return res;
      }
      
  • findIndex 返回数组中第一个满足测试函数的元素的索引

    • function uniq(arr){
          const res = [];
          for(let i = 0 ; i < arr.length ; i++){
              if(arr.findIndex((item) => item === arr[i]) === i ){
                  res.push(arr[i]);
              }
          }
          return res;
      }
      

# 3. 查找元素存在型

  • includes

    • // 查找元素存在型
      function uniq(arr){
          const res = [];
          for(let i = 0 ; i < arr.length ; i++){
              if(!res.includes(arr[i])){
                  res.push(arr[i])
              }
          }
          return res;
      }
      

# 4. 利用数据结构类型

  • set

    • // set
      function uniq(arr){
          return [...new Set(arr)]
      }
      
  • map

    • function uniq(arr){
          const map = new Map();
          arr.forEach(item =>{
              map.set(item, true)
          })
          // 返回键值 Object.keys(key)
          return[...map.keys()]
      }
      

# 5. 总结

在简单的测试用例大概 2000 万条数据下,indexOf 的方案速度相对最快

img

# 6.实现数组的乱序输出

# 前端面试(算法篇) - 数组乱序 (opens new window)

写得挺好的 搭配 384. 打乱数组 (opens new window) 食用更佳

首先是伪随机的两种方法

  • 【1】随机取数

从原数组中随机抽取一个数,然后使用 splice 删掉该元素

function getRandomArrElement(arr, count) {
    let res = []
    while (res.length < count) {
        // 生成随机 index
        let randomIdx = (Math.random() * arr.length) >> 0;
        // splice 返回的是一个数组
        res.push(arr.splice(randomIdx, 1)[0]);
    }
    return res
}

上面生成随机 index 用到了按位右移操作符 >>

当后面的操作数是 0 的时候,该语句的结果就和 Math.floor() 一样,是向下取整

但位操作符是在数值表示的最底层执行操作,因此速度更快

// 按位右移
(Math.random() * 100) >> 0

// Math.floor
Math.floor(Math.random() * 100)
/* 这两种写法的结果是一样的,但位操作的效率更高 */
  • 【2】通过 sort 乱序

首先认识一下 Array.prototype.sort() (opens new window) 不了解的查看下 这个必须滚瓜烂熟

let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
arr.sort((a, b) => 0.5 - Math.random());

但这并不是真正的乱序,计算机的 random 函数因为循环周期的存在,无法生成真正的随机数(力扣那题用这个方法也跑不通XD)

  • 【3】Fisher–Yates shuffle 洗牌算法

这个方法太漂亮勒!

另外这个作者的写法也很漂亮!棒!

洗牌算法的思路是:

1.先从数组末尾开始,选取最后一个元素,与数组中随机一个位置的元素交换位置

2.然后在已经排好的最后一个元素以外的位置中,随机产生一个位置,让该位置元素与倒数第二个元素进行交换

以此类推,打乱整个数组的顺序

function shuffle(arr) {
  let len = arr.length;

  while (len) {
    let i = (Math.random() * len--) >> 0;// 获得随机数
    // 交换位置
    let temp = arr[len];
    arr[len] = arr[i];
    arr[i] = temp;
  }
  return arr;
}

再结合 ES6 的解构赋值,使用洗牌算法就更方便了:

Array.prototype.shuffle = function() {
    let m = this.length, i;
    while (m) {
        i = (Math.random() * m--) >> 0;
        [this[m], this[i]] = [this[i], this[m]]
    }
    return this;
}

# 7.排序问题

# 场景应用问题

# 1.循环打印红黄绿-经典异步编程问题

# 2.sleep函数

字节二面曾遇到过的问题,在组内工具函数库中看到了,试着写一写

/**
 * 睡眠指定时间 返回promise实例
 * @param {int} duration  睡眠时间, 毫秒
 * @return {Promise Instance} Promise 实例
 *
 * @example
 * sleep(100)
 * => Promise {<pending>}
 *
 */
const sleep = (duration) => new Promise((resolve) => {
    // 写法1
    setTimeout(() => {
        resolve();
    }, duration)
    // 写法2
    // setTimeout(resolve, time)
});
sleep(1000).then(() => {console.log("sleep")});

// 也可以封装得再彻底一些,像这样👇(不过这样灵活性不好,上述方式更加合理优秀!)
const sleep = (duration) => new Promise((resolve) => {
    setTimeout(() => {
        resolve();
    }, duration)
}).then(() => {
    console.log("sleep");
});
sleep(1000);

# 4.debounce防抖函数

浏览器灵魂之问 第10篇: 实现事件的防抖和节流 (opens new window)

用吃巧克力来理解 防抖和节流! (opens new window)

# 定义

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时

这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

# 用途

  • 当用户在短时间内,多次点击登陆,发送短信等请求数据的操作时
  • 文本编辑器一段时间不操作,进行自动保存
  • 搜索框的联想——实时发送请求
    • 用户输入文字,只在停顿1s时才进行联想

# 手写防抖函数

//模拟一段ajax请求
function ajax(content) {
  console.log('ajax request ' + content)
}

function debounce(fun, delay) {
  // 绑定函数上下文d
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      con
      fun.apply(this, args);
    }, delay)
  }
}

let inputb = document.getElementById('debounce')

let debounceAjax = debounce(ajax, 500)

inputb.addEventListener('keyup', function (e) {
  debounceAjax(e.target.value)
})

# 具体应用

image-20220322001337618

img

img

# 尤大的防抖

image-20220401215355580

# 5.throttle节流函数

# 定义

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

通过事件节流来降低事件调用的频率。

# 用途

  • scroll 函数(页面滚动)的事件监听上,通过事件节流来降低事件调用的频率(每隔1s(固定的时间)才计算一次位置信息,避免边滚动边计算)。
  • 鼠标mouseover事件,只执行一次时

# 手写节流函数

function throttle(fun, delay) {
  let deferTimer;
  let preTime = Date.now();
  return function (args) {
    let that = this
    let _args = arguments
    let now = +new Date()
    console.log(now);
    if (preTime && now < preTime + delay) {
      // 还没到节流规定的单位时间
      clearTimeout(deferTimer)
      deferTimer = setTimeout(function () {
        preTime = now
        fun.apply(that, _args)
      }, delay)
    } else {
      // 到了节流规定的单位时间
      preTime = now
      fun.apply(that, _args)
    }
  }
}

# 6.对象深拷贝

如何写出一个惊艳面试官的深拷贝? (opens new window)

# 简单的对象深拷贝

  • 判断当前键对应的值是否为Object类型 + 递归
    • 缺陷:无法解决循环引用问题 比如:target.target = target
const deepClone = (target) => {
  if (typeof target === 'object') {
    const cloneTarget = Array.isArray(target) ? [] : {};
    for (const attr in target) {
      cloneTarget[attr] = deepClone(target[attr]);
    }
    return cloneTarget;
  } else {
    return target;
  }
};

# 测试

const testTarget = {
  a: 666,
  b: [1, 2, 3],
  c: { d: '666' },
};

// 基础版本的deepClone无法解决循环引用问题,会爆栈~
// testTarget.testTarget = testTarget;

const copy = deepClone(testTarget);
copy.b = ['修改深拷贝过来的引用数据类型', '也没有关系~'];

console.log('deepClone:', copy); // deepClone: { a: 666, b: [ '修改深拷贝过来的引用数据类型', '也没有关系~' ], c: { d: '666' } }
console.log('origin:', testTarget); // origin: { a: 666, b: [ 1, 2, 3 ], c: { d: '666' } }

# 7.将拍平对象转换为层级对象

生成新的对象(默认不会出现嵌套情况)

相同父集需要做归并处理

// strarr -> obj
let strarr = {
  'a.b.c.d': 1,
  'a.b.c.e': 2,
  'a.b.f': 3,
  'a.j': 4,
};
let obj = {
  a: {
    b: {
      c: {
        d: 1,
        e: 2,
      },
      f: 3,
    },
    j: 4,
  },
};
  • 方法1
function fn(arr) {
  const values = Object.keys(arr);
  let obj = {};
  for (let i = 0; i < values.length; i++) {
    let value = values[i];
    let keyArr = value.split('.');
    for (let j = 0; j < keyArr.length; j++) {
      if (j == 0) {
        var pre = !obj[keyArr[j]]
        ? (obj[keyArr[j]] = {})
        : obj[keyArr[j]];
      } else if (j != keyArr.length - 1) {
        pre = !pre[keyArr[j]] ? (pre[keyArr[j]] = {}) : pre[keyArr[j]];
      } else {
        pre[keyArr[j]] = arr[value];
      }
    }
    pre = {};
  }
  return obj;
}