「一劳永逸」送你21道高频JavaScript手写面试题(上)

简介: 「一劳永逸」送你21道高频JavaScript手写面试题

前言


基本上面试的时候,经常会遇到手撕 XXX 之类的问题,这次准备梳理总结一遍,巩固我们原生 JS 基础的同时,下次想复习面试手撕题的时候,找起来方便,也节省时间。


代码在这里 👉GitHub


梳理的顺序是随机的,不按照难以程度。


实现一个事件委托(易错)


事件委托这里就不阐述了,比如给 li 绑定点击事件


看错误版,(容易过的,看「面试官水平了」)👇

ul.addEventListener("click", function (e) {
  console.log(e, e.target);
  if (e.target.tagName.toLowerCase() === "li") {
    console.log("打印"); // 模拟fn
  }
});

「有个小 bug,如果用户点击的是 li 里面的 span,就没法触发 fn,这显然不对」👇

<ul id="xxx">
  下面的内容是子元素1
  <li>
    li内容>>> <span> 这是span内容123</span>
  </li>
  下面的内容是子元素2
  <li>
    li内容>>> <span> 这是span内容123</span>
  </li>
  下面的内容是子元素3
  <li>
    li内容>>> <span> 这是span内容123</span>
  </li>
</ul>

这样子的场景就是不对的,那我们看看高级版本 👇

function delegate(element, eventType, selector, fn) {
  element.addEventListener(
    eventType,
    (e) => {
      let el = e.target;
      while (!el.matches(selector)) {
        if (element === el) {
          el = null;
          break;
        }
        el = el.parentNode;
      }
      el && fn.call(el, e, el);
    },
    true
  );
  return element;
}

实现一个可以拖拽的 DIV


这个题目看起来简单,你可以试一试 30 分钟能不能完成,直接贴出代码吧 👇

<div id="xxx"></div>
var dragging = false;
var position = null;
xxx.addEventListener("mousedown", function (e) {
  dragging = true;
  position = [e.clientX, e.clientY];
});
document.addEventListener("mousemove", function (e) {
  if (dragging === false) return null;
  const x = e.clientX;
  const y = e.clientY;
  const deltaX = x - position[0];
  const deltaY = y - position[1];
  const left = parseInt(xxx.style.left || 0);
  const top = parseInt(xxx.style.top || 0);
  xxx.style.left = left + deltaX + "px";
  xxx.style.top = top + deltaY + "px";
  position = [x, y];
});
document.addEventListener("mouseup", function (e) {
  dragging = false;
});

手写防抖和节流函数


「节流 throttle」,规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。场景 👇


  • scroll 滚动事件,每隔特定描述执行回调函数
  • input 输入框,每个特定时间发送请求或是展开下拉列表,(防抖也可以)


节流重在加锁「flag = false」

function throttle(fn, delay) {
  let flag = true,
    timer = null;
  return function (...args) {
    let context = this;
    if (!flag) return;
    flag = false;
    clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(context, args);
      flag = true;
    }, delay);
  };
}

「防抖 debounce」,在事件被触发 n 秒后再执行回调,如果在这 n 秒内又被触发,则重新计时。场景 👇


  • 浏览器窗口大小 resize 避免次数过于频繁
  • 登录,发短信等按钮避免发送多次请求
  • 文本编辑器实时保存


防抖重在清零「clearTimeout(timer)」

function debounce(fn, delay) {
  let timer = null;
  return function (...args) {
    let context = this;
    if (timer) clearTimeout(timer);
    timer = setTimeout(function () {
      fn.apply(context, args);
    }, delay);
  };
}

实现数组去重


这个是 Array 数组测试用例 👇

var array = [
  1,
  1,
  "1",
  "1",
  null,
  null,
  undefined,
  undefined,
  new String("1"),
  new String("1"),
  /a/,
  /a/,
  NaN,
  NaN,
];

如何通过一个数组去重,给面试官留下深印象呢 👇


使用 Set

let unique_1 = (arr) => [...new Set(arr)];

使用 filter

function unique_2(array) {
  var res = array.filter(function (item, index, array) {
    return array.indexOf(item) === index;
  });
  return res;
}

使用 reduce

let unique_3 = (arr) =>
  arr.reduce((pre, cur) => (pre.includes(cur) ? pre : [...pre, cur]), []);

使用 Object 键值对 🐂🐂,这个也是去重最好的效果 👇

function unique_3(array) {
  var obj = {};
  return array.filter(function (item, index, array) {
    return obj.hasOwnProperty(typeof item + item)
      ? false
      : (obj[typeof item + item] = true);
  });
}

使用obj[typeof item + item] = true,原因就在于对象的键值只能是字符串,所以使用typeof item + item代替


实现柯里化函数


柯里化就是把接受「多个参数」的函数变换成接受一个「单一参数」的函数,并且返回接受「余下参数」返回结果的一种应用。


思路:


  • 判断传递的参数是否达到执行函数的 fn 个数
  • 没有达到的话,继续返回新的函数,并且返回 curry 函数传递剩余参数
let currying = (fn, ...args) =>
  fn.length > args.length
    ? (...arguments) => currying(fn, ...args, ...arguments)
    : fn(...args);

测试用例 👇

let addSum = (a, b, c) => a + b + c;
let add = curry(addSum);
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1, 2, 3));

实现数组 flat


「将多维度的数组降为一维数组」

Array.prototype.flat(num)
// num表示的是维度
// 指定要提取嵌套数组的结构深度,默认值为 1
使用 Infinity,可展开任意深度的嵌套数组

写这个给面试官看的话,嗯嗯,应该会被打死,写一个比较容易的 👇

let flatDeep = (arr) => {
  return arr.reduce((res, cur) => {
    if (Array.isArray(cur)) {
      return [...res, ...flatDep(cur)];
    } else {
      return [...res, cur];
    }
  }, []);
};

「你想给面试官留下一个深刻印象的话」,可以这么写,👇

function flatDeep(arr, d = 1) {
  return d > 0
    ? arr.reduce(
        (acc, val) =>
          acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val),
        []
      )
    : arr.slice();
}
// var arr1 = [1,2,3,[1,2,3,4, [2,3,4]]];
// flatDeep(arr1, Infinity);

可以传递一个参数,数组扁平化几维,简单明了,看起来逼格满满 🐂🐂🐂


深拷贝


深拷贝解决的就是「共用内存地址所导致的数据错乱问题」


思路:


  • 递归
  • 判断类型
  • 检查环(也叫循环引用)
  • 需要忽略原型
function deepClone(obj, map = new WeakMap()) {
  if (obj instanceof RegExp) return new RegExp(obj);
  if (obj instanceof Date) return new Date(obj);
  if (obj == null || typeof obj != "object") return obj;
  if (map.has(obj)) {
    return map.get(obj);
  }
  let t = new obj.constructor();
  map.set(obj, t);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      t[key] = deepClone(obj[key], map);
    }
  }
  return t;
}
//测试用例
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
  d: new RegExp(/^\s+|\s$/g),
};
let clone_obj = deepClone(obj);
obj.d = /^\s|[0-9]+$/g;
console.log(clone_obj);
console.log(obj);

实现一个对象类型的函数


核心:Object.prototype.toString

let isType = (type) => (obj) =>
  Object.prototype.toString.call(obj) === `[object ${type}]`;
// let isArray = isType('Array')
// let isFunction = isType('Function')
// console.log(isArray([1,2,3]),isFunction(Map))

isType 函数 👆,也属于「偏函数」的范畴,偏函数实际上是返回了一个包含「预处理参数」的新函数。


「一劳永逸」送你21道高频JavaScript手写面试题(下):https://developer.aliyun.com/article/1483403

相关文章
|
25天前
|
前端开发 JavaScript 网络协议
前端最常见的JS面试题大全
【4月更文挑战第3天】前端最常见的JS面试题大全
47 5
|
2天前
|
存储 JavaScript 前端开发
每日一道javascript面试题(九)函数的参数可以和函数体中的变量重名吗
每日一道javascript面试题(九)函数的参数可以和函数体中的变量重名吗
|
2天前
|
JavaScript 前端开发
每日一道javascript面试题(七)你真的知道箭头函数吗
每日一道javascript面试题(七)你真的知道箭头函数吗
|
14天前
|
前端开发 JavaScript 测试技术
「一劳永逸」送你21道高频JavaScript手写面试题(下)
「一劳永逸」送你21道高频JavaScript手写面试题
25 0
|
2月前
|
设计模式 JavaScript 前端开发
最常见的26个JavaScript面试题和答案
最常见的26个JavaScript面试题和答案
47 1
|
2月前
|
存储 JavaScript 前端开发
【JavaScript】面试手撕浅拷贝
引入 浅拷贝和深拷贝应该是面试时非常常见的问题了,为了能将这两者说清楚,于是打算用两篇文章分别解释下深浅拷贝。 PS: 我第一次听到拷贝这个词,有种莫名的熟悉感,感觉跟某个英文很相似,后来发现确实Copy的音译,感觉这翻译还是蛮有意思的
48 6
|
2月前
|
JavaScript 前端开发
【JavaScript】面试手撕节流
上篇我们讲了防抖,这篇我们就谈谈防抖的好兄弟 -- 节流。这里在老生常谈般的提一下他们两者之间的区别,顺带给读者巩固下。
53 3
|
3月前
|
前端开发 JavaScript UED
【JavaScript】面试手撕防抖
防抖: 首先它是常见的性能优化技术,主要用于处理频繁触发的浏览器事件,如窗口大小变化、滚动事件、输入框内容改变等。在用户连续快速地触发同一事件时,防抖机制会确保相关回调函数在一个时间间隔内只会被执行一次。
37 0
|
3月前
|
前端开发 JavaScript 算法
【JavaScript】面试手撕数组排序
这章主要讲的是数组的排序篇,我们知道面试的时候,数组的排序是经常出现的题目。所以这块还是有必要进行一下讲解的。笔者观察了下前端这块的常用算法排序题,大概可以分为如下
25 2
|
3月前
|
JavaScript 前端开发 索引
【JavaScript】面试手撕数组原型链(易)
续借上文,这篇文章主要讲的是数组原型链相关的考题,有些人可能会纳闷,数组和原型链之间有什么关系呢?我们日常使用的数组forEach,map等都是建立在原型链之上的。举个🌰,如我有一个数组const arr = [1,2,3]我想要调用arr.sum方法对arr数组的值进行求和,该如何做呢?我们知道数组没有sum函数,于是我们需要在数组的原型上定义这个函数,才能方便我们调用,具体代码如下。接下来我们就是采用这种方式去实现一些数组常用的方法。
39 6