接上一篇文章, 专科前端生存之路 ,最后的面试题的答案。
以下都是近期(10、11月)本人面试真题。整理出来,有多种实现方式,下面只是我的实现方法。可灵活参考。
手写Bind、Apple、Call 【美团、神策数据】
// Bind
Function.prototype.myBind = function (oThis) {
var aArgs = [].slice.call(arguments, 1);
var fun = this;
return function () {
fun.apply(oThis, aArgs.concat([].slice.call(arguments)));
};
};
// call
Function.prototype.myCall = function(context) {
context=context||window
context.fn = this
const args = [...arguments].slice(1)
const result = context.fn(...args)
delete context.fn
return result
}
// myApply
Function.prototype.myApply = function(context) {
context = context || window
context.fn = this
let result
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
手写debounce、throttle 【美团、头条】
function debounce(fn, ms) {
let timer = null;
return function (...args) {
if (timer) {
clearTimeout(timer);
}
let context = this;
timer = setTimeout(() => {
fn.apply(context, args);
timer = false;
}, ms);
};
}
function throttle(fn, delay) {
let timer = null;
let flag = false;
return function (args) {
if (flag) {
return;
}
let context = this;
flag = true;
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
flag = false;
}, delay);
};
}
基础算法题:冒泡、快速、插入、深度优先(DFS)、广度优先(BFS)、二分查找 【不知名的小公司】
function bubb(arr) {
for (let i = 0; i <= arr.length; i++) {
for (let j = 0; j <= arr.length; j++) {
let item = arr[i];
let item2 = arr[j];
if (item < item2) {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
let array = [4, 2, 5, 3, 1, 5, 9, 7, 5, 8, 6, 4];
function quick(arr) {
if (arr.length <= 1) {
return arr;
}
let centerIndex = Math.floor(arr.length / 2);
let center = arr.splice(centerIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++) {
const item = arr[i];
if (item <= center) {
left.push(item);
} else {
right.push(item);
}
}
return [...quick(left), center, ...quick(right)];
}
function Insertion(arr) {
let len = arr.length;
let preIndex, current;
for (let i = 0; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while (preIndex >= 0 && current < arr[preIndex]) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
// 数据结构: 队列,先入先出
function bfs(node) {
var nodes = [];
if (node != null) {
var queue = [];
queue.push(node);
while (queue.length != 0) {
var item = queue.shift();
// 操作当前节点 就是放到我们的目标数组中,这里你也可以做一些操作
nodes.push(item);
var children = item.children;
// 将当前操作的节点的子节点 塞到队列的最后
for (var i = 0; i < children.length; i++){
queue.push(children[i]);
}
}
}
return nodes;
}
// 栈 先入后出
function DFS(node, nodeList) {
if (node) {
nodeList.push(node);
var children = node.children;
for (var i = 0; i < children.length; i++)
//每次递归的时候将 需要遍历的节点 和 节点所存储的数组传下去
DFS(children[i], nodeList);
}
return nodeList;
}
// 二分查找
function search(nums, target) {
let low = 0,
high = nums.length - 1,
mid,
elem
while(low <= high) {
mid = Math.floor((low+high)/2)
elem = nums[mid]
if(elem < target) {
low = mid + 1
} else if(elem > target) {
high = mid - 1
} else {
return mid
}
}
return -1
}
console.log(search([-1,0,3,5,9,12],9));
合并区间乱序 【快手、美团】
let arr = [[1,3],[2,6],[8,10],[15,18]]
function merge(arr) {
if (!arr || !arr.length) return [];
arr.sort((a, b) => a[0] - b[0]); // 按照区间第一位进行排序
let result = [arr[0]] // 排序之后第一个是最小的
for (let i = 1; i < arr.length; i++) { // 从第二个开始比较
let resultLast = result.length - 1
if (result[resultLast][1] > arr[i][0]) { // 判断结尾是不是大于开始
result[resultLast][1] = Math.max(result[resultLast][1], arr[i][1]) // 区间重复就进行合并了
} else {
result.push(arr[i]) // 区间没有重复
}
}
return result
}
console.log(merge(arr));
连续子数组最大和【阿里】
function maxSum(arr = []) {
let tempSum = 0;
let maxSum = 0;
for (let i = 0; i < arr.length; i++) {
tempSum += arr[i];
if (tempSum > maxSum) {
maxSum = tempSum;
} else if (tempSum < 0) {
tempSum = 0;
}
}
return maxSum;
}
console.log(maxSum([-2,1-3,4,-1,2,1,-5,4]));
千分位【阿里】
function format(str) {
// 校验、小数点、负数 先切掉
return str.split('').reduceRight((pre, next, index,origin) => {
return (index % 3 ? next : next + ',') + pre;
});
}
无重复字符串的最长子串
// 开始之前,我们将导致窗口开始收缩的那个值 称为 「亮」值
var lengthOfLongestSubstring = function(s) {
// 窗口数组,可以使用对象代替,利用对象key值的唯一性更好
const windowList = [];
// 窗口的左右边界
let left = 0;
let right = 0;
// 最终的子串最大值
let max = 0;
while (right < s.length) {
if (!windowList.includes(s[right])) {
// 扩大窗口有边界
windowList.push(s[right]);
right++;
} else {
while (left < right) {
// 开始缩小左边界
windowList.shift();
left++;
// 判断缩小的窗口内是否还包括 「亮」值
if (!windowList.includes(s[right])) {
break;
}
}
}
// 一直替换最大值
max = Math.max(max, right - left);
}
return max
};
数组转树形对象【头条】
/**
* 数组转树 非递归求解
* 利用数组和对象相互引用 时间复杂度O(n)
* @param {Object} list
*/
function totree(list,parId) {
let obj = {};
let result = [];
//将数组中数据转为键值对结构 (这里的数组和obj会相互引用)
list.map(el => {
obj[el.id] = el;
})
for(let i=0, len = list.length; i < len; i++) {
let id = list[i].parentId;
if(id == parId) {
result.push(list[i]);
continue;
}
if(obj[id].children) {
obj[id].children.push(list[i]);
} else {
obj[id].children = [list[i]];
}
}
return result;
}
数组拍平和对象拍平【快手】
function flattenArr(arr, depth = 1) {
let res = [];
for (let idx = 0; idx < arr.length; idx++) {
let i = arr[idx];
if (Array.isArray(i) && (depth > 0 || depth === Infinity)) {
res.push(...flattenArr(i, --depth));
} else {
res.push(i);
}
}
return res;
}
function flatObj(data) {
var result = {};
function recurse(cur, prop) {
// 如果输入进来的不是对象,就将其放在数组中,返回
if (typeof cur !== 'object') {
result[prop] = cur;
// 如果输入进来的是数组,长度不为0就递归数组,得出结果
} else if (Array.isArray(cur)) {
for (var i = 0, len = cur.length; i < len; i++) {
recurse(cur[i], prop + '[' + i + ']');
}
if (len == 0) {
result[prop] = [];
}
} else {
// 对象
var isEmpty = true;
for (var p in cur) {
isEmpty = false;
recurse(cur[p], prop ? prop + '.' + p : p);
}
if (isEmpty && prop) {
result[prop] = {};
}
}
}
recurse(data, '');
return result;
}
数组乱序
// 数组乱序
function shuffle(arr) {
for (let i = arr.length; i; i--) {
let j = Math.floor(Math.random() * i);
[arr[i - 1], arr[j]] = [arr[j], arr[i - 1]];
}
return arr;
}
手写一个简易版的redux
// redux
function createStore(reducer) {
// 默认初始状态
let state;
let listeners = []; // 存储所有的监听函数
// 得到总的状态树
function getState() {
return state;
}
// 派发动作
function dispatch(action) {
// 得到新状态
state = reducer(state, action);
// 订阅
listeners.forEach(fn => fn());
}
// 由于默认 state 树是没有状态的, 是一个控制,所以先派发一次action,给初始状态赋值,
// 由于用户自己定义的reducer 默认返回初始状态,由于派发的这个type类型找不到,所以默认返回了初始值
dispatch({ type: '@@REDUX_INIT' });
// 添加发布订阅模式
function subscribe(listener) {
listeners.push(listener);
// 返回一个取消订阅方法(就是个高阶函数)
return function () {
listeners = listeners.filter(fn => fn !== listener);
};
}
return {
getState,
dispatch,
subscribe,
};
}
两数之和
var twoSum = function(nums, target) {
const map = {}
const len = nums.length
for(let i=0;i<len; i++){
const targetNum = target - nums[i];
if(targetNum in map) return [map[targetNum], i]
map[nums[i]] = i
}
};
合并有序数组【便利蜂、快手、美团】
var arr1 = [1, 2, 3, 6, 8];
var arr2 = [2, 6, 7];
var merge = function (nums1, m, nums2, n) {
let count = m + n;
while (m > 0 && n > 0) {
nums1[--count] = nums1[m - 1] < nums2[n - 1] ? nums2[--n] : nums1[--m];
}
// 如果arr2开头最小
if (n > 0) {
nums1.splice(0, n, ...nums2.slice(0, n));
}
};
merge(arr1, 5, arr2, 3);
console.log(arr1);
字符串全排列 【头条】
function permutate(str) {
//保存每一轮递归的排列结果
var result = [];
//初始条件:长度为1
if (str.length == 1) {
return [str]
} else {
//求剩余子串的全排列,对每个排列进行遍历
var preResult = permutate(str.slice(1));
console.log(preResult);
for (var j = 0; j < preResult.length; j++) {
for (var k = 0; k < preResult[j].length + 1; k++) {
//将首字母插入k位置
var temp = preResult[j].slice(0, k) + str[0] + preResult[j].slice(k);
if(!result.includes(temp)){
result.push(temp);
}
}
}
return result;
}
}
console.log(permutate('aabc'));
实现一个并发控制的request【快手】
// 如果你会rxjs的话,这个一行代码就搞定
from(fetchList).pipe(mergeMap(res)=> res(),10).subscribe(cb)
// 普通版本
class Request {
constructor(maxLimit) {
this.maxLimit = maxLimit;
// 等待的任务
this.waitingQueue = [];
}
initRequest = reqArr => {
this.waitingQueue.push(...reqArr)
};
// 执行下一个请求
next = () => {
if (this.waitingQueue.length > 0) {
const next = this.waitingQueue.splice(0, 1);
this.request({
...next[0],
});
}
};
// init->开始请求
run = () => {
// 不满足limit
const list = this.waitingQueue.splice(0, this.maxLimit);
for (let i = 0; i < list.length; i++) {
this.request({
...list[i],
});
}
};
// 主请求方法
request = ({ method, api, params, onSuccess = () => {}, onError = () => {} }) => {
// ...
fetch(api, options)
.then(data => {
onSuccess(data);
})
.catch(err => {
onError(err);
})
.finally(() => {
this.next();
});
};
pushRequest = (method, api, params, onSuccess, onError) => {
this.waitingQueue.push({
method,
api,
params,
onSuccess,
onError,
});
};
checkStatus = response => {
if (response.ok) {
return response;
} else {
let error = new Error(response.statusText);
error.status = response.status;
throw error;
}
};
parseJSON = response => {
return response.json();
};
getSearchFromObject = param => {
if (!param) return '';
var str = [];
for (var p in param)
if (param.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + '=' + encodeURIComponent(param[p]));
}
return '?' + str.join('&');
};
}
实现一个多维数组代理器 【快手】
// proxy
let arr = [{ name: 1, age: 2 }];
function deepProxy(obj, cb) {
if (typeof obj === 'object') {
for (let key in obj) {
if (typeof obj[key] === 'object') {
obj[key] = deepProxy(obj[key], cb);
}
}
}
return new Proxy(obj, {
set: function (target, key, value, receiver) {
console.log(`arr name orivalue= ${target[key]}, curValue=${value}`);
// let cbType = target[key] == undefined ? 'create' : 'modify';
// //排除数组修改length回调
// if (!(Array.isArray(target) && key === 'length')) {
// cb(cbType, { target, key, value });
// }
return Reflect.set(target, key, value, receiver);
},
});
}
let p = deepProxy(arr, (type, data) => {
console.log(type, data);
});
p[0].name = 3;
实现一个lazyman 【阿里】
function _LazyMan(name) {
this.tasks = [];
let self = this;
let fn = (name => {
return () => {
console.log(`hi,this is ${name}`);
self.next();
};
})(name);
this.tasks.push(fn);
setTimeout(() => {
self.next();
}, 0);
}
_LazyMan.prototype.next = function () {
let fn = this.tasks.shift();
fn && fn();
};
_LazyMan.prototype.eat =function (name) {
let self = this;
let fn = ((name)=>{
return ()=>{
console.log(`eat:${name}`);
self.next();
}
})(name);
this.tasks.push(fn);
return this;
}
_LazyMan.prototype.sleep = function (time) {
let self = this;
let fn = ((time)=>{
return ()=>{
setTimeout(() => {
console.log(`sleep:${time}s`);
self.next();
}, time*1000);
}
})(time)
this.tasks.push(fn);
return this;
}
function LazyMan(name) {
return new _LazyMan(name);
}
LazyMan('愚墨').eat('午饭').sleep(3).eat('晚饭')
深克隆【不知名的小公司】
/**
* js深拷贝(包括 循环引用 的情况)
*
* @param {*} originObj
* @param {*} [map=new WeakMap()] 使用hash表记录所有的对象的引用关系,初始化为空
* @returns
*/
function deepClone( originObj, map = new WeakMap() ) {
if(!originObj || typeof originObj !== 'object') return originObj; //空或者非对象则返回本身
//如果这个对象已经被记录则直接返回
if( map.get(originObj) ) {
return map.get(originObj);
}
//这个对象还没有被记录,将其引用记录在map中,进行拷贝
let result = Array.isArray(originObj) ? [] : {}; //拷贝结果
map.set(originObj, result); //记录引用关系
let keys = Object.keys(originObj); //originObj的全部key集合
//拷贝
for(let i =0,len=keys.length; i<len; i++) {
let key = keys[i];
let temp = originObj[key];
result[key] = deepClone(temp, map);
}
return result;
}
以上是面试的几个公司的笔试题目,下一篇文章分享各个公司的面试题目。