关于javascript:美团前端一面必会手写面试题汇总

43次阅读

共计 11783 个字符,预计需要花费 30 分钟才能阅读完成。

手写 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);
  }
};

二叉树档次遍历

// 二叉树档次遍历

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()))

模板引擎实现

let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
  name: '姓名',
  age: 18
}
render(template, data); // 我是姓名,年龄 18,性别 undefined

function render(template, data) {const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) { // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找以后模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的构造
  }
  return template; // 如果模板没有模板字符串间接返回
}

实现数组去重

给定某无序数组,要求去除数组中的反复数字并且返回新的无反复数组。

ES6 办法(应用数据结构汇合):

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

ES5 办法:应用 map 存储不反复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

uniqueArray(array); // [1, 2, 3, 5, 9, 8]

function uniqueArray(array) {let map = {};
  let res = [];
  for(var i = 0; i < array.length; i++) {if(!map.hasOwnProperty([array[i]])) {map[array[i]] = 1;
      res.push(array[i]);
    }
  }
  return res;
}

实现字符串的 repeat 办法

输出字符串 s,以及其反复的次数,输入反复的后果,例如输出 abc,2,输入 abcabc。

function repeat(s, n) {return (new Array(n + 1)).join(s);
}

递归:

function repeat(s, n) {return (n > 0) ? s.concat(repeat(s, --n)) : "";
}

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

数字有小数版本:

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'

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

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

参考:前端手写面试题具体解答

手写节流函数

函数节流是指规定一个单位工夫,在这个单位工夫内,只能有一次触发事件的回调函数执行,如果在同一个单位工夫内某事件被触发屡次,只有一次能失效。节流能够应用在 scroll 函数的事件监听上,通过事件节流来升高事件调用的频率。

// 函数节流的实现;
function throttle(fn, delay) {let curTime = Date.now();

  return function() {
    let context = this,
        args = arguments,
        nowTime = Date.now();

    // 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - curTime >= delay) {curTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

手写 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;
};

实现双向数据绑定

let obj = {}
let input = document.getElementById('input')
let span = document.getElementById('span')
// 数据劫持
Object.defineProperty(obj, 'text', {
  configurable: true,
  enumerable: true,
  get() {console.log('获取数据了')
  },
  set(newVal) {console.log('数据更新了')
    input.value = newVal
    span.innerHTML = newVal
  }
})
// 输出监听
input.addEventListener('keyup', function(e) {obj.text = e.target.value})

实现数组的 filter 办法

Array.prototype._filter = function(fn) {if (typeof fn !== "function") {throw Error('参数必须是一个函数');
    }
    const res = [];
    for (let i = 0, len = this.length; i < len; i++) {fn(this[i]) && res.push(this[i]);
    }
    return res;
}

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

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

实现有并行限度的 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();

查找数组公共前缀(美团)

题目形容

编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。示例 1:输出:strs = ["flower","flow","flight"]
输入:"fl"

示例 2:输出:strs = ["dog","racecar","car"]
输入:""
解释:输出不存在公共前缀。

答案

const longestCommonPrefix = function (strs) {const str = strs[0];
  let index = 0;
  while (index < str.length) {const strCur = str.slice(0, index + 1);
    for (let i = 0; i < strs.length; i++) {if (!strs[i] || !strs[i].startsWith(strCur)) {return str.slice(0, index);
      }
    }
    index++;
  }
  return str;
};

循环打印红黄绿

上面来看一道比拟典型的问题,通过这个问题来比照几种异步编程办法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯一直交替反复亮灯?

三个亮灯函数:

function red() {console.log('red');
}
function green() {console.log('green');
}
function yellow() {console.log('yellow');
}

这道题简单的中央在于 须要“交替反复”亮灯,而不是“亮完一次”就完结了。

(1)用 callback 实现

const task = (timer, light, callback) => {setTimeout(() => {if (light === 'red') {red()
        }
        else if (light === 'green') {green()
        }
        else if (light === 'yellow') {yellow()
        }
        callback()}, timer)
}
task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', Function.prototype)
    })
})

这里存在一个 bug:代码只是实现了一次流程,执行后红黄绿灯别离只亮一次。该如何让它交替反复进行呢?

下面提到过递归,能够递归亮灯的一个周期:

const step = () => {task(3000, 'red', () => {task(2000, 'green', () => {task(1000, 'yellow', step)
        })
    })
}
step()

留神看黄灯亮的回调里又再次调用了 step 办法 以实现循环亮灯。

(2)用 promise 实现

const task = (timer, light) => 
    new Promise((resolve, reject) => {setTimeout(() => {if (light === 'red') {red()
            }
            else if (light === 'green') {green()
            }
            else if (light === 'yellow') {yellow()
            }
            resolve()}, timer)
    })
const step = () => {task(3000, 'red')
        .then(() => task(2000, 'green'))
        .then(() => task(2100, 'yellow'))
        .then(step)
}
step()

这里将回调移除,在一次亮灯完结后,resolve 以后 promise,并仍然应用递归进行。

(3)用 async/await 实现

const taskRunner =  async () => {await task(3000, 'red')
    await task(2000, 'green')
    await task(2100, 'yellow')
    taskRunner()}
taskRunner()

Function.prototype.bind

Function.prototype.bind = function(context, ...args) {if (typeof this !== 'function') {throw new Error("Type Error");
  }
  // 保留 this 的值
  var self = this;

  return function F() {
    // 思考 new 的状况
    if(this instanceof F) {return new self(...args, ...arguments)
    }
    return self.apply(context, [...args, ...arguments])
  }
}

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

Object.assign

Object.assign()办法用于将所有可枚举属性的值从一个或多个源对象复制到指标对象。它将返回指标对象(请留神这个操作是浅拷贝)

Object.defineProperty(Object, 'assign', {value: function(target, ...args) {if (target == null) {return new TypeError('Cannot convert undefined or null to object');
    }

    // 指标对象须要对立是援用数据类型,若不是会主动转换
    const to = Object(target);

    for (let i = 0; i < args.length; i++) {
      // 每一个源对象
      const nextSource = args[i];
      if (nextSource !== null) {
        // 应用 for...in 和 hasOwnProperty 双重判断,确保只拿到自身的属性、办法(不蕴含继承的)for (const nextKey in nextSource) {if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {to[nextKey] = nextSource[nextKey];
          }
        }
      }
    }
    return to;
  },
  // 不可枚举
  enumerable: false,
  writable: true,
  configurable: true,
})

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

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

Array.prototype.reduce()

Array.prototype.reduce = function(callback, initialValue) {if (this == undefined) {throw new TypeError('this is null or not defined');
  }
  if (typeof callback !== 'function') {throw new TypeError(callbackfn + 'is not a function');
  }
  const O = Object(this);
  const len = this.length >>> 0;
  let accumulator = initialValue;
  let k = 0;
  // 如果第二个参数为 undefined 的状况下
  // 则数组的第一个有效值作为累加器的初始值
  if (accumulator === undefined) {while (k < len && !(k in O)) {k++;}
    // 如果超出数组界线还没有找到累加器的初始值,则 TypeError
    if (k >= len) {throw new TypeError('Reduce of empty array with no initial value');
    }
    accumulator = O[k++];
  }
  while (k < len) {if (k in O) {accumulator = callback.call(undefined, accumulator, O[k], k, O);
    }
    k++;
  }
  return accumulator;
}

正文完
 0