写点什么

掌握这些前端手写面试题能进大厂吗

  • 2022-11-18
    浙江
  • 本文字数:9773 字

    阅读完需:约 32 分钟

reduce 用法汇总

语法


array.reduce(function(total, currentValue, currentIndex, arr), initialValue);/*  total: 必需。初始值, 或者计算结束后的返回值。  currentValue: 必需。当前元素。  currentIndex: 可选。当前元素的索引;                       arr: 可选。当前元素所属的数组对象。  initialValue: 可选。传递给函数的初始值,相当于total的初始值。*/
复制代码


reduceRight() 该方法用法与reduce()其实是相同的,只是遍历的顺序相反,它是从数组的最后一项开始,向前遍历到第一项


1. 数组求和


const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num);
// 设定初始值求和const arr = [12, 34, 23];const sum = arr.reduce((total, num) => total + num, 10); // 以10为初始值求和

// 对象数组求和var result = [ { subject: 'math', score: 88 }, { subject: 'chinese', score: 95 }, { subject: 'english', score: 80 }];const sum = result.reduce((accumulator, cur) => accumulator + cur.score, 0); const sum = result.reduce((accumulator, cur) => accumulator + cur.score, -10); // 总分扣除10分
复制代码


2. 数组最大值


const a = [23,123,342,12];const max = a.reduce((pre,next)=>pre>cur?pre:cur,0); // 342
复制代码


3. 数组转对象


var streams = [{name: '技术', id: 1}, {name: '设计', id: 2}];var obj = streams.reduce((accumulator, cur) => {accumulator[cur.id] = cur; return accumulator;}, {});
复制代码


4. 扁平一个二维数组


var arr = [[1, 2, 8], [3, 4, 9], [5, 6, 10]];var res = arr.reduce((x, y) => x.concat(y), []);
复制代码


5. 数组去重


实现的基本原理如下:
① 初始化一个空数组② 将需要去重处理的数组中的第1项在初始化数组中查找,如果找不到(空数组中肯定找不到),就将该项添加到初始化数组中③ 将需要去重处理的数组中的第2项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中④ ……⑤ 将需要去重处理的数组中的第n项在初始化数组中查找,如果找不到,就将该项继续添加到初始化数组中⑥ 将这个初始化数组返回
复制代码


var newArr = arr.reduce(function (prev, cur) {    prev.indexOf(cur) === -1 && prev.push(cur);    return prev;},[]);
复制代码


6. 对象数组去重


const dedup = (data, getKey = () => { }) => {    const dateMap = data.reduce((pre, cur) => {        const key = getKey(cur)        if (!pre[key]) {            pre[key] = cur        }        return pre    }, {})    return Object.values(dateMap)}
复制代码


7. 求字符串中字母出现的次数


const str = 'sfhjasfjgfasjuwqrqadqeiqsajsdaiwqdaklldflas-cmxzmnha';
const res = str.split('').reduce((pre,next)=>{ pre[next] ? pre[next]++ : pre[next] = 1 return pre },{})
复制代码


// 结果-: 1a: 8c: 1d: 4e: 1f: 4g: 1h: 2i: 2j: 4k: 1l: 3m: 2n: 1q: 5r: 1s: 6u: 1w: 2x: 1z: 1
复制代码


8. compose 函数


redux compose 源码实现


function compose(...funs) {    if (funs.length === 0) {        return arg => arg;    }    if (funs.length === 1) {       return funs[0];    }    return funs.reduce((a, b) => (...arg) => a(b(...arg)))}
复制代码

手写节流函数

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 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); } };}
复制代码

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;}
复制代码

图片懒加载

可以给 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 并行限制

就是实现有并行限制的 Promise 调度器问题


class Scheduler {  constructor() {    this.queue = [];    this.maxCount = 2;    this.runCounts = 0;  }  add(promiseCreator) {    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 timeout = time => new Promise(resolve => { setTimeout(resolve, time);})
const scheduler = new Scheduler();
const addTask = (time,order) => { scheduler.add(() => timeout(time).then(()=>console.log(order)))}

addTask(1000, '1');addTask(500, '2');addTask(300, '3');addTask(400, '4');scheduler.taskStart()// 2// 3// 1// 4
复制代码

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


参考:前端手写面试题详细解答

将 js 对象转化为树形结构

// 转换前:source = [{            id: 1,            pid: 0,            name: 'body'          }, {            id: 2,            pid: 1,            name: 'title'          }, {            id: 3,            pid: 2,            name: 'div'          }]// 转换为: tree = [{          id: 1,          pid: 0,          name: 'body',          children: [{            id: 2,            pid: 1,            name: 'title',            children: [{              id: 3,              pid: 1,              name: 'div'            }]          }        }]
复制代码


代码实现:


function jsonToTree(data) {  // 初始化结果数组,并判断输入数据的格式  let result = []  if(!Array.isArray(data)) {    return result  }  // 使用map,将当前对象的id与当前对象对应存储起来  let map = {};  data.forEach(item => {    map[item.id] = item;  });  //   data.forEach(item => {    let parent = map[item.pid];    if(parent) {      (parent.children || (parent.children = [])).push(item);    } else {      result.push(item);    }  });  return result;}
复制代码

手写 Object.create

思路:将传入的对象作为原型


function create(obj) {  function F() {}  F.prototype = obj  return new F()}
复制代码

实现防抖函数(debounce)

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


那么与节流函数的区别直接看这个动画实现即可。


手写简化版:


// 防抖函数const debounce = (fn, delay) => {  let timer = null;  return (...args) => {    clearTimeout(timer);    timer = setTimeout(() => {      fn.apply(this, args);    }, delay);  };};
复制代码


适用场景:


  • 按钮提交场景:防止多次提交按钮,只执行最后提交的一次

  • 服务端验证场景:表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似


生存环境请用 lodash.debounce

实现 JSON.parse

var json = '{"name":"cxk", "age":25}';var obj = eval("(" + json + ")");
复制代码


此方法属于黑魔法,极易容易被 xss 攻击,还有一种new Function大同小异。

手写 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;};
复制代码

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 结果{ user: 'anonymous',  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型  city: '北京', // 中文需解码  enabled: true, // 未指定值得 key 约定为 true}*/
复制代码


function parseParam(url) {  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中  let paramsObj = {};  // 将 params 存到对象中  paramsArr.forEach(param => {    if (/=/.test(param)) { // 处理有 value 的参数      let [key, val] = param.split('='); // 分割 key 和 value      val = decodeURIComponent(val); // 解码      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创建 key 并设置值 paramsObj[key] = val; } } else { // 处理没有 value 的参数 paramsObj[param] = true; } })
return paramsObj;}
复制代码

实现一个拖拽

<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;    }  }}
复制代码

实现字符串的 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)) : "";}
复制代码

手写 instanceof 方法

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。


实现步骤:


  1. 首先获取类型的原型

  2. 然后获得对象的原型

  3. 然后一直循环判断对象的原型是否等于类型的原型,直到对象原型为 null,因为原型链最终为 null


具体实现:


function myInstanceof(left, right) {  let proto = Object.getPrototypeOf(left), // 获取对象的原型      prototype = right.prototype; // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上 while (true) { if (!proto) return false; if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto); }}
复制代码

实现千位分隔符

// 保留三位小数parseToMoney(1234.56); // return '1,234.56'parseToMoney(123456789); // return '123,456,789'parseToMoney(1087654.321); // return '1,087,654.321'
复制代码


function parseToMoney(num) {  num = parseFloat(num.toFixed(3));  let [integer, decimal] = String.prototype.split.call(num, '.');  integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');  return integer + '.' + (decimal ? decimal : '');}
复制代码

对象数组列表转成树形结构(处理菜单)

[    {        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;}
复制代码

实现一个 JSON.parse

JSON.parse(text[, reviver])
复制代码


用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)


第一种:直接调用 eval


function jsonParse(opt) {    return eval('(' + opt + ')');}jsonParse(jsonStringify({x : 5}))// Object { x: 5}jsonParse(jsonStringify([1, "false", false]))// [1, "false", falsr]jsonParse(jsonStringify({b: undefined}))// Object { b: "undefined"}
复制代码


避免在不必要的情况下使用 evaleval() 是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行 JS 代码,有 XSS 漏洞。


如果你只想记这个方法,就得对参数 json 做校验。


var rx_one = /^[\],:{}\s]*$/;var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;var rx_four = /(?:^|:|,)(?:\s*\[)+/g;if (    rx_one.test(        json            .replace(rx_two, "@")            .replace(rx_three, "]")            .replace(rx_four, "")    )) {    var obj = eval("(" +json + ")");}
复制代码


第二种:Function


核心:Function 与 eval 有相同的字符串参数特性


var func = new Function(arg1, arg2, ..., functionBody);
复制代码


在转换 JSON 的实际应用中,只需要这么做


var jsonStr = '{ "age": 20, "name": "jack" }'var json = (new Function('return ' + jsonStr))();
复制代码


evalFunction都有着动态编译 js 代码的作用,但是在实际的编程中并不推荐使用

解析 URL Params 为对象

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';parseParam(url)/* 结果{ user: 'anonymous',  id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型  city: '北京', // 中文需解码  enabled: true, // 未指定值得 key 约定为 true}*/

复制代码


function parseParam(url) {  const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来  const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中  let paramsObj = {};  // 将 params 存到对象中  paramsArr.forEach(param => {    if (/=/.test(param)) { // 处理有 value 的参数      let [key, val] = param.split('='); // 分割 key 和 value      val = decodeURIComponent(val); // 解码      val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值 paramsObj[key] = [].concat(paramsObj[key], val); } else { // 如果对象没有这个 key,创建 key 并设置值 paramsObj[key] = val; } } else { // 处理没有 value 的参数 paramsObj[param] = true; } })
return paramsObj;}

复制代码

实现 redux-thunk

redux-thunk 可以利用 redux 中间件让 redux 支持异步的 action


// 如果 action 是个函数,就调用这个函数// 如果 action 不是函数,就传给下一个中间件// 发现 action 是函数就调用const thunk = ({ dispatch, getState }) => (next) => (action) => {  if (typeof action === 'function') {    return action(dispatch, getState);  }
return next(action);};export default thunk
复制代码


用户头像

还未添加个人签名 2022-07-31 加入

还未添加个人简介

评论

发布
暂无评论
掌握这些前端手写面试题能进大厂吗_JavaScript_helloworld1024fd_InfoQ写作社区