「一劳永逸」送你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

相关文章
|
19小时前
|
缓存 前端开发 JavaScript
Javascript模块化开发基础,最新美团点评前端团队面试题
Javascript模块化开发基础,最新美团点评前端团队面试题
|
1天前
|
JavaScript 前端开发 程序员
javascript基础(入门),当上项目经理才知道,推荐程序员面试秘籍
javascript基础(入门),当上项目经理才知道,推荐程序员面试秘籍
|
2天前
|
自然语言处理 JavaScript 前端开发
三个JavaScript面试题
【5月更文挑战第7天】- 闭包是JavaScript函数能记住词法作用域,即使在外部执行。示例:计数器函数`createCounter()`返回访问`count`的匿名函数,每次调用计数递增。 - 事件循环处理异步操作,通过检查任务队列执行回调。示例:`setTimeout`异步任务在3秒后添加到队列,待执行,输出顺序为同步任务1、2,然后异步任务1。 - 箭头函数是ES6简洁的函数定义方式,如`greet = name => `Hello, ${name}!`。它没有自己的`this`,不适用作构造函数。
30 6
|
2天前
|
存储 JavaScript 前端开发
每日一道javascript面试题(九)函数的参数可以和函数体中的变量重名吗
每日一道javascript面试题(九)函数的参数可以和函数体中的变量重名吗
|
2天前
|
存储 JavaScript 前端开发
每日一道javascript面试题(八)你真的知道了解const吗
每日一道javascript面试题(八)你真的知道了解const吗
|
2天前
|
JavaScript 前端开发
每日一道javascript面试题(七)你真的知道箭头函数吗
每日一道javascript面试题(七)你真的知道箭头函数吗
|
2天前
|
JavaScript 前端开发
每日一道javascript面试题(六)有var和无var
每日一道javascript面试题(六)有var和无var
|
2天前
|
JavaScript 前端开发
每日一道javascript面试题(五)
每日一道javascript面试题(五)
每日一道javascript面试题(五)
|
2天前
|
前端开发 JavaScript
每日一道javascript面试题(三)
每日一道javascript面试题(三)
|
2天前
|
JavaScript 前端开发
每日一道javascript面试题(二)
每日一道javascript面试题(二)
每日一道javascript面试题(二)