在前端开发参加面试的时候,无论是校招还是社招,往往都会碰到让我们直接在白纸或者白板上手撸代码的题目。由于是手撸代码,这些题目肯定不会过于复杂和冗长,否则面试那么一小会时间根本写不完。本文总结了几个我本人在面试中碰到的小问题,暂且记录下来以供后人参考吧。
1. 实现throttle函数。
throttle函数即节流函数,在underscore和lodash这两个库中都有对应的实现。其实现的效果就是在给定的时间间隔内,函数最多只能执行1次。例如有函数A,设定其时间间隔为10s,如果在第0秒的时候已经执行了函数A,然后在第5秒的时候想要再次执行函数A的话,不会立即执行,而是等待5秒钟再执行。在第7秒的时候想要再次触发函数A,则第5秒那个函数A直接取消,第7秒这个函数A等待3秒钟后执行。这样保证了每个10秒钟的区间内,函数A只能执行1次。
var throttle = function (func, wait) { var timeout = null; // setTimeout的返回值 var previous = 0; // 上次执行此函数的时间 return function () { var now = new Date(); var remaining = wait - (now - previous); // 距离设定的时间间隔还剩多少时间 var context = this; var args = arguments; // 清除已经设定的定时器,如果timeout=null,则这下面句没有任何效果 clearTimeout(timeout); // 现在距离上次执行已经超过了设定间隔,直接执行该函数 if (remaining <= 0) { previous = now; timeout = null; func.apply(context, args); return; } // 还没到时间间隔就再次想执行,则清除之前设置的定时器,重设定时器等待一段时间后执行函数 timeout = setTimeout(function () { previous = now; timeout = null; func.apply(context, args); }, remaining); }; };
2. 实现debounce函数。
debounce即防抖函数。可参考underscore或lodash库对此函数的实现。如果函数A防抖并且时间间隔为10秒,那么当想要执行函数A时,不会立即执行而是等待10秒之后再执行。如果第5秒又想要执行函数A了,那第10秒要执行的A直接被取消,而是重新开始10秒计时,也就是等到第15秒才会执行函数A。debounce常见的场景就是搜索引擎的实时搜索联想,在你向搜索框输入文字的过程中,为了避免频繁地调用后端接口,必须等待输入完成,间隔一小段时间没有新的输入时,再去调用后台接口获取搜索联想词。
var debounce = function (task, wait) { var timeout = null; return function () { var context = this; var arg = arguments; clearTimeout(timeout); timeout = setTimeout(function () { task.apply(context, arg); }, wait); } };
3. 假设目前已经有函数$ajax,主要功能是向后台发送一个请求,结果以Promise方式返回,如$ajax(option).then(successCb).catch(failCb),其中successCb和failCb分别是成功和失败时候的回调函数。请实现一个函数myAjax,返回值依然是Promise,但要求加入重试功能,该函数内部依然是使用$ajax实现,只不过在$ajax失败一次之后间隔1秒钟再重试1次,再失败再隔1秒后重试,直到重试到第5次,如果全都失败了,那myAjax所返回的Promise则reject,只要有任意一次成功,则停止重试,直接resolve。
var myAjax = (function () { var failCount = 0; return function (option) { return new Promise((resolve, reject) => { $ajax(option).then(() => { failCount = 0; resolve(); }).catch(() => { failCount++; if (failCount >= 5) { failCount = 0; reject(); return; } setTimeout(function () { myAjax(option).then(() => { resolve(); }).catch(() => { reject(); }); }, 1000); }) }) } })();
4. 实现wait函数,即延迟执行某函数,使用方法为wait(1000).then(myFunction),即等到1秒钟之后执行myFunction。
var wait = function (time) { return new Promise((resolve, reject) => { setTimeout(function () { resolve(); }, time); }) };
5. 编写一个函数,将嵌套的数组转化成扁平的数组,例如:输入 [1, 2, 3, [4, 5, [6]], [6, 8] ],输出 [1, 2, 3, 4, 5, 6, 6, 8]。
var flatten = function (arr) { let result = []; for (let val of arr) { if (Array.isArray(val)) { result.push(...flatten(val)); } else { result.push(val); } } return result; }
6. 编写一个函数,输入一个字符串,将字符串中第一个只出现一次的字符返回。不存在的话返回false。
思路:如果某个字符在字符串中只出现了一次,那么该字符在字符串里面的第一次出现和最后一次出现的位置是一样的,利用String.prototype.indexOf和String.prototype.lastIndexOf求出一个字符在字符串里面第一次出现和最后一次出现的位置,判断是否相等来决定是否只出现了一次。如下:
var uniqueChar = function (str) { let len = str.length; for (let i = 0; i < len; i++){ let chr = str[i]; if (i === str.lastIndexOf(chr)){ return chr; } } return false; }
不过上面这个算法有个小瑕疵,就是时间复杂度是O(n^2),并不算太高效。本着用空间换时间的办法,我们可以换一种思路,先遍历一次字符串,期间以每个字符为hash的key,hash的value为该字符出现的次数。这样一次遍历下来就构建完成了一个hash表,然后再遍历一次,查看每个字符在hash表里面的value是不是1,如果是1,则停止遍历直接返回该字符。
var uniqueChar = function (str) { let obj = {}; for (let i = 0; i < str.length; i++) { let chr = str[i]; if (obj.hasOwnProperty(chr)) { obj[chr] ++; } else { obj[chr] = 1; } } for (let i = 0; i < str.length; i++) { let chr = str[i]; if (obj[chr] === 1) { return chr; } } return false; }
7. 随便找一个Array.prototype里面的方法,自己手动实现一下。
连接若干个数组的concat方法: Array.prototype.concat = function () { var result = [...this]; for (var i = 0; i < arguments.length; i++) { if (Array.isArray(arguments[i])) { result.push(...arguments[i]); } else { result.push(arguments[i]); } } return result; }