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

例: 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}次`);

手写类型判断函数

function getType(value) {  // 判断数据是 null 的状况  if (value === null) {    return value + "";  }  // 判断数据是援用类型的状况  if (typeof value === "object") {    let valueClass = Object.prototype.toString.call(value),      type = valueClass.split(" ")[1].split("");    type.pop();    return type.join("").toLowerCase();  } else {    // 判断数据是根本数据类型的状况和函数的状况    return typeof value;  }}

实现Event(event bus)

event bus既是node中各个模块的基石,又是前端组件通信的依赖伎俩之一,同时波及了订阅-公布设计模式,是十分重要的根底。

简略版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};

面试版:

class EventEmeitter {  constructor() {    this._events = this._events || new Map(); // 贮存事件/回调键值对    this._maxListeners = this._maxListeners || 10; // 设立监听下限  }}// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  // 从贮存事件键值对的this._events中获取对应事件回调函数  handler = this._events.get(type);  if (args.length > 0) {    handler.apply(this, args);  } else {    handler.call(this);  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  // 将type事件以及对应的fn函数放入this._events中贮存  if (!this._events.get(type)) {    this._events.set(type, fn);  }};// 触发名为type的事件EventEmeitter.prototype.emit = function(type, ...args) {  let handler;  handler = this._events.get(type);  if (Array.isArray(handler)) {    // 如果是一个数组阐明有多个监听者,须要顺次此触发外面的函数    for (let i = 0; i < handler.length; i++) {      if (args.length > 0) {        handler[i].apply(this, args);      } else {        handler[i].call(this);      }    }  } else {    // 单个函数的状况咱们间接触发即可    if (args.length > 0) {      handler.apply(this, args);    } else {      handler.call(this);    }  }  return true;};// 监听名为type的事件EventEmeitter.prototype.addListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  if (!handler) {    this._events.set(type, fn);  } else if (handler && typeof handler === "function") {    // 如果handler是函数阐明只有一个监听者    this._events.set(type, [handler, fn]); // 多个监听者咱们须要用数组贮存  } else {    handler.push(fn); // 曾经有多个监听者,那么间接往数组里push函数即可  }};EventEmeitter.prototype.removeListener = function(type, fn) {  const handler = this._events.get(type); // 获取对应事件名称的函数清单  // 如果是函数,阐明只被监听了一次  if (handler && typeof handler === "function") {    this._events.delete(type, fn);  } else {    let postion;    // 如果handler是数组,阐明被监听屡次要找到对应的函数    for (let i = 0; i < handler.length; i++) {      if (handler[i] === fn) {        postion = i;      } else {        postion = -1;      }    }    // 如果找到匹配的函数,从数组中革除    if (postion !== -1) {      // 找到数组对应的地位,间接革除此回调      handler.splice(postion, 1);      // 如果革除后只有一个函数,那么勾销数组,以函数模式保留      if (handler.length === 1) {        this._events.set(type, handler[0]);      }    } else {      return this;    }  }};
实现具体过程和思路见实现event

手写 Promise.race

该办法的参数是 Promise 实例数组, 而后其 then 注册的回调办法是数组中的某一个 Promise 的状态变为 fulfilled 的时候就执行. 因为 Promise 的状态只能扭转一次, 那么咱们只须要把 Promise.race 中产生的 Promise 对象的 resolve 办法, 注入到数组中的每一个 Promise 实例中的回调函数中即可.

Promise.race = function (args) {  return new Promise((resolve, reject) => {    for (let i = 0, len = args.length; i < len; i++) {      args[i].then(resolve, reject)    }  })}

将数字每千分位用逗号隔开

数字有小数版本:

let format = n => {    let num = n.toString() // 转成字符串    let decimals = ''        // 判断是否有小数    num.indexOf('.') > -1 ? decimals = num.split('.')[1] : decimals    let len = num.length    if (len <= 3) {        return num    } else {        let temp = ''        let remainder = len % 3        decimals ? temp = '.' + decimals : temp        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',') + temp        } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',') + temp         }    }}format(12323.33)  // '12,323.33'

数字无小数版本:

let format = n => {    let num = n.toString()     let len = num.length    if (len <= 3) {        return num    } else {        let remainder = len % 3        if (remainder > 0) { // 不是3的整数倍            return num.slice(0, remainder) + ',' + num.slice(remainder, len).match(/\d{3}/g).join(',')         } else { // 是3的整数倍            return num.slice(0, len).match(/\d{3}/g).join(',')         }    }}format(1232323)  // '1,232,323'

实现apply办法

apply原理与call很类似,不多赘述

// 模仿 applyFunction.prototype.myapply = function(context, arr) {  var context = Object(context) || window;  context.fn = this;  var result;  if (!arr) {    result = context.fn();  } else {    var args = [];    for (var i = 0, len = arr.length; i < len; i++) {      args.push("arr[" + i + "]");    }    result = eval("context.fn(" + args + ")");  }  delete context.fn;  return result;};

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

手写 Promise.all

1) 外围思路

  1. 接管一个 Promise 实例的数组或具备 Iterator 接口的对象作为参数
  2. 这个办法返回一个新的 promise 对象,
  3. 遍历传入的参数,用Promise.resolve()将参数"包一层",使其变成一个promise对象
  4. 参数所有回调胜利才是胜利,返回值数组与参数程序统一
  5. 参数数组其中一个失败,则触发失败状态,第一个触发失败的 Promise 错误信息作为 Promise.all 的错误信息。

2)实现代码

一般来说,Promise.all 用来解决多个并发申请,也是为了页面数据结构的不便,将一个页面所用到的在不同接口的数据一起申请过去,不过,如果其中一个接口失败了,多个申请也就失败了,页面可能啥也出不来,这就看以后页面的耦合水平了

function promiseAll(promises) {  return new Promise(function(resolve, reject) {    if(!Array.isArray(promises)){        throw new TypeError(`argument must be a array`)    }    var resolvedCounter = 0;    var promiseNum = promises.length;    var resolvedResult = [];    for (let i = 0; i < promiseNum; i++) {      Promise.resolve(promises[i]).then(value=>{        resolvedCounter++;        resolvedResult[i] = value;        if (resolvedCounter == promiseNum) {            return resolve(resolvedResult)          }      },error=>{        return reject(error)      })    }  })}// testlet p1 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(1)    }, 1000)})let p2 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(2)    }, 2000)})let p3 = new Promise(function (resolve, reject) {    setTimeout(function () {        resolve(3)    }, 3000)})promiseAll([p3, p1, p2]).then(res => {    console.log(res) // [3, 1, 2]})

手写 apply 函数

apply 函数的实现步骤:

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

实现字符串翻转

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

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

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

转化为驼峰命名

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

二叉树档次遍历

// 二叉树档次遍历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++  }  // 档次遍历 队列  levelOrderTraversal(visitor) {    if(this.root == null) {      return    }    let stack = [this.root]    let index = 0 // 指针 指向0    let currentNode     while (currentNode = stack[index++]) {      // 反转二叉树      let tmp = currentNode.left      currentNode.left = currentNode.right      currentNode.right = tmp      visitor.visit(currentNode.element)      if(currentNode.left) {        stack.push(currentNode.left)      }      if(currentNode.right) {        stack.push(currentNode.right)      }    }  }}
// 测试var bst = new BST((a,b)=>a.age-b.age) // 模仿sort办法// ![](http://img-repo.poetries.top/images/20210522203619.png)// ![](http://img-repo.poetries.top/images/20210522211809.png)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})// 应用访问者模式class Visitor {  constructor() {    this.visit = function (elem) {      elem.age = elem.age*2    }  }}// ![](http://img-repo.poetries.top/images/20210523095515.png)console.log(bst.levelOrderTraversal(new Visitor()))

实现一个拖拽

<style>  html, body {    margin: 0;    height: 100%;  }  #box {    width: 100px;    height: 100px;    background-color: red;    position: absolute;    top: 100px;    left: 100px;  }</style>
<div id="box"></div>
window.onload = function () {  var box = document.getElementById('box');  box.onmousedown = function (ev) {    var oEvent = ev || window.event; // 兼容火狐,火狐下没有window.event    var distanceX = oEvent.clientX - box.offsetLeft; // 鼠标到可视区右边的间隔 - box到页面右边的间隔    var distanceY = oEvent.clientY - box.offsetTop;    document.onmousemove = function (ev) {      var oEvent = ev || window.event;      var left = oEvent.clientX - distanceX;      var top = oEvent.clientY - distanceY;      if (left <= 0) {        left = 0;      } else if (left >= document.documentElement.clientWidth - box.offsetWidth) {        left = document.documentElement.clientWidth - box.offsetWidth;      }      if (top <= 0) {        top = 0;      } else if (top >= document.documentElement.clientHeight - box.offsetHeight) {        top = document.documentElement.clientHeight - box.offsetHeight;      }      box.style.left = left + 'px';      box.style.top = top + 'px';    }    box.onmouseup = function () {      document.onmousemove = null;      box.onmouseup = null;    }  }}

判断括号字符串是否无效(小米)

题目形容

给定一个只包含 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否无效。无效字符串需满足:- 左括号必须用雷同类型的右括号闭合。- 左括号必须以正确的程序闭合。示例 1:输出:s = "()"输入:true示例 2:输出:s = "()[]{}"输入:true示例 3:输出:s = "(]"输入:false

答案

const isValid = function (s) {  if (s.length % 2 === 1) {    return false;  }  const regObj = {    "{": "}",    "(": ")",    "[": "]",  };  let stack = [];  for (let i = 0; i < s.length; i++) {    if (s[i] === "{" || s[i] === "(" || s[i] === "[") {      stack.push(s[i]);    } else {      const cur = stack.pop();      if (s[i] !== regObj[cur]) {        return false;      }    }  }  if (stack.length) {    return false;  }  return true;};

异步并发数限度

/** * 关键点 * 1. new promise 一经创立,立刻执行 * 2. 应用 Promise.resolve().then 能够把工作加到微工作队列,避免立刻执行迭代办法 * 3. 微工作处理过程中,产生的新的微工作,会在同一事件循环内,追加到微工作队列里 * 4. 应用 race 在某个工作实现时,持续增加工作,放弃工作依照最大并发数进行执行 * 5. 工作实现后,须要从 doingTasks 中移出 */function limit(count, array, iterateFunc) {  const tasks = []  const doingTasks = []  let i = 0  const enqueue = () => {    if (i === array.length) {      return Promise.resolve()    }    const task = Promise.resolve().then(() => iterateFunc(array[i++]))    tasks.push(task)    const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))    doingTasks.push(doing)    const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()    return res.then(enqueue)  };  return enqueue().then(() => Promise.all(tasks))}// testconst timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {  console.log(res)})

实现日期格式化函数

输出:

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}

实现数组的flat办法

function _flat(arr, depth) {  if(!Array.isArray(arr) || depth <= 0) {    return arr;  }  return arr.reduce((prev, cur) => {    if (Array.isArray(cur)) {      return prev.concat(_flat(cur, depth - 1))    } else {      return prev.concat(cur);    }  }, []);}

字符串最长的不反复子串

题目形容

给定一个字符串 s ,请你找出其中不含有反复字符的 最长子串 的长度。示例 1:输出: s = "abcabcbb"输入: 3解释: 因为无反复字符的最长子串是 "abc",所以其长度为 3。示例 2:输出: s = "bbbbb"输入: 1解释: 因为无反复字符的最长子串是 "b",所以其长度为 1。示例 3:输出: s = "pwwkew"输入: 3解释: 因为无反复字符的最长子串是 "wke",所以其长度为 3。     请留神,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。示例 4:输出: s = ""输入: 0

答案

const lengthOfLongestSubstring = function (s) {  if (s.length === 0) {    return 0;  }  let left = 0;  let right = 1;  let max = 0;  while (right <= s.length) {    let lr = s.slice(left, right);    const index = lr.indexOf(s[right]);    if (index > -1) {      left = index + left + 1;    } else {      lr = s.slice(left, right + 1);      max = Math.max(max, lr.length);    }    right++;  }  return max;};

树形构造转成列表(解决菜单)

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

判断对象是否存在循环援用

循环援用对象原本没有什么问题,然而序列化的时候就会产生问题,比方调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.

上面办法能够用来判断一个对象中是否已存在循环援用:

const isCycleObject = (obj,parent) => {    const parentArr = parent || [obj];    for(let i in obj) {        if(typeof obj[i] === 'object') {            let flag = false;            parentArr.forEach((pObj) => {                if(pObj === obj[i]){                    flag = true;                }            })            if(flag) return true;            flag = isCycleObject(obj[i],[...parentArr,obj[i]]);            if(flag) return true;        }    }    return false;}const a = 1;const b = {a};const c = {b};const o = {d:{a:3},c}o.c.b.aa = a;console.log(isCycleObject(o)

查找有序二维数组的目标值:

var findNumberIn2DArray = function(matrix, target) {    if (matrix == null || matrix.length == 0) {        return false;    }    let row = 0;    let column = matrix[0].length - 1;    while (row < matrix.length && column >= 0) {        if (matrix[row][column] == target) {            return true;        } else if (matrix[row][column] > target) {            column--;        } else {            row++;        }    }    return false;};

二维数组斜向打印:

function printMatrix(arr){  let m = arr.length, n = arr[0].length    let res = []  // 左上角,从0 到 n - 1 列进行打印  for (let k = 0; k < n; k++) {    for (let i = 0, j = k; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  // 右下角,从1 到 n - 1 行进行打印  for (let k = 1; k < m; k++) {    for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {      res.push(arr[i][j]);    }  }  return res}

实现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