面试题总结

675 阅读7分钟

用reduce实现map

用reduce实现map

Promise和setTimeout综合输出顺序问题,宏事件微事件考察。

  • 浏览器运行机制详解
  • 重点在于:在执行异步队列时,执行完一个宏任务 ==> 执行完所有微任务 ===> 执行下一个宏任务,即优先执行微任务

手写一个双向绑定,input输入的值响应在p标签上

实现左右布局,输入框自适应,按钮定宽的样式

<style>
div {
  display: flex;
}
input {
  flex: 1;
}
button{
  width: 100px;
}
</style>
<div>
    <input type="text" />
    <button>提交</button>
</div>

伪数组转换为数组的方法

    let fakeArr = {
        0: 2,
        1: 3,
        2: 4,
        3: 4,
        length: 4
    }

    console.log(Array.prototype.slice.call(fakeArr, 0))
    console.log(Array.from(fakeArr))

抽取数组中n个数字,其和为sum的函数

一个元素,先transform:translate(100px,100px),再rotate(45deg)的效果,以及两个操作颠倒执行后的效果

rotate会导致元素的X、Y轴旋转

响应码的意义

判断输出

   function A(params) {
        this.c = 1;
    }
    let a = new A();
    A.prototype = {
        c: 1,
        d: 2
    };
    console.log(a.c);
    console.log(a.d);
  • a始终指向A之前无数据的原型
  • a实例本身包含属性c
  • 所以输出1 undefined

手写一个360旋转的动画

    /* animation-name	规定需要绑定到选择器的 keyframe 名称。。
    animation-duration	规定完成动画所花费的时间,以秒或毫秒计。
    animation-timing-function	规定动画的速度曲线。
    animation-delay	规定在动画开始之前的延迟。
    animation-iteration-count	规定动画应该播放的次数。
    animation-direction	规定是否应该轮流反向播放动画。*/

    @keyframes circle {
        100% {
            transform: rotate(360deg);
        }
    }

    .box1 {
        animation: circle 3s linear 0s infinite;
    }

keydown keypress keyup的区别

key相关事件发生顺序:onkeydown onkeypress onkeyup只有onkeyup能够获取输入框完整的输入

输入查询时, 两次的ajax都会修改dom, 如何解决

=====的比较规则

  • 双等号==
      1. 如果两个值类型相同,再进行三个等号(===)的比较
      1. 如果两个值类型不同,也有可能相等,需根据以下规则进行类型转换在比较:
      • 如果一个是null,一个是undefined,那么相等
      • 如果一个是字符串,一个是数值,把字符串转换成数值之后再进行比较
  • 三等号===:
      1. 如果类型不同,就一定不相等
      1. 如果两个都是数值,并且是同一个值,那么相等;如果其中至少一个是NaN,那么不相等。(判断一个值是否是NaN,只能使用isNaN( ) 来判断)
      1. 如果两个都是字符串,每个位置的字符都一样,那么相等,否则不相等。
      1. 如果两个值都是true,或是false,那么相等
      1. 如果两个值都引用同一个对象或是函数,那么相等,否则不相等
      1. 如果两个值都是null,或是undefined,那么相等

rem的使用

react如何绑定事件处理函数

sum(1)(2)(3) sum(1, 2, 3)的实现

function add(...args) {
    const totalArgs = [...args]
    // 每次调用函数,都是往参数数组里添加参数,然后再返回当前函数
    const result = function (...params) {
        totalArgs.push(...params)
        return result
    }
    // RHS引用的时候,把参数数组求和返回
    result.toString = () => {
        return totalArgs.reduce((p, n) => {
            return p + n
        })
    }
    return result
}

console.log(add(1, 2, 3))
console.log(add(1)(2, 3))
console.log(add(1)(2)(3))

防抖节流

从实现的效果来解释(延时2s为例),可能更容易理解和记忆:

  • 防抖: 如果没到时限,就清了计时器,重新倒计时。最后一次触发才会会执行操作
  • 节流: 如果没到时限,本次触发无效。每两秒执行一次操作

为了代码框架更清晰,没有实现函数执行上下文参数等细节

防抖

const debounce = (fn) => {
    let timer
    return () => {
        // 如果没到时限,就清了计时器,重新倒计时
        if (timer) clearTimeout(timer)
        timer = setTimeout(() => {
            fn()
            timer = null
        }, 2000)
    }
}

节流

1. 定时器版

第一次触发也需要等2s后执行

const throttle = (fn) => {
    let timer
    return () => {
        // 如果没到时限,本次触发无效
        if (timer) return
        timer = setTimeout(() => {
            fn()
            timer = null
        }, 2000)
    }
}
2. 时间戳版

第一次触发立即执行(需页面加载和触发时间超过2s)

const throttle1 = (fn) => {
    let previos = Date.now()
    return () => {
        // 如果没到时限,本次触发无效
        if (Date.now() - previos > 2000) return
        // 如果触发有效,记住本次触发的时间
        previos = Date.now()
        fn()
    }
}

3. 组合版本

组合版本可以保留最后一次的触发操作

const throttle2 = (fn) => {
    let previos = Date.now()
    let timer
    return () => {
        clearTimeout(timer)
        // 如果超过了时限,直接执行,
        if (Date.now() - previos > 2000) {
            previos = Date.now()
            fn()
        } else {
            // 如果没超过时限,定倒计时
            timer = setTimeout(() => {
                // !!连续触发的最后一次,才会执行到这里。因为只有最后一次的定时器没有被clear。!!
                fn()
                timer = null
                previos = Date.now()
            }, 2000)
        }
    }
}

手写promise

class Promise1 {
    constructor(start) {
        this.status = 'pending'
        this.resolve = this.resolve.bind(this)
        this.reject = this.reject.bind(this)

        this.successCBList = []
        this.errCBList = []
        this.value
        this.reason
        // 初始化promise
        start(this._resolve, this._reject)
    }

    // 异步任务完成时,会被调用,这里就是异步任务完成的时候
    _resolve(data) {
        if (this.status === 'pending') {
            this.status = 'fulfilled'
            this.value = data
            this.successCBList.map(CB => {
                this.value = CB(this.value)
            })
        }
    }

    _reject(err) {
        if (this.status === 'pending') {
            this.status = 'rejected'
            this.reason = err
            this.errCBList.map(CB => {
                CB(this.reason)
            })
        }
    }

    then(successCB, errCB) {
        // then函数返回一个promise,所以才可以链式调用then
        const p2 = new Promise1((resolve, reject) => {
            
            if (this.status === 'fulfilled') {
                // 如果状态未为成功,那么就执行then方法的成功回调
                const x = successCB(this.value)
                // 并处理成功回调的返回值,为链式调用做准备
                resolvePromise(x, resolve, reject);
            }

            if (this.status === 'rejected') {
                // 如果状态为失败,那么就执行then方法的失败回调
                const x = errCB(this.reason)
                // 并处理成功回调的返回值,为链式调用做准备
                resolvePromise(x, resolve, reject);
            }

            // 如果状态为pending,那就把 成功和失败的回调 处理后保存起来,等待状态变化后调用
            if (this.status === 'pending') {
                this.successCBList.push(() => {
                    const x = successCB(this.value)
                    resolvePromise(x, resolve, reject);
                })
                this.errCBList.push(() => {
                    const x = errCB(this.reason)
                    resolvePromise(x, resolve, reject);
                })
            }
        })
        return p2
    }

    catch(CB) {
        this.errorCB = CB
    }

}
// 解析then的返回值
function resolvePromise(x, resolve, reject) {
    // then的返回值x,如果为promise,那么就把promise执行了,然后把结果传给p2的resolve
    // 那么链式调用的下一个then,就可以获取到上一个then的结果了
    if(x instanceof Promise1) {
        x.then(res =>{
            // then函数中有可能嵌套返回promise
            resolvePromise(res, resolve, reject)
        },err =>{
            reject(err)
        })
    }else{
        resolve(x)
    }
}

// 我们没办法知道一个异步任务何时完成,所以我们需要规定在任务完成的时候必须执行一个回调函数,它就是resolve/reject。
// 这样我们就可以在回调函数里(源码里的_resolve、_reject),改变promise的状态,并且执行then/catch的callback
const p1 = new Promise1((resolve, reject) => {
    // setTimeout(() => {
        resolve(123)
    // }, 1000)
})

p1.then(data => {
    console.log(data);
}).then(() => {
    console.log(123);
    return new Promise1((resolve, reject) => {
        setTimeout(() => {
            resolve(new Promise1((resolve, reject) => {
                setTimeout(() => {
                    resolve(456)
                }, 1000)
            }))
        }, 1000)
    })
}).then(data => {
    console.log(data);
})

并发限制

var urls = [
    'https://www.kkkk1000.com/images/getImgData/getImgDatadata.jpg',
    'https://www.kkkk1000.com/images/getImgData/gray.gif',
    'https://www.kkkk1000.com/images/getImgData/Particle.gif',
    'https://www.kkkk1000.com/images/getImgData/arithmetic.png',
    'https://www.kkkk1000.com/images/getImgData/arithmetic2.gif',
    'https://www.kkkk1000.com/images/getImgData/getImgDataError.jpg',
    'https://www.kkkk1000.com/images/getImgData/arithmetic.gif',
    'https://www.kkkk1000.com/images/wxQrCode2.png'
];

function loadImg(url) {
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.onload = function() {
            console.log('一张图片加载完成');
            resolve();
        }
        img.onerror = reject
        img.src = url
    })
};

实现一个异步串行加载,然后执行三次就好了,相当于有三条队列来加载图片

function limitFn(limit) {
    // 这相当于一个队列,来异步串行加载图片,也就是一个加载完了再加载另一个
    let load = () => {
        if (urls.length === 0) return
        let url = urls.shift()
        loadImg(url).then(load)
    }

    // 开启多条队列,就相当于把并发数量控制为limit
    for (let i = 0; i < limit; i++) {
        load()
    }
}

limitFn(2)

让函数fn每间隔delay秒执行一次,执行times次

实现repeat(fn, times, delay)函数

1. 使用for await实现异步串行

function delay(func, times, delay) {
  return async function(content) {
    for (var i = 0; i < times; i++) {
      await new Promise(resolve => {
        setTimeout(() => {
          func.call(this, content);
          resolve(true);
        }, delay);
      });
    }
  };
}
delay(console.log, 3, 4000)(123);

2. 使用promise实现异步串行

function delay(fn, times, delay) {
  return async function(content) {
    let p = Promise.resolve()
    let p1 = () => new Promise((resolve => {
      setTimeout(() => {
        fn(content)
        resolve(true)
      }, delay)
    }))
    for (var i = 0; i < times; i++) {
      p = p.then(() => p1())
    }
  };
}
delay(console.log, 3, 4000)(123);