每次一到面试,就是在掘金上看各种面经,背八股。今年断断续续也一直在面试,很多时候都是栽在手撕环节。所以现在把我之前在面试过程中遇到的手撕题目整理分享给大家,希望可以帮到和我一样栽在手撕环节的童鞋们。不吸勿喷~
js手写
1、防抖
// 防抖:在n秒后执行该事件,若在n秒内被重复触发,则从新计算
const debounce = (fn,delay)=>{
let timer = null;
return function (...argument) {
if(timer){
clearTimeout(timer)
}
timer = setTimeout(()=>{
fn.apply(this,argument)
},delay)
}
}
2、节流
// 节流:n秒内只运行一次,若在n秒内重复触发,只执行一次。
function throttle(fn,delay) {
let last = 0 // 上次触发时间
return function(...args){
const now = Date.now();
if(now - last > delay){
last = now;
fn.apply(this,args)
}
}
}
3、手写promsie
class MyPromise{
constructor(exector){
this.value = null; // 成功的值
this.status = "pending"; // 初始状态
this.reason=null; // 失败原因的值
this.fulfilledCallBacks = []; // 成功的回调函数数组
this.rejectedCallBacks = []; // 失败的回调函数数组
let resolve=(value)=>{
if(this.status==="pending"){
this.value = value;
this.status="fulfilled";
this.fulfilledCallBacks.forEach(fn=>fn())
}
}
let reject=(reason)=>{
if(this.status==="pending"){
this.reason = reason;
this.status="rejected";
this.rejectedCallBacks.forEach(fn=>fn())
}
}
try {
exector(resolve,reject)
} catch (error) {
reject(error)
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.status === 'fulfilled') {
setTimeout(() => {
const x = onFulfilled(this.value);
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
}
if (this.status === 'rejected') {
setTimeout(() => {
const x = onRejected(this.reason)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
}
if (this.status === 'pending') {
this.fulfilledCallBacks.push(() => { // 将成功的回调函数放入成功数组
setTimeout(() => {
const x = onFulfilled(this.value)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
})
this.rejectedCallBacks.push(() => { // 将失败的回调函数放入失败数组
setTimeout(() => {
const x = onRejected(this.reason)
x instanceof MyPromise ? x.then(resolve, reject) : resolve(x)
})
})
}
})
}
}
// 测试
function p1() {
return new MyPromise((resolve, reject) => {
setTimeout(resolve, 1000, 1)
})
}
function p2() {
return new MyPromise((resolve, reject) => {
setTimeout(resolve, 1000, 2)
})
}
p1().then((res) => {
console.log(1111)
console.log(res) // 1
return p2()
}).then((ret) => {
console.log(ret) // 2
})
4、promise.all
// 所有的promise 都成功,则返回成功后的数组结果
// 所有的promise 有一个失败,则返回失败的结果
MyPromise.all = function (proimises){
return new Promise((resolve,reject)=>{
let result = [];
let index = 0;
for(let i=0;i<proimises.length;i++){
Promise.resolve(proimises[i]).then(res=>{
result[i] = res;
index++;
if(index===proimises.length){
resolve(result)
}
}).catch(reject)
}
})
}
5、promise.race
// race 哪一个最快得到结果,就返回哪一个结果,无论成功或者失败
function race(promiseArr){
return new Promise((resolve,reject)=>{
for(let i=0;i<promiseArr.length;i++){
Promise.resolve(promiseArr[i]).then((ret)=>resolve(ret)).catch((err)=>reject(err))
}
})
}
6、promise.allSettled
// 将每一个promise的结果,集合成数组返回
const allSettled = (promises)=>{
return new Promise((resolve,reject)=>{
const res = [];
const count = 0;
const addData = (status,value,index)=>{
res[index]={
status,
value
}
count++;
if(count===promises.length) return resolve(res);
}
promises.forEach((element,index) => {
if(element instanceof Promise){
element.then((res)=>addData('fulfilled',res,index),err=>{
addData("rejected",err,index)
})
}else{
addData("fulfilled",element,index)
}
});
})
}
7、实现一个带并发的异步调度器Schedule,保证同时运行的任务最多只能有两个
class Scheduler{
constructor(){
this.waitTasks = []; // 代执行的任务列队
this.excutingTasks = []; // 正在执行的任务列队
this.maxExcutingNum = 2; // 允许同时运行的任务数量
}
add(promiseMarker){
if(this.excutingTasks.length < this.maxExcutingNum){
this.run(promiseMarker)
}else{
this.waitTasks.push(promiseMarker)
}
}
run(promiseMaker){
const len = this.excutingTasks.push(promiseMaker);
const index = len - 1;
promiseMaker().then(()=>{
this.excutingTasks.splice(index,1);
if(this.waitTasks.length>0){
this.run(this.waitTasks.shift())
}
})
}
}
const scheduler = new Scheduler();
const timeout = (time)=> new Promise((resolve)=>setTimeout(resolve,time))
const addTask = (time,order)=>{
scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
// output 2,1,3,4
8、 通过给定一个url地址字符串获取host,honame,主域等信息
// 这里主要借助的是location的一些方法,通过给定的地址创建一个a链接,并且把地址复制给a链接的地址,这样a链接就可以通过location方法获取相应的地址数据,然后返回成对象的形式
const getLocation = (url)=>{
if(url){
let aDom = document.createElement("a");
aDom.href = url;
let j = {
hostname: aDom.hostname,
host: aDom.host,
origin: aDom.origin,
protocol: aDom.protocol,
pathname: aDom.pathname,
hash: aDom.hash,
domain:aDom.domain,
port:aDom.port,
search:aDom.search,
}
return j
}
}
console.log(getLocation('https://www.baidu.com:8080/windows/location/page.html?vid=1&id=xjq#tip'))
9、手写new运算符
// new 运算符其实就是在内部生成了一个对象,在将你的属性添加到这个对象上,然后返回这个对象。
function myNew(fn,...args){
// 基于原型创建了一个对象
let object = Object.create(null);
// 添加属性到新对象上,并获取obj函数的结果
let res = fn.apply(object,...args)
// 判断返回值,如果执行结果有返回值并且是一个对象,返回执行的结果,否则,返回创建的新对象
return res && typeof res==="object" ? res : object;
}
10、手写instanceof
// instanceof原理:在查找的过程中会遍历左边变量的原型链,直到找到右边变量的prototype,查找失败,返回false
let myInstanceof = (target,origin)=>{
while(target){
if(target.__proto__ === origin.prototype){
return true
}
target = target.__proto__
}
return false
}
11、手写数组的flat
function flat(arr){
return arr.reduce((pre,cur)=>{
return pre.concat(Array.isArray(cur) ? flat(cur) : cur);
},[])
}
console.log(flat([1,2,[3,4,[5]]]))
function flat(arr,depth=1){
let result = [];
arr.forEach(element => {
if(Array.isArray(element) && depth>0){
result = result.concat(flat(element,depth-1))
}else{
result.push(element)
}
});
return result;
}
console.log(flat([1,2,[3,4,[5,[7]]]],1))
12、实现数组chunk
// slice 提取字符串中的某一部分,并返回一个新的字符串,且不会改变原字符串
// slice (beginIndex,endIndex) 从beginIndex 开始提取,从endIndex处结束,若省略endIndex,slize会一直提取到字符串的末尾
function chunk(arr,size){
let arr2=[];
for(let i=0;i<arr.length;i+=size){
console.log(i)
arr2.push(arr.slice(i,i+size))
}
return arr2;
}
console.log(chunk2([0, 1, 2, 3, 4, 5], 1))
13、对象拍平
function isObject(val){
return typeof val==="object" && val!==null;
}
function flatten(obj){
if(!isObject(obj){
return
}
let res = {};
const dfs = (cur,prefix)=>{
if(isObject(cur)){
if(Array.isArray(cur)){
cur.forEach((item,index)=>{
dfs(item,`${prefix}[${index}]`)
})
}else{
for(let k in cur){
dfs(cur[k],`${prefix}${prefix ? "." : ""}${k}`)
}
}
} else{
res[prefix] = cur
}
}
dfs(obj,"");
return res
}
console.log(flatten({
a: {
b: 1,
c: 2,
d: {e: 5}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}))
14、统计对象有多少层?
function getLevel2(obj){
if(obj===null){
return 0;
}
let result=0;
function fn(params,level=0){
if(typeof params === "object" && params!==null){
Object.values(params).forEach(item=>{
if(typeof item==="object" && item!==null){
fn(item,level+1)
}else{
result = level + 1 > result ? level+1:result
}
})
}else{
result = result > level ? result : level
}
}
fn(obj)
return result
}
console.log(getLeval({a:1,b:{ c:1,d:{e:1,g:{h:7},f:[ 1,1 ]}}}))
15、实现深拷贝
深拷贝:开辟一个新栈,两个完全相同的对象,对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性
常见的拷贝方式:
1、lodash的_.cloneDeep()
2、JSON.stringify()
注:JSON.stringify()实现深拷贝有什么问题?
1、当obj中有时间对象时,使用JSON.stringify在JSON.parse后,时间对象会变成字符串。
2、当obj中有函数,undefined,使用JSON.stringify会将函数或者undefined丢失。
3、NaN和infinity格式的数值以及null都会被处理成null。
4、obj中正则会被处理成空对象{}
5、obj中的原型属性会被忽略
function deepClone(obj,hash=new WeakMap()){
if(obj===null) return obj;
if(obj instanceof Date) return new Date(obj);
if(obj instanceof RegExp) return new RegExp(obj)
//可能是对象或者普通的值,如果是函数的话就不需要深拷贝
if(typeof obj!=="object") return obj;
//如果是对象的话,进行深拷贝
if(hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
// 找到的是所属类原型上的constructor,而原型上的constructor指向的是当前类的本身
hash.set(obj,cloneObj)
for(let key in obj){
// 实现一个递归
if(obj.hasOwnProperty(key)){
cloneObj[key]=deepClone(obj[key],hash)
}
}
return cloneObj
}
16、curried(1)(2)(3)(4)(5)() 求和
function curry(fn){
let parmas = []
return function sum(...args){
if(args.length){//判断是否有参数
parmas = [...parmas,...args]
return sum
}
return fn(parmas)
}
}
function add(arr){
return arr.reduce((acc,item)=>{
return acc+item
})
}
let curried = curry(add)
console.log(curried(1)(2)(3)(4)(5)())
17、大数相加
function bigNumber(str1,str2){
const arr1 = str1.split("").reverse();
const arr2 = str2.split("").reverse();
const length = Math.max(arr1.length,arr2.length);
let flag = 0 ;
let res = [];
for( i=0;i<length;i++){
const nums1 = Number(arr1[i] || 0);
const nums2 = Number(arr2[i] || 0);
let sum = nums1 + nums2 + flag;
if(sum>=10){
sum = sum % 10;
flag = 1;
}else{
flag = 0;
}
res.push(sum);
if(i === length-1 && flag){
res.push(flag)
}
}
return res.reverse().join("")
}
console.log(bigNumber("1234","557"))
18、循环打印红绿灯
function light(func,delay){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve(func())
},delay)
})
}
async function index2(){
while(1){
await light(yellow,1000);
await light(red,2000);
await light(green,3000)
}
}
function yellow(){
console.log("我是黄灯")
}
function red(){
console.log("我是红灯")
}
function green(){
console.log("我是绿灯")
}
index2()