面试常见手撕题实录

168 阅读4分钟

CSS

垂直居中

flex、grid以及绝对定位三种方法

<!-- flexbox -->
<div class="father flexbox">
  <div class="son"></div>
</div>

<!-- grid -->
<div class="father gridbox">
  <div class="son"></div>
</div>

<!-- 子绝父相 -->
<div class="father relative">
  <div class="son absolute" ></div>
</div>

// css
.father {
  width: 200px;
  height: 200px;
  background-color: pink;
}

.son {
  width: 50px;
  height: 50px;
  background-color: red;
}

.flexbox{
  display: flex;
  justify-content: center;
  align-items: center;
}

.gridbox {
  display: grid;
  place-items: center;
}

.relative{
  position: relative;
}

.absolute{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}

实现一个三角形

将一个div宽度和高度都为0,通过设置 border 属性,实现一个任意朝向的等边三角形

<div class="triangle"></div>
.triangle{
    width: 0;
    height: 0;
    border-left: 50px solid transparent;
    border-right: 50px solid transparent;
    border-bottom: 100px solid red;
}

JS手撕题

防抖

触发事件后延时执行,延时期间重复触发,则重新计时

function debounce(fn, delay = 500){
    let timer = null
    
    return function(...args){
        if(timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn.call(this, ...args)
        },delay)
    }
}

const debouncedfunc = debounce(func, 500)
debouncedfunc(...args)

适用于输入框输入搜索联想等场景,只有在连续触发事件停止一段时间后才会执行,避免频繁触发请求。

节流

设定时间内重复触发事件,只执行一次

function thettle(fn, delay=500){
    let timer = null
    
    return function(...args){
        if(timer) return 
        timer = setTimeout(() => {
            timer = null
            fn.call(this, ...args)
        }, delay)
    }
}

const thettledfunc = thettle(func, 500)
thettledfunc(...args)

适用于滚动事件窗口大小调整等场景,在固定的时间间隔内,只会执行一次操作,限制操作的执行频率。

深拷贝

1.循环引用检查: 使用 WeakMap 缓存已经拷贝过的对象,通过检查缓存来避免循环引用导致的无限递归。

2.特殊值和非对象类型处理: 如果传入的对象是 null 或者不是对象类型,直接返回原始值,不进行递归拷贝。

3.日期对象和正则表达式对象处理: 对于日期对象和正则表达式对象,分别创建新的对象,避免引用相同的对象。

4.数组处理: 对于数组,创建一个新的数组 arrCopy,并缓存当前数组的引用,然后递归拷贝数组的每个元素,将拷贝后的元素放入新的数组中。

5.普通对象处理: 对于普通对象,创建一个新的对象 objCopy,并缓存当前对象的引用,然后递归拷贝对象的每个属性,将拷贝后的属性放入新的对象中。

function deepClone(obj, cache = new WeakMap()){
    // 循环引用检查
    if(cache.has(obj)){
        return cache.get(obj)
    }
    
    // 特殊值和非对象类型处理
    if(obj === null || typeof obj !== 'object'){
        return obj
    }
    
    // 日期对象和正则表达式对象处理
    if(obj instanceof Date){
        return new Date(obj)
    }
    if(obj instanceof RegExp){
        return new RegExp(obj)
    }
    
    // 数组处理
    if(Array.isArray(obj)){
        const newArr = []
        cache.set(obj, newArr) // 缓存数组引用,避免循环引用
        obj.forEach((item, index) => {
            newArr[index] = deepClone(item, cache)
        })
        return newArr
    }
    
    // 普通对象
    const newObj = {}
    cache.set(obj, newObj) // 缓存对象引用,避免循环引用
    Object.keys(obj).forEach(key => {
        if(obj.hasOwnProperty(key)){
            newObj[key] = deepClone(obj[key], cache)
        }
    })
    return newObj
}

Promise.all

Promise.all 的特点是当传入的所有 Promise 都成功完成时,返回的 Promise 才会被resolve,值是一个包含所有传入 Promise resolve 值的数组。如果其中任何一个 Promise 被reject,返回的 Promise 就会被reject,并传递第一个被reject的 Promise 的原因。

function myPromiseAll(promises){
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promises)){
            return reject(new TypeError('promises must be an array'))
        }
        const results = []
        if(promises.length === 0){
            return resolve(results)
        }
        let donePromise = 0
        for(let i = 0; i < promises.length; i++){
            promises[i].then(result => {
                results[i] = result
                donePromise++
                
                if(donePromise === promises.length){
                    return resolve(results)
                }
            }).catch(error => {
                return rejcet(error)
            })
        }
    })
    
}

Promise.race

在传入的多个 Promise 中有一个率先改变状态(resolve或reject)时就改变状态

function myPromiseRace(promises){
    return new Promise((resolve, reject) => {
        if(!Array.isArray(promises)){
            return reject(new TypeError('promises muse be an array!'))
        }
        for(let i = 0; i < promises.length; i++){
            Promise.resolve(promises[i])
                .then(resolve)
                .catch(reject)
        }
    })
}

instanceof 实现

实现:A instanceof B,结果返回一个布尔值

原理:通过判断 A 对象的原型链中能不能找到 B 对象的 prototype

function myInstanceof(A, B) {
  A = A.__proto__;
  B = B.prototype;
  while (true) {
    if (A === B) return true;
    else if (A === null) return false;
    A = A.__proto__;
  }
}
console.log(myInstanceof([], Array)); // true
console.log(myInstanceof([], Function)); // false

注:

  • 基本数据类型不能通过 instanceof 来判断
  • 所有引用类型数据用 instanceof 检测Object 都会返回 true

retry函数

题目: 实现一个retry函数,失败了重试,直至最大失败次数或者执行成功

思路: 通过递归实现

1.利用Promise实现一个暂停函数

2.catch方法捕获错误

3.延时后递归调用retry或者返回Promise.reject

const pause = (delay) => new Promise((resolve) => setTimeout(resolve,delay))

const retry = function(fn, retries,delay = 500){
  fn.catch((err) => retries > 0 
    ? pause(delay).then(() => retry(fn,--retries,delay)
    : Promise.reject(err)      
    ) 
}

数组扁平化

数组扁平化是将多层嵌套的数组转换为一个层级的数组的过程

function myFlat(flat){
    return flat.reduce((pre, cur) => {
        return pre.concat(Array.isArray(cur) ? myFlat(cur) : cur)
    }, [])
}

const nestedArray = [1, [2, [3, 4], 5], 6];
const myFlatResult = myFlat(nestedArray);
console.log(myFlatResult); // [ 1, 2, 3, 4, 5, 6 ]

实现reduce

Array.prototype.selfReduce = function (callback, initValue) {
    const originArray = this;
    if(!originArray.length) {
        throw new Error('selfReduce of empty array with no initial value');
    }
    // 累计器
    let accumulator = initValue === undefined ? originArray[0] : initValue
    // 循环执行 callback
    for (let i = 0; i < originArray.length; i++) {
        // 如果没有初始值且是最后一次循环,不再执行callback
        if (initValue === undefined && (i + 1) === originArray.length) break;
        accumulator = callback(
            accumulator, 
            initValue === undefined ? originArray[i + 1] : originArray[i], 
            i, 
            originArray
        );
    }
    return accumulator
}

const res = [1, 2, 3, 4, 5].selfReduce((pre, cur) => pre + cur)
console.log(res) // 15