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

[    {        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;}

前端手写面试题具体解答

实现一个padStart()或padEnd()的polyfil

String.prototype.padStartString.prototype.padEndES8中新增的办法,容许将空字符串或其余字符串增加到原始字符串的结尾或结尾。咱们先看下应用语法:

String.padStart(targetLength,[padString])

用法:

'x'.padStart(4, 'ab') // 'abax''x'.padEnd(5, 'ab') // 'xabab'// 1. 若是输出的指标长度小于字符串本来的长度则返回字符串自身'xxx'.padStart(2, 's') // 'xxx'// 2. 第二个参数的默认值为 " ",长度是为1的// 3. 而此参数可能是个不确定长度的字符串,若是要填充的内容达到了指标长度,则将不要的局部截取'xxx'.padStart(5, 'sss') // ssxxx// 4. 可用来解决日期、金额格式化问题'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12"

polyfill实现:

String.prototype.myPadStart = function (targetLen, padString = " ") {  if (!targetLen) {    throw new Error('请输出须要填充到的长度');  }  let originStr = String(this); // 获取到调用的字符串, 因为this本来是String{},所以须要用String转为字符串  let originLen = originStr.length; // 调用的字符串本来的长度  if (originLen >= targetLen) return originStr; // 若是 本来 > 指标 则返回本来字符串  let diffNum = targetLen - originLen; // 10 - 6 // 差值  for (let i = 0; i < diffNum; i++) { // 要增加几个成员    for (let j = 0; j < padString.length; j++) { // 输出的padString的长度可能不为1      if (originStr.length === targetLen) break; // 判断每一次增加之后是否到了指标长度      originStr = `${padString[j]}${originStr}`;    }    if (originStr.length === targetLen) break;  }  return originStr;}console.log('xxx'.myPadStart(16))console.log('xxx'.padStart(16))

还是比较简单的,而padEnd的实现和它一样,只须要把第二层for循环里的${padString[j]}${orignStr}换下地位就能够了。

实现字符串翻转

在字符串的原型链上增加一个办法,实现字符串翻转:

String.prototype._reverse = function(a){    return a.split("").reverse().join("");}var obj = new String();var res = obj._reverse ('hello');console.log(res);    // olleh

须要留神的是,必须通过实例化对象之后再去调用定义的办法,不然找不到该办法。

实现instanceOf

// 模仿 instanceoffunction instance_of(L, R) {  //L 示意左表达式,R 示意右表达式  var O = R.prototype; // 取 R 的显示原型  L = L.__proto__; // 取 L 的隐式原型  while (true) {    if (L === null) return false;    if (O === L)      // 这里重点:当 O 严格等于 L 时,返回 true      return true;    L = L.__proto__;  }}

实现观察者模式

观察者模式(基于公布订阅模式) 有观察者,也有被观察者

观察者须要放到被观察者中,被观察者的状态变动须要告诉观察者 我变动了 外部也是基于公布订阅模式,收集观察者,状态变动后要被动告诉观察者

class Subject { // 被观察者 学生  constructor(name) {    this.state = 'happy'    this.observers = []; // 存储所有的观察者  }  // 收集所有的观察者  attach(o){ // Subject. prototype. attch    this.observers.push(o)  }  // 更新被观察者 状态的办法  setState(newState) {    this.state = newState; // 更新状态    // this 指被观察者 学生    this.observers.forEach(o => o.update(this)) // 告诉观察者 更新它们的状态  }}class Observer{ // 观察者 父母和老师  constructor(name) {    this.name = name  }  update(student) {    console.log('以后' + this.name + '被告诉了', '以后学生的状态是' + student.state)  }}let student = new Subject('学生'); let parent = new Observer('父母'); let teacher = new Observer('老师'); // 被观察者存储观察者的前提,须要先接收观察者student. attach(parent); student. attach(teacher); student. setState('被欺侮了');

实现some办法

Array.prototype.mySome=function(callback, context = window){             var len = this.length,                 flag=false,           i = 0;             for(;i < len; i++){                if(callback.apply(context, [this[i], i , this])){                    flag=true;                    break;                }              }             return flag;        }        // var flag=arr.mySome((v,index,arr)=>v.num>=10,obj)        // console.log(flag);

实现一下hash路由

根底的html代码:

<html>  <style>    html, body {      margin: 0;      height: 100%;    }    ul {      list-style: none;      margin: 0;      padding: 0;      display: flex;      justify-content: center;    }    .box {      width: 100%;      height: 100%;      background-color: red;    }  </style>  <body>  <ul>    <li>      <a href="#red">红色</a>    </li>    <li>      <a href="#green">绿色</a>    </li>    <li>      <a href="#purple">紫色</a>    </li>  </ul>  </body></html>

简略实现:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  window.onhashchange = function (e) {    const color = hash.slice(1)    box.style.background = color  }</script>

封装成一个class:

<script>  const box = document.getElementsByClassName('box')[0];  const hash = location.hash  class HashRouter {    constructor (hashStr, cb) {      this.hashStr = hashStr      this.cb = cb      this.watchHash()      this.watch = this.watchHash.bind(this)      window.addEventListener('hashchange', this.watch)    }    watchHash () {      let hash = window.location.hash.slice(1)      this.hashStr = hash      this.cb(hash)    }  }  new HashRouter('red', (color) => {    box.style.background = color  })</script>

版本号排序的办法

题目形容:有一组版本号如下 ['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);

手写深度比拟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;}

实现Array.isArray办法

Array.myIsArray = function(o) {  return Object.prototype.toString.call(Object(o)) === '[object Array]';};console.log(Array.myIsArray([])); // true

手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则从新计时。这能够应用在一些点击申请的事件上,防止因为用户的屡次点击向后端发送屡次申请。

// 函数防抖的实现function debounce(fn, wait) {  let timer = null;  return function() {    let context = this,        args = arguments;    // 如果此时存在定时器的话,则勾销之前的定时器从新记时    if (timer) {      clearTimeout(timer);      timer = null;    }    // 设置定时器,使事件间隔指定事件后执行    timer = setTimeout(() => {      fn.apply(context, args);    }, wait);  };}

设计一个办法提取对象中所有value大于2的键值对并返回最新的对象

实现:

var obj = { a: 1, b: 3, c: 4 }foo(obj) // { b: 3, c: 4 }

办法有很多种,这里提供一种比拟简洁的写法,用到了ES10Object.fromEntries()

var obj = { a: 1, b: 3, c: 4 }function foo (obj) {  return Object.fromEntries(    Object.entries(obj).filter(([key, value]) => value > 2)  )}var obj2 = foo(obj) // { b: 3, c: 4 }console.log(obj2)
// ES8中 Object.entries()的作用:var obj = { a: 1, b: 2 }var entries = Object.entries(obj); // [['a', 1], ['b', 2]]// ES10中 Object.fromEntries()的作用:Object.fromEntries(entries); // { a: 1, b: 2 }

实现call办法

call做了什么:

  • 将函数设为对象的属性
  • 执行和删除这个函数
  • 指定this到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
// 模仿 call bar.mycall(null);//实现一个call办法:// 原理:利用 context.xxx = self obj.xx = func-->obj.xx()Function.prototype.myCall = function(context = window, ...args) {  if (typeof this !== "function") {    throw new Error('type error')  }  // this-->func  context--> obj  args--> 传递过去的参数  // 在context上加一个惟一值不影响context上的属性  let key = Symbol('key')  context[key] = this; // context为调用的上下文,this此处为函数,将这个函数作为context的办法  // let args = [...arguments].slice(1)   //第一个参数为obj所以删除,伪数组转为数组  // 绑定参数 并执行函数  let result = context[key](...args);  // 革除定义的this 不删除会导致context属性越来越多  delete context[key];  // 返回后果   return result;};
//用法:f.call(obj,arg1)function f(a,b){ console.log(a+b) console.log(this.name)}let obj={ name:1}f.myCall(obj,1,2) //否则this指向window

查找文章中呈现频率最高的单词

function findMostWord(article) {  // 合法性判断  if (!article) return;  // 参数解决  article = article.trim().toLowerCase();  let wordList = article.match(/[a-z]+/g),    visited = [],    maxNum = 0,    maxWord = "";  article = " " + wordList.join("  ") + " ";  // 遍历判断单词呈现次数  wordList.forEach(function(item) {    if (visited.indexOf(item) < 0) {      // 退出 visited       visited.push(item);      let word = new RegExp(" " + item + " ", "g"),        num = article.match(word).length;      if (num > maxNum) {        maxNum = num;        maxWord = item;      }    }  });  return maxWord + "  " + maxNum;}

原生实现

function ajax() {  let xhr = new XMLHttpRequest() //实例化,以调用办法  xhr.open('get', 'https://www.google.com')  //参数2,url。参数三:异步  xhr.onreadystatechange = () => {  //每当 readyState 属性扭转时,就会调用该函数。    if (xhr.readyState === 4) {  //XMLHttpRequest 代理以后所处状态。      if (xhr.status >= 200 && xhr.status < 300) {  //200-300申请胜利        let string = request.responseText        //JSON.parse() 办法用来解析JSON字符串,结构由字符串形容的JavaScript值或对象        let object = JSON.parse(string)      }    }  }  request.send() //用于理论收回 HTTP 申请。不带参数为GET申请}

字符串查找

请应用最根本的遍从来实现判断字符串 a 是否被蕴含在字符串 b 中,并返回第一次呈现的地位(找不到返回 -1)。
a='34';b='1234567'; // 返回 2a='35';b='1234567'; // 返回 -1a='355';b='12354355'; // 返回 5isContain(a,b);
function isContain(a, b) {  for (let i in b) {    if (a[0] === b[i]) {      let tmp = true;      for (let j in a) {        if (a[j] !== b[~~i + ~~j]) {          tmp = false;        }      }      if (tmp) {        return i;      }    }  }  return -1;}

实现日期格式化函数

输出:

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日
const dateFormat = (dateInput, format)=>{    var day = dateInput.getDate()     var month = dateInput.getMonth() + 1      var year = dateInput.getFullYear()       format = format.replace(/yyyy/, year)    format = format.replace(/MM/,month)    format = format.replace(/dd/,day)    return format}

实现Promise

var PromisePolyfill = (function () {  // 和reject不同的是resolve须要尝试开展thenable对象  function tryToResolve (value) {    if (this === value) {    // 次要是避免上面这种状况    // let y = new Promise(res => setTimeout(res(y)))      throw TypeError('Chaining cycle detected for promise!')    }    // 依据标准2.32以及2.33 对对象或者函数尝试开展    // 保障S6之前的 polyfill 也能和ES6的原生promise混用    if (value !== null &&      (typeof value === 'object' || typeof value === 'function')) {      try {      // 这里记录这次then的值同时要被try包裹      // 次要起因是 then 可能是一个getter, 也也就是说      //   1. value.then可能报错      //   2. value.then可能产生副作用(例如屡次执行可能后果不同)        var then = value.then        // 另一方面, 因为无奈保障 then 的确会像预期的那样只调用一个onFullfilled / onRejected        // 所以减少了一个flag来避免resolveOrReject被屡次调用        var thenAlreadyCalledOrThrow = false        if (typeof then === 'function') {        // 是thenable 那么尝试开展        // 并且在该thenable状态扭转之前this对象的状态不变          then.bind(value)(          // onFullfilled            function (value2) {              if (thenAlreadyCalledOrThrow) return              thenAlreadyCalledOrThrow = true              tryToResolve.bind(this, value2)()            }.bind(this),            // onRejected            function (reason2) {              if (thenAlreadyCalledOrThrow) return              thenAlreadyCalledOrThrow = true              resolveOrReject.bind(this, 'rejected', reason2)()            }.bind(this)          )        } else {        // 领有then 然而then不是一个函数 所以也不是thenable          resolveOrReject.bind(this, 'resolved', value)()        }      } catch (e) {        if (thenAlreadyCalledOrThrow) return        thenAlreadyCalledOrThrow = true        resolveOrReject.bind(this, 'rejected', e)()      }    } else {    // 根本类型 间接返回      resolveOrReject.bind(this, 'resolved', value)()    }  }  function resolveOrReject (status, data) {    if (this.status !== 'pending') return    this.status = status    this.data = data    if (status === 'resolved') {      for (var i = 0; i < this.resolveList.length; ++i) {        this.resolveList[i]()      }    } else {      for (i = 0; i < this.rejectList.length; ++i) {        this.rejectList[i]()      }    }  }  function Promise (executor) {    if (!(this instanceof Promise)) {      throw Error('Promise can not be called without new !')    }    if (typeof executor !== 'function') {    // 非标准 但与Chrome谷歌保持一致      throw TypeError('Promise resolver ' + executor + ' is not a function')    }    this.status = 'pending'    this.resolveList = []    this.rejectList = []    try {      executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))    } catch (e) {      resolveOrReject.bind(this, 'rejected', e)()    }  }  Promise.prototype.then = function (onFullfilled, onRejected) {  // 返回值穿透以及谬误穿透, 留神谬误穿透用的是throw而不是return,否则的话  // 这个then返回的promise状态将变成resolved即接下来的then中的onFullfilled  // 会被调用, 然而咱们想要调用的是onRejected    if (typeof onFullfilled !== 'function') {      onFullfilled = function (data) {        return data      }    }    if (typeof onRejected !== 'function') {      onRejected = function (reason) {        throw reason      }    }    var executor = function (resolve, reject) {      setTimeout(function () {        try {        // 拿到对应的handle函数解决this.data        // 并以此为根据解析这个新的Promise          var value = this.status === 'resolved'            ? onFullfilled(this.data)            : onRejected(this.data)          resolve(value)        } catch (e) {          reject(e)        }      }.bind(this))    }    // then 承受两个函数返回一个新的Promise    // then 本身的执行永远异步与onFullfilled/onRejected的执行    if (this.status !== 'pending') {      return new Promise(executor.bind(this))    } else {    // pending      return new Promise(function (resolve, reject) {        this.resolveList.push(executor.bind(this, resolve, reject))        this.rejectList.push(executor.bind(this, resolve, reject))      }.bind(this))    }  }  // for prmise A+ test  Promise.deferred = Promise.defer = function () {    var dfd = {}    dfd.promise = new Promise(function (resolve, reject) {      dfd.resolve = resolve      dfd.reject = reject    })    return dfd  }  // for prmise A+ test  if (typeof module !== 'undefined') {    module.exports = Promise  }  return Promise})()PromisePolyfill.all = function (promises) {  return new Promise((resolve, reject) => {    const result = []    let cnt = 0    for (let i = 0; i < promises.length; ++i) {      promises[i].then(value => {        cnt++        result[i] = value        if (cnt === promises.length) resolve(result)      }, reject)    }  })}PromisePolyfill.race = function (promises) {  return new Promise((resolve, reject) => {    for (let i = 0; i < promises.length; ++i) {      promises[i].then(resolve, reject)    }  })}

图片懒加载

能够给img标签对立自定义属性data-src='default.png',当检测到图片呈现在窗口之后再补充src属性,此时才会进行图片资源加载。

function lazyload() {  const imgs = document.getElementsByTagName('img');  const len = imgs.length;  // 视口的高度  const viewHeight = document.documentElement.clientHeight;  // 滚动条高度  const scrollHeight = document.documentElement.scrollTop || document.body.scrollTop;  for (let i = 0; i < len; i++) {    const offsetHeight = imgs[i].offsetTop;    if (offsetHeight < viewHeight + scrollHeight) {      const src = imgs[i].dataset.src;      imgs[i].src = src;    }  }}// 能够应用节流优化一下window.addEventListener('scroll', lazyload);

基于Promise.all实现Ajax的串行和并行

基于Promise.all实现Ajax的串行和并行
  • 串行:申请是异步的,须要期待上一个申请胜利,能力执行下一个申请
  • 并行:同时发送多个申请「HTTP申请能够同时进行,然而JS的操作都是一步步的来的,因为JS是单线程」,期待所有申请都胜利,咱们再去做什么事件?
Promise.all([    axios.get('/user/list'),    axios.get('/user/list'),    axios.get('/user/list')]).then(results => {    console.log(results);}).catch(reason => {});

Promise.all并发限度及async-pool的利用

并发限度指的是,每个时刻并发执行的promise数量是固定的,最终的执行后果还是放弃与原来的
const delay = function delay(interval) {    return new Promise((resolve, reject) => {        setTimeout(() => {            // if (interval === 1003) reject('xxx');            resolve(interval);        }, interval);    });};let tasks = [() => {    return delay(1000);}, () => {    return delay(1003);}, () => {    return delay(1005);}, () => {    return delay(1002);}, () => {    return delay(1004);}, () => {    return delay(1006);}];/* Promise.all(tasks.map(task => task())).then(results => {    console.log(results);}); */let results = [];asyncPool(2, tasks, (task, next) => {    task().then(result => {        results.push(result);        next();    });}, () => {    console.log(results);});
const delay = function delay(interval) {    return new Promise((resolve, reject) => {        setTimeout(() => {            resolve(interval);        }, interval);    });};let tasks = [() => {    return delay(1000);}, () => {    return delay(1003);}, () => {    return delay(1005);}, () => {    return delay(1002);}, () => {    return delay(1004);}, () => {    return delay(1006);}];

JS实现Ajax并发申请管制的两大解决方案

tasks:数组,数组蕴含很多办法,每一个办法执行就是发送一个申请「基于Promise治理」
function createRequest(tasks, pool) {    pool = pool || 5;    let results = [],        together = new Array(pool).fill(null),        index = 0;    together = together.map(() => {        return new Promise((resolve, reject) => {            const run = function run() {                if (index >= tasks.length) {                    resolve();                    return;                };                let old_index = index,                    task = tasks[index++];                task().then(result => {                    results[old_index] = result;                    run();                }).catch(reason => {                    reject(reason);                });            };            run();        });    });    return Promise.all(together).then(() => results);} /* createRequest(tasks, 2).then(results => {    // 都胜利,整体才是胜利,按顺序存储后果    console.log('胜利-->', results);}).catch(reason => {    // 只有有也给失败,整体就是失败    console.log('失败-->', reason);}); */
function createRequest(tasks, pool, callback) {    if (typeof pool === "function") {        callback = pool;        pool = 5;    }    if (typeof pool !== "number") pool = 5;    if (typeof callback !== "function") callback = function () {};    //------    class TaskQueue {        running = 0;        queue = [];        results = [];        pushTask(task) {            let self = this;            self.queue.push(task);            self.next();        }        next() {            let self = this;            while (self.running < pool && self.queue.length) {                self.running++;                let task = self.queue.shift();                task().then(result => {                    self.results.push(result);                }).finally(() => {                    self.running--;                    self.next();                });            }            if (self.running === 0) callback(self.results);        }    }    let TQ = new TaskQueue;    tasks.forEach(task => TQ.pushTask(task));}createRequest(tasks, 2, results => {    console.log(results);});