乐趣区

关于javascript:社招前端常考手写面试题总结

手写 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保留初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保留 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保留 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保留 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 办法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态扭转必须期待前一个状态扭转后再进行扭转
    if (value instanceof MyPromise) {return value.then(resolve, reject);
    }

    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变,if (self.state === PENDING) {
        // 批改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 办法
  function reject(value) {
    // 保障代码的执行程序为本轮事件循环的开端
    setTimeout(() => {
      // 只有状态为 pending 时能力转变
      if (self.state === PENDING) {
        // 批改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {callback(value);
        });
      }
    }, 0);
  }

  // 将两个办法传入函数执行
  try {fn(resolve, reject);
  } catch (e) {
    // 遇到谬误时,捕捉谬误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {return value;};

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {throw error;};

  // 如果是期待状态,则将函数退出对应列表中
  if (this.state === PENDING) {this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态曾经凝固,则间接执行对应状态的函数

  if (this.state === RESOLVED) {onResolved(this.value);
  }

  if (this.state === REJECTED) {onRejected(this.value);
  }
};

手写 call 函数

call 函数的实现步骤:

  1. 判断调用对象是否为函数,即便咱们是定义在函数的原型上的,然而可能呈现应用 call 等形式调用的状况。
  2. 判断传入上下文对象是否存在,如果不存在,则设置为 window。
  3. 解决传入的参数,截取第一个参数后的所有参数。
  4. 将函数作为上下文对象的一个属性。
  5. 应用上下文对象来调用这个办法,并保留返回后果。
  6. 删除方才新增的属性。
  7. 返回后果。
// call 函数实现
Function.prototype.myCall = function(context) {
  // 判断调用对象
  if (typeof this !== "function") {console.error("type error");
  }
  // 获取参数
  let args = [...arguments].slice(1),
      result = null;
  // 判断 context 是否传入,如果未传入则设置为 window
  context = context || window;
  // 将调用函数设为对象的办法
  context.fn = this;
  // 调用函数
  result = context.fn(...args);
  // 将属性删除
  delete context.fn;
  return result;
};

实现节流函数(throttle)

节流函数原理: 指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的工夫才会执行回调函数。总结起来就是:事件,依照一段时间的距离来进行触发

像 dom 的拖拽,如果用消抖的话,就会呈现卡顿的感觉,因为只在进行的时候执行了一次,这个时候就应该用节流,在肯定工夫内屡次执行,会晦涩很多

手写简版

应用工夫戳的节流函数会在第一次触发事件时立刻执行,当前每过 wait 秒之后才执行一次,并且最初一次触发事件不会被执行

工夫戳形式:

// func 是用户传入须要防抖的函数
// wait 是等待时间
const throttle = (func, wait = 50) => {
  // 上一次执行该函数的工夫
  let lastTime = 0
  return function(...args) {
    // 以后工夫
    let now = +new Date()
    // 将以后工夫和上一次执行函数工夫比照
    // 如果差值大于设置的等待时间就执行函数
    if (now - lastTime > wait) {
      lastTime = now
      func.apply(this, args)
    }
  }
}

setInterval(throttle(() => {console.log(1)
  }, 500),
  1
)

定时器形式:

应用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最初一次进行触发后,还会再执行一次函数

function throttle(func, delay){
  var timer = null;
  returnfunction(){
    var context = this;
    var args = arguments;
    if(!timer){timer = setTimeout(function(){func.apply(context, args);
        timer = null;
      },delay);
    }
  }
}

实用场景:

  • DOM 元素的拖拽性能实现(mousemove
  • 搜寻联想(keyup
  • 计算鼠标挪动的间隔(mousemove
  • Canvas 模仿画板性能(mousemove
  • 监听滚动事件判断是否到页面底部主动加载更多
  • 拖拽场景:固定工夫内只执行一次,避免超高频次触发地位变动
  • 缩放场景:监控浏览器resize
  • 动画场景:防止短时间内屡次触发动画引起性能问题

总结

  • 函数防抖:将几次操作合并为一次操作进行。原理是保护一个计时器,规定在 delay 工夫后触发函数,然而在 delay 工夫内再次触发的话,就会勾销之前的计时器而从新设置。这样一来,只有最初一次操作能被触发。
  • 函数节流:使得肯定工夫内只触发一次函数。原理是通过判断是否达到肯定工夫来触发函数。

Array.prototype.forEach()

Array.prototype.forEach = function(callback, thisArg) {if (this == null) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== "function") {throw new TypeError(callback + 'is not a function');
  }
  const O = Object(this);
  const len = O.length >>> 0;
  let k = 0;
  while (k < len) {if (k in O) {callback.call(thisArg, O[k], k, O);
    }
    k++;
  }
}

Promise.race

Promise.race = function(promiseArr) {return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      // 如果不是 Promise 实例须要转化为 Promise 实例
      Promise.resolve(p).then(val => resolve(val),
        err => reject(err),
      )
    })
  })
}

实现非负大整数相加

JavaScript 对数值有范畴的限度,限度如下:

Number.MAX_VALUE // 1.7976931348623157e+308
Number.MAX_SAFE_INTEGER // 9007199254740991
Number.MIN_VALUE // 5e-324
Number.MIN_SAFE_INTEGER // -9007199254740991

如果想要对一个超大的整数 (> Number.MAX_SAFE_INTEGER) 进行加法运算,然而又想输入个别模式,那么应用 + 是无奈达到的,一旦数字超过 Number.MAX_SAFE_INTEGER 数字会被立刻转换为迷信计数法,并且数字精度相比以前将会有误差。

实现一个算法进行大数的相加:

function sumBigNumber(a, b) {
  let res = '';
  let temp = 0;

  a = a.split('');
  b = b.split('');

  while (a.length || b.length || temp) {temp += ~~a.pop() + ~~b.pop();
    res = (temp % 10) + res;
    temp  = temp > 9
  }
  return res.replace(/^0+/, '');
}

其次要的思路如下:

  • 首先用字符串的形式来保留大数,这样数字在数学示意上就不会发生变化
  • 初始化 res,temp 来保留两头的计算结果,并将两个字符串转化为数组,以便进行每一位的加法运算
  • 将两个数组的对应的位进行相加,两个数相加的后果可能大于 10,所以可能要仅为,对 10 进行取余操作,将后果保留在以后位
  • 判断以后位是否大于 9,也就是是否会进位,若是则将 temp 赋值为 true,因为在加法运算中,true 会主动隐式转化为 1,以便于下一次相加
  • 反复上述操作,直至计算完结

参考 前端进阶面试题具体解答

JSONP

script 标签不遵循同源协定,能够用来进行 跨域申请,长处就是兼容性好但仅限于 GET 申请

const jsonp = ({url, params, callbackName}) => {const generateUrl = () => {
    let dataSrc = '';
    for (let key in params) {if (Object.prototype.hasOwnProperty.call(params, key)) {dataSrc += `${key}=${params[key]}&`;
      }
    }
    dataSrc += `callback=${callbackName}`;
    return `${url}?${dataSrc}`;
  }
  return new Promise((resolve, reject) => {const scriptEle = document.createElement('script');
    scriptEle.src = generateUrl();
    document.body.appendChild(scriptEle);
    window[callbackName] = data => {resolve(data);
      document.removeChild(scriptEle);
    }
  })
}

实现一个 call

call 做了什么:

  • 将函数设为对象的属性
  • 执行 & 删除这个函数
  • 指定 this 到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);
// 实现一个 call 办法:Function.prototype.myCall = function(context) {
  // 此处没有思考 context 非 object 状况
  context.fn = this;
  let args = [];
  for (let i = 1, len = arguments.length; i < len; i++) {args.push(arguments[i]);
  }
  context.fn(...args);
  let result = context.fn(...args);
  delete context.fn;
  return result;
};

应用 Promise 封装 AJAX 申请

// promise 封装实现:function getJSON(url) {
  // 创立一个 promise 对象
  let promise = new Promise(function(resolve, reject) {let xhr = new XMLHttpRequest();
    // 新建一个 http 申请
    xhr.open("GET", url, true);
    // 设置状态的监听函数
    xhr.onreadystatechange = function() {if (this.readyState !== 4) return;
      // 当申请胜利或失败时,扭转 promise 的状态
      if (this.status === 200) {resolve(this.response);
      } else {reject(new Error(this.statusText));
      }
    };
    // 设置谬误监听函数
    xhr.onerror = function() {reject(new Error(this.statusText));
    };
    // 设置响应的数据类型
    xhr.responseType = "json";
    // 设置申请头信息
    xhr.setRequestHeader("Accept", "application/json");
    // 发送 http 申请
    xhr.send(null);
  });
  return promise;
}

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

封装一个能够设置过期工夫的 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);

实现一个 JS 函数柯里化

事后解决的思维,利用闭包的机制

  • 柯里化的定义:接管一部分参数,返回一个函数接管残余参数,接管足够参数后,执行原函数
  • 函数柯里化的次要作用和特点就是 参数复用 提前返回 提早执行
  • 柯里化把屡次传入的参数合并,柯里化是一个高阶函数
  • 每次都返回一个新函数
  • 每次入参都是一个

当柯里化函数接管到足够参数后,就会执行原函数,如何去确定何时达到足够的参数呢?

有两种思路:

  • 通过函数的 length 属性,获取函数的形参个数,形参的个数就是所需的参数个数
  • 在调用柯里化工具函数时,手动指定所需的参数个数

将这两点联合一下,实现一个简略 curry 函数

通用版

// 写法 1
function curry(fn, args) {
  var length = fn.length;
  var args = args || [];
  return function(){newArgs = args.concat(Array.prototype.slice.call(arguments));
      if (newArgs.length < length) {return curry.call(this,fn,newArgs);
      }else{return fn.apply(this,newArgs);
      }
  }
}
// 写法 2
// 分批传入参数
// redux 源码的 compose 也是用了相似柯里化的操作
const curry = (fn, arr = []) => {// arr 就是咱们要收集每次调用时传入的参数
  let len = fn.length; // 函数的长度,就是参数的个数

  return function(...args) {let newArgs = [...arr, ...args] // 收集每次传入的参数

    // 如果传入的参数个数等于咱们指定的函数参数个数,就执行指定的真正函数
    if(newArgs.length === len) {return fn(...newArgs)
    } else {
      // 递归收集参数
      return curry(fn, newArgs)
    }
  }
}
// 测试
function multiFn(a, b, c) {return a * b * c;}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4)

ES6 写法

const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args])
// 测试
let curryTest=curry((a,b,c,d)=>a+b+c+d)
curryTest(1,2,3)(4) // 返回 10
curryTest(1,2)(4)(3) // 返回 10
curryTest(1,2)(3,4) // 返回 10
// 柯里化求值
// 指定的函数
function sum(a,b,c,d,e) {return a + b + c + d + e}

// 传入指定的函数,执行一次
let newSum = curry(sum)

// 柯里化 每次入参都是一个参数
newSum(1)(2)(3)(4)(5)

// 偏函数
newSum(1)(2)(3,4,5)
// 柯里化简略利用
// 判断类型,参数多少个,就执行多少次收集
function isType(type, val) {return Object.prototype.toString.call(val) === `[object ${type}]`
}

let newType = curry(isType)

// 相当于把函数参数一个个传了,把第一次先缓存起来
let isString = newType('String')
let isNumber = newType('Number')

isString('hello world')
isNumber(999)

实现每隔一秒打印 1,2,3,4

// 应用闭包实现
for (var i = 0; i < 5; i++) {(function(i) {setTimeout(function() {console.log(i);
    }, i * 1000);
  })(i);
}
// 应用 let 块级作用域
for (let i = 0; i < 5; i++) {setTimeout(function() {console.log(i);
  }, i * 1000);
}

版本号排序的办法

题目形容: 有一组版本号如下 ['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。当初须要对其进行排序,排序的后果为 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {return arr2.length - arr1.length;}

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

二叉树深度遍历

// 二叉树深度遍历

class Node {constructor(element, parent) {
    this.parent = parent // 父节点 
    this.element = element // 以后存储内容
    this.left = null // 左子树
    this.right = null // 右子树
  }
}

class BST {constructor(compare) {
    this.root = null // 树根
    this.size = 0 // 树中的节点个数

    this.compare = compare || this.compare
  }
  compare(a,b) {return a - b}
  add(element) {if(this.root === null) {this.root = new Node(element, null)
      this.size++
      return
    }
    // 获取根节点 用以后增加的进行判断 放右边还是放左边
    let currentNode = this.root 
    let compare
    let parent = null 
    while (currentNode) {compare = this.compare(element, currentNode.element)
      parent = currentNode // 先将父亲保存起来
      // currentNode 要不停的变动
      if(compare > 0) {currentNode = currentNode.right} else if(compare < 0) {currentNode = currentNode.left} else {currentNode.element = element // 相等时 先笼罩后续解决}
    }

    let newNode = new Node(element, parent)
    if(compare > 0) {parent.right = newNode} else if(compare < 0) {parent.left = newNode}

    this.size++
  }
  // 前序遍历
  preorderTraversal(visitor) {
    const traversal = node=>{if(node === null) return 
      visitor.visit(node.element)
      traversal(node.left)
      traversal(node.right)
    }
    traversal(this.root)
  }
  // 中序遍历
  inorderTraversal(visitor) {
    const traversal = node=>{if(node === null) return 
      traversal(node.left)
      visitor.visit(node.element)
      traversal(node.right)
    }
    traversal(this.root)
  }
  // 后序遍历
  posterorderTraversal(visitor) {
    const traversal = node=>{if(node === null) return 
      traversal(node.left)
      traversal(node.right)
      visitor.visit(node.element)
    }
    traversal(this.root)
  }
  // 反转二叉树:无论先序、中序、后序、层级都能够反转
  invertTree() {
    const traversal = node=>{if(node === null) return 
      let temp = node.left 
      node.left = node.right 
      node.right = temp
      traversal(node.left)
      traversal(node.right)
    }
    traversal(this.root)
    return this.root
  }
}

先序遍历

二叉树的遍历形式

// 测试
var bst = new BST((a,b)=>a.age-b.age) // 模仿 sort 办法

bst.add({age: 10})
bst.add({age: 8})
bst.add({age:19})
bst.add({age:6})
bst.add({age: 15})
bst.add({age: 22})
bst.add({age: 20})

// 先序遍历
// console.log(bst.preorderTraversal(),'先序遍历')
// console.log(bst.inorderTraversal(),'中序遍历')
// ![](http://img-repo.poetries.top/images/20210522214837.png)
// console.log(bst.posterorderTraversal(),'后序遍历')


// 深度遍历:先序遍历、中序遍历、后续遍历
// 广度遍历:档次遍历(同层级遍历)// 都可拿到树中的节点

// 应用访问者模式
class Visitor {constructor() {this.visit = function (elem) {elem.age = elem.age*2}
  }
}

// bst.posterorderTraversal({//   visit(elem) {
//     elem.age = elem.age*10
//   }
// })

// 不能通过索引操作 拿到节点去操作
// bst.posterorderTraversal(new Visitor())

console.log(bst.invertTree(),'反转二叉树')

查找字符串中呈现最多的字符和个数

例: abbcccddddd -> 字符最多的是 d,呈现了 5 次

let str = "abcabcabcbbccccc";
let num = 0;
let char = '';

 // 使其依照肯定的秩序排列
str = str.split('').sort().join('');
// "aaabbbbbcccccccc"

// 定义正则表达式
let re = /(\w)\1+/g;
str.replace(re,($0,$1) => {if(num < $0.length){
        num = $0.length;
        char = $1;        
    }
});
console.log(` 字符最多的是 ${char},呈现了 ${num}次 `);

封装异步的 fetch,应用 async await 形式来应用

(async () => {
    class HttpRequestUtil {async get(url) {const res = await fetch(url);
            const data = await res.json();
            return data;
        }
        async post(url, data) {
            const res = await fetch(url, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async put(url, data) {
            const res = await fetch(url, {
                method: 'PUT',
                headers: {'Content-Type': 'application/json'},
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
        async delete(url, data) {
            const res = await fetch(url, {
                method: 'DELETE',
                headers: {'Content-Type': 'application/json'},
                data: JSON.stringify(data)
            });
            const result = await res.json();
            return result;
        }
    }
    const httpRequestUtil = new HttpRequestUtil();
    const res = await httpRequestUtil.get('http://golderbrother.cn/');
    console.log(res);
})();

实现数组的乱序输入

次要的实现思路就是:

  • 取出数组的第一个元素,随机产生一个索引值,将该第一个元素和这个索引对应的元素进行替换。
  • 第二次取出数据数组第二个元素,随机产生一个除了索引为 1 的之外的索引值,并将第二个元素与该索引值对应的元素进行替换
  • 依照下面的法则执行,直到遍历实现
var arr = [1,2,3,4,5,6,7,8,9,10];
for (var i = 0; i < arr.length; i++) {const randomIndex = Math.round(Math.random() * (arr.length - 1 - i)) + i;
  [arr[i], arr[randomIndex]] = [arr[randomIndex], arr[i]];
}
console.log(arr)

还有一办法就是倒序遍历:

var arr = [1,2,3,4,5,6,7,8,9,10];
let length = arr.length,
    randomIndex,
    temp;
  while (length) {randomIndex = Math.floor(Math.random() * length--);
    temp = arr[length];
    arr[length] = arr[randomIndex];
    arr[randomIndex] = temp;
  }
console.log(arr)

验证是否是邮箱

function isEmail(email) {var regx = /^([a-zA-Z0-9_\-])+@([a-zA-Z0-9_\-])+(\.[a-zA-Z0-9_\-])+$/;
    return regx.test(email);
}

实现有并行限度的 Promise 调度器

题目形容:JS 实现一个带并发限度的异步调度器 Scheduler,保障同时运行的工作最多有两个

addTask(1000,"1");
 addTask(500,"2");
 addTask(300,"3");
 addTask(400,"4");
 的输入程序是:2 3 1 4

 整个的残缺执行流程:一开始 1、2 两个工作开始执行
500ms 时,2 工作执行结束,输入 2,工作 3 开始执行
800ms 时,3 工作执行结束,输入 3,工作 4 开始执行
1000ms 时,1 工作执行结束,输入 1,此时只剩下 4 工作在执行
1200ms 时,4 工作执行结束,输入 4 

实现代码如下:

class Scheduler {constructor(limit) {this.queue = [];
    this.maxCount = limit;
    this.runCounts = 0;
  }
  add(time, order) {const promiseCreator = () => {return new Promise((resolve, reject) => {setTimeout(() => {console.log(order);
          resolve();}, time);
      });
    };
    this.queue.push(promiseCreator);
  }
  taskStart() {for (let i = 0; i < this.maxCount; i++) {this.request();
    }
  }
  request() {if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {return;}
    this.runCounts++;
    this.queue
      .shift()()
      .then(() => {
        this.runCounts--;
        this.request();});
  }
}
const scheduler = new Scheduler(2);
const addTask = (time, order) => {scheduler.add(time, order);
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
scheduler.taskStart();

对象数组列表转成树形构造(解决菜单)

[
    {
        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;
}
退出移动版