大家好,我是若川。最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12
若川视野原意是若川的前端视野。但太长了就留下了四个字,不知道的以为关注的不是技术公众号。今天分享一篇慕课网精英讲师河畔一角的好文章~
废话不多说,上货!
手写Promise
这道题说实话已经老掉牙了,但是还是有不少公司会问,而且还有相当一部分前端写不出来
function Promise(callback){const pending = 'pending';const fulfilled = 'fulfilled';const rejected = 'rejected';// 当前状态this.state = pending;// 当前值this.value = null;// 失败原因this.reason = null;// 成功和失败数组对象this.fulfilledCallback = [];this.rejectedCallback = [];// 成功处理this.resolve = (data)=>{setTimeout(()=>{if(this.state == pending){this.state = fulfilled;this.value = data;this.fulfilledCallback.map(fn=>fn(this.value));}})}// 失败处理this.reject = (reason)=>{setTimeout(()=>{if(this.state == pending){this.state = rejected;this.reason = reason;this.rejectedCallback.map(fn=>fn(this.reason));}})}// 捕获成功和失败,扔到成功和失败数组this.then = function(succesFn,errorFn){this.fulfilledCallback.push(succesFn);this.rejectedCallback.push(errorFn);}// 捕获异常,直接扔到异常数组中this.catch = (errorFn)=>{this.rejectedCallback.push(errorFn);}// 默认需要执行一次resolve和rejectcallback(this.resolve,this.reject);
}// 验证结果
new Promise((resolve,reject)=>{setTimeout(()=>{resolve(10);},1000)
}).then((data)=>{console.log(data);
},(error)=>{console.log(error);
})
注意事项:
Promise要暴露then/catch方法
Promise构造函数接收一个立即执行的函数callback
then/catch只负责把回调放入数组即可
resolve/reject负责执行
resolve/reject 需要添加宏任务(setTimeout)
compose组合函数实现
后一个函数作为前一个函数的参数
最后一个函数可以接受多个参数,前面的函数只能接受单个参数;后一个的返回值传给前一个
// Demo:
const add = num => num + 10
const multiply = num => num * 2
const foo = compose(multiply, add)
foo(5) => 30
// 聚合函数
export default function compose(...funcs) {// 如果是空,直接返回空函数并接受一个参数if (funcs.length === 0) {return arg => arg}// 如果有一个,直接执行并返回结果if (funcs.length === 1) {return funcs[0]}// 如果对reduce不了解,可以先去看下技术文章return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
注意事项:
compose是一个聚合函数
compose执行后返回一个函数(所以这就是为什么当func.length==0的时候,要return一个箭头函数)
reduce始终返回一个箭头函数,后一个函数先执行并把结果作为前一个函数的参数,依次进行
数组柯里化Curry(求和)
阿里面试题
实现如下效果:
sum(1,3).sumOf() 4
sum(1,3)(2,4).sumOf() 10
function sum(){var arr = [].slice.apply(arguments);var fn = function(){arr = arr.concat([].slice.apply(arguments))return fn;}fn.sumOf = function(){return arr.reduce((total,num)=>total+num,0);}return fn;
}
实现一个LazyPig
阿里面试题
实现一个LazyPig,可以按照以下方式调用:
LazyPig("Peggy")
// 输出:
> Hello,I'm Peggy!
LazyPig("Peggy").sleep(10).eat("dinner")
// 输出
> Hello,I'm Peggy!
//等待10秒..
Wake up after 10
Eat dinner~
function LazyPig(name){console.log(`Hello,I'm ${name}`)var fn = {}fn.sleep = function(time){console.log(`Wake up ${time}`)let start = Date.now()while(Date.now()-start<=time){}console.log(`Wake up down`)return fn;}fn.eat = function(food){console.log(`Eat ${food}`)return fn;}return fn;
}
数组扁平化
let list = [1, 5, [9, 8], [2, [1, 9]], 7];
// 第一种方法:
console.log(list.toString().split(','));// 第二种方法:
function flatten(list) {return list.reduce((prev, item) => {return prev.concat(Array.isArray(item) ? flatten(item) : item);}, [])
}
console.log(flatten(list));
对象扁平化
/**
* 对象扁平化
* 说明:请实现 flatten(input) 函数,input 为一个 javascript 对象(Object 或者 Array),返回值为扁平化后的结果。
* 示例:
* var input = {
* a: 1,
* b: [ 1, 2, { c: true }, [ 3 ] ],
* d: { e: 2, f: 3 },
* g: null,
* }
* var output = flatten(input);
* output如下
* {
* "a": 1,
* "b[0]": 1,
* "b[1]": 2,
* "b[2].c": true,
* "b[3][0]": 3,
* "d.e": 2,
* "d.f": 3,
* // "g": null, 值为null或者undefined,丢弃
* }
*/
解答:
```js
let result = {};
var flatten = (data, key) => {if (data instanceof Array) {data.forEach((param, index) => {if (param instanceof Object) {flatten(param, `${key}[${index}]`);} else {result[`${key}[${index}]`] = param;}});} else if (data instanceof Object) {for (var itemKey in data) {const itemValue = data[itemKey];if (itemValue instanceof Object) {flatten(itemValue, itemKey);} else if (itemValue) {if (key) {result[`${key}.${itemKey}`] = flatten(itemValue, itemKey);} else {result[itemKey] = itemValue;}}}} else {return data;}
};
flatten(input);
console.log(result)
数组转换为Tree
// 数组转换成Tree
var list = [{id: 1, name: 'jack', pid: 0},{id: 2, name: 'jack', pid: 1},{id: 3, name: 'jack', pid: 1},{id: 4, name: 'jack', pid: 2},
] const getTree = (root, result, pid) => {for (let i = 0; i < root.length; i++) {if (root[i].pid == pid) {let item = { ...root[i], children: [] }result.push(item)getTree(root, item.children, item.id)}}
}let array = [];
getTree(list, array, 0)
console.log(JSON.stringify(array))
对象深拷贝
// 对象深度克隆
let obj = {name: 'jack',age: 10,fn: () => {return this.name;},list: ['fe', 'node', 'small'],all: {child: true}
}
// 方法一:(面试官不想要)
JSON.parse(JSON.stringify(obj))
// 方法二:
function deepClone(obj){let result;if(typeof obj === 'object'){result = Array.isArray(obj) ? [] : {}for(let i in obj){result[i] = typeof obj[i] === 'object' ? deepClone(obj[i]):obj[i];}}else{result = obj;}return result;
}
贪心算法(找零)
商店老板有1、2、5、10面额的纸币,小伙买东西给了100花了80,计算如何找零是最佳(阿里面试题)
function MinCoinChange(coins) {return function(amount) {let total = 0, change = []for(let i= coins.length; i>=0; i--) {let coin = coins[i]while(total + coin <= amount) {change.push(coin)total += coin}}return change}
}MinCoinChange([1,2,5,10])(20)返回:10,10
数组去重(两次以上去重)
常规去重大家都知道Set
// 已知数组
var arr = [1,1,1,1,1,1,1,3,3,3,3,3,5,5];
// 方法一
function delRepeat(){arr = arr.sort();//先排序for(let i=0;i<arr.length;i++){if(arr[i] == arr[i+2]){arr.splice(i,1);i--;}}return arr;
}
// 方法二
function delRepeat(){var newArr = [];var obj = {};arr.map(item=>{if(obj[item]){obj[item] +=1 ;}else{obj[item] = 1;}obj[item]<=2?newArr.push(item):''})return newArr;
}
大数相加算法
大数相加有很多考题形式,有整数、小数、平方根等(腾讯考题)
已知:let a = "12345.12123",b="987654.92";
function sum(a,b){let arr1 = a.split(''),arr2 = b.split('');let count = 0;let arr = [];let a1 = a.split('.')[1],b1 = b.split('.')[1];let len = a1.length - b1.length;if(len>0)arr2.push(’0’.repeat(len))if(len<0)arr1.push(’0’.repeat(Math.abs(len)))while(arr1.length || arr2.length){let m = arr1.pop() || 0,n = arr2.pop() || 0;if(m != '.'){let num = Number(m) + Number(n) + count;if(num > 9){count = 1;num%=10;}else{count = 0;} arr.unshift(num);}else{arr.unshift('.');}}if(count>0)arr.unshift(count);let res = arr.join('');console.log(res);
}
如果是正整数,BigInt会更方便处理
二叉树求和
var treenode = {value: 1,left: {value: 2,left: {value: 4,},right: {value: 5,left: {value: 7,},right: {value: 8,},},},right: {value: 3,right: {value: 6,},},
}function sum(root) {let list = []if (root) list.push(root.value);if (root.left) {list = list.concat(sum(root.left));}if (root.right) {list = list.concat(sum(root.right));}return list;
}
console.log(sum(treenode));
爬楼梯
/*
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:
f(1) : 1
f(2) : 11 , 2
f(3) : 12, 111, 21
f(4) : 121, 1111, 211, 112, 22
f(n) = f(n-1) + f(n-2)
*/
function fn(n) {if (n == 1) return 1;if (n == 2) return 2;return fn(n - 1) + fn(n - 2);
}
console.log(fn(4))
简易模板引擎
const template = '嗨,{{ info.name.value }}您好,今天是星期 {{ day.value }}';const data = {info: {name: {value: '张三'}},day: {value: '三'}
};function render(template, data) {return template.replace(/{{\s+?([\w.]+)\s+?}}/g, function ($0, $1) {return eval(`data.${$1}`)})
}const result = render(template, data);
// 嗨,张三您好,今天是星期三
console.log(result)
前端模拟并发请求
已知当前有100个请求,每次只能同时调用5个,设计一个并发函数。
function send(){// 初始化数组let list = Array.from({length:100}).map((k,i)=>i)// 最大并发次数const limit = 5;//定义异步函数const asyncGet =async (item)=>{return item;}// 初始化100个异步请求函数,当闭包被执行的时候会执行一个请求,当请求执行完后,会获取下一个并执行const arr = []const handlers = ()=>{list = list.map(item=>{return ()=>{return asyncGet(item).then((res)=>{console.log('res:'+res)let next = list.shift();if(next){next()}else{console.log('全部执行完成')}})}})}handlers();// 一次性取出最大并发数并执行for(let i=0;i<limit;i++){let fn = list.shift();arr.push(fn())}Promise.all(arr).then((res)=>{})
}send();
防抖和节流(最经典的闭包案例)
/*
防抖:
定义:规定时间内,只触发一次,如果规定时间内再次调用,会清空继续创建新的任务。
场景:widow.onresize或者onscroll,或者搜索框
*/
function debounce(fn, wait) {var timeout = null;return function() {if(timeout !== null){clearTimeout(timeout);timeout = setTimeout(fn, wait);}else{timeout = setTimeout(fn, wait);}}
}
window.addEventListener('scroll', debounce(()=>{
// TO-DO
}, 500));
/*
节流:固定时间内,只触发一次。
场景:搜索框
*/
function throttle(fn,delay){let valid = truereturn function() {if(!valid){//休息时间 暂不接客return false }// 工作时间,执行函数并且在间隔期内把状态位设为无效valid = falsesetTimeout(() => {fn()valid = true;}, delay)}
}
以上就是本次给大家整理分享的前端算法面试题,绝不是从网上随意找的,很多都是我经历过的以及朋友面试的,希望对大家有帮助。
最近组建了一个江西人的前端交流群,如果你是江西人可以加我微信 ruochuan12 私信 江西 拉你进群。
推荐阅读
1个月,200+人,一起读了4周源码
我历时3年才写了10余篇源码文章,但收获了100w+阅读
老姚浅谈:怎么学JavaScript?
我在阿里招前端,该怎么帮你(可进面试群)
················· 若川简介 ·················
你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列
从2014年起,每年都会写一篇年度总结,已经写了7篇,点击查看年度总结。
同时,最近组织了源码共读活动
识别上方二维码加我微信、拉你进源码共读群
今日话题
略。欢迎分享、收藏、点赞、在看我的公众号文章~