前端面试之JavaScript常见手撕代码题汇总

718 阅读2分钟

1.防抖和节流

  • 防抖
function debounce(handler, delay) {
    var time;
    delay = delay || 1000
    return function(){
        var _args = arguments
        var _self = this

        time && clearTimeout(time)
        time = setTimeout(() => {
            handler.call(_self, _args)
        }, delay)
    }
}
  • 节流
function throttle(handler, delay) {
    var lastTime = Date.now()
    return function () {
        var _args = arguments
        var _self = this

        var curTime = Date.now()
        console.log('curTime - lastTime=', curTime - lastTime)
        if (curTime - lastTime >= delay) {
            handler.apply(_self, _args)
            lastTime = Date.now()
        }
    }
}
  • 节流升级版(时间间隔不够的时候,最后总会执行一次)
function throttle(handler, delay) {
    var lastTime = Date.now()
    var id;
    return function(){
        var _args = arguments
        var _self = this

        var curTime = Date.now()
        console.log('curTime - lastTime=',curTime - lastTime)
        if (curTime - lastTime >= delay) {
            if (id){
                clearTimeout(id);
            }
            handler.apply(_self, _args)
            lastTime = Date.now()
        } else {
            id && clearTimeout(id);//把上一次的定时器清除
            id = setTimeout(() => {
                handler.apply(_self, _args)
            }, 2000);
        }
    }
}

2.用reduce实现map函数

Array.prototype.reduceMap = function(cb){
    return this.reduce((prev, cur, index, this)=>{
        prev.push(cb(cur), index, this);
        return prev;
    },[])
}

3.实现一个retry函数

// https://blog.csdn.net/qq_40420294/article/details/101920789
function retry(cb, times, delay) {
    return new Promise((resolve, reject) => {
        function attempt(){
            cb().then(resolve).catch(error => {
                console.log('还有'+times+'次尝试');
                if (times == 0) {
                    reject(error)
                } else {
                    times--;
                    setTimeout(() => {
                        attempt()
                    }, delay);
                }
            })
        }
        attempt();
    })
}

function getData(){
    let p = new Promise((resolve, reject) => {
        setTimeout(() => {
            let num = Math.ceil(Math.random()*20) // 生成 1-20 的随机数
            console.log('random num: ', num);
            if (num <= 10) {
                console.log('符合条件,值为: ', num);
                resolve(num);
            } else {
                reject('数字大于10, 执行失败')
            }
        }, 2000);
    })
    return p;
}

retry(getData, 5, 500);

4.用async await实现一个中间件,计算函数执行时间

async function middle(ctx, next) {
    const start = Date.now();
    await next();
    const end = Date.now();
    console.log(`total seconds: ${end-start}`);
}

5.深拷贝

function deepClone(obj) {
    let types = new Set(['boolean', 'string', 'number', 'undefined']);
    let _type = typeof obj;
    if (_type in types || obj === null) {
        return obj;
    }

    let objClone = Array.isArray(obj) ? [] : {};
    for(let key in obj) {
        if (obj.hasOwnProperty(key)) {
            objClone[key] = (typeof(obj[key]) === 'object') ? deepClone(obj[key]) : obj[key]
        }
    }

    return objClone;
}

let a = {
    name: 'lucy',
    others: {
        title: 'student',
        level: 666
    }
}
let b = deepClone(a);
a.others.title = 'teacher';
console.log(b.others.title) // student

6.手写promise.all

Promise._all = function(list) {
    return new Promise((resolve, reject) => {
        /**
         * 返回值的集合
         */
        let values = []
        let count = 0
        for (let [i, p] of list.entries()) {
            // 数组参数如果不是MyPromise实例,先调用MyPromise.resolve
            this.resolve(p).then(res => {
                values[i] = res
                count++
                // 所有状态都变成fulfilled时返回的MyPromise状态就变成fulfilled
                if (count === list.length) resolve(values)
            }, err => {
                // 有一个被rejected时返回的MyPromise状态就变成rejected
                reject(err)
            })
        }
    })
}

let p1 = new Promise((resolve, reject) => {
    resolve('p1')
})

let p2 = new Promise((resolve, reject) => {
    resolve('p2')
})

let p = Promise._all([p1, p2])
p.then(res => {
    console.log(res)
})

//  ["p1", "p2"]

7.手写promise.race

Promise.race = promises => new Promise((resolve, reject) => {
    promise.forEach(promise => {
        promise.then(resolve, reject)
    })
})

8.计算斐波那契数列(递归、动规、闭包)

  • 递归版
function fib(n) {
	if (n == 0) return 0;
    if (n && n <= 2) return 1;
    return fib(n-1) + fib(n-2)
}
  • 动规版
function fib(n) {
    if (n <= 1) return 1;
    let dp = [0, 1, 1]
    for(let i = 2; i <= n; i++) {
        dp[i] = dp[i-1] + dp[i-2];
    }
    return dp[n];
}
  • 闭包版
var fib = (function(){
    let arr = [0, 1, 1];
    return function(n){
        if (arr[n]) return arr[n];
        else {
            arr[n] = fib(n-1) + fib(n-2);
            return arr[n];
        }
    }
})()

9.斐波那契数列中第n项由多少个第一项和第二项组成

// 统计当前数字有多少个第一项,多少个第二项
// 1, 1, 2, 3, 5
function fibCount(n) {
    if (n < 0) return -1;
    let dp1 = [1, 0], dp2 = [0, 1];
    for(let i = 2; i <= n; i++) {
        dp1[i] = dp1[i-1] + dp1[i-2];
        dp2[i] = dp2[i-1] + dp2[i-2];
    }
    return [dp1[n], dp2[n]]
}

10.js怎么实现一个私有方法(电信云一面)

function Person(name, age){
    this.name = name;
    this.age = age;

    var _private_variable = ""; // 定义私有变量

    // 定义私有方法
    function privateMethod(){
        _private_variable = "inner";
        console.log(_private_variable);
    }

    privateMethod(); //调用私有方法
}

11.ajax请求过程

/**
 * 1.创建XMLHttpRequest对象
 * 2.创建http请求,指名请求方法、地址、异步参数(true:异步,false: 同步)
 * 3.发送请求
 * 4.获取数据 onreadystatechange
 */
/**
 * ajax跨域:https://segmentfault.com/a/1190000012469713
 * CORS, 反向代理
 */
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('GET', 'example.php',true);
xmlHttp.send();
xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        document.getElementById('app').innerHTML = xmlHttp.responseText;
    }
}

/**
 * post请求一定要设置请求头的格式内容
 * send方法在post请求时才使用字符串参数,否则不用带参数
 */
var xmlHttp = new XMLHttpRequest();
xmlHttp.open('POST', 'example.html',true);
xmlHttp.setRequestHeader('content-type', 'application/x-www-form-urlencoded');
xmlHttp.send('name=lucy&from=china');
xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
        document.getElementById('app').innerHTML = xmlHttp.responseText;
    }
}
// xmlhttp.readyState一共有5种请求状态,从0到4发送变化:
// 0:请求未初始化;
// 1:服务器连接已建立;
// 2:请求已接收;
// 3:请求处理中;
// 4:请求已完成,且响应已就绪

// xmlhttp.status响应状态码,比较常用的有:
// 200:请求成功;
// 304:该资源在上次请求之后没有任何修改(这通常是浏览器的缓存机制,使用GET请求时尤其需要注意);
// 403:(禁止)服务器拒绝请求;
// 404:(未找到)服务器找不到请求的网页;
// 408:(请求超时)服务器等候请求时发生超时;
// 500:(服务器内部错误)服务器遇到错误,无法完成请求。
// 原文链接:https://blog.csdn.net/yangwei234/article/details/84536079

12.快速排序

function quicksort(arr) {
    _quicksort_helper(arr, 0, arr.length - 1);
}

function _quicksort_helper(arr, left, right) {
    if (left < right) {
        var mid = _partition(arr, left, right);
        _quicksort_helper(arr, left, mid-1);
        _quicksort_helper(arr, mid+1,right);
    }
}

function _partition(arr, left, right) {
    var pivot = arr[left];
    var l = left, r = right;

    while(l < r) {
        while (r > l && arr[r] >= pivot) --r;
        arr[l] = arr[r];
        while (l < r && arr[l] <= pivot) ++l;
        arr[r] = arr[l]; 
    }

    arr[l] = pivot;

    return l;
}

var arr = [45,24,45,89,52,31,17];
quicksort(arr);
console.log(arr);

// [17, 24, 31, 45, 45, 52, 89]

13.发布-订阅模式(★★★★★)

太常见了,一定要很熟悉

class Event {
    constructor(){}
    handlers = {};

    addEventListener(type, handler) {
        if (!(type in this.handlers)) {
            this.handlers[type] = [];
        }

        this.handlers[type].push(handler);
    }

    dispatchEvent(type, ...args) {
        if (!(type in this.handlers)) {
            return new Error('not registered!!!')
        }

        this.handlers[type].forEach(handler => {
            handler(...args);
        });
    }

    removeEventListener(type, handler) {
        if (!(type in this.handlers)) {
            return new Error('invalid event!!!')
        }

        if (!handler) {
            delete this.handlers[type]
        } else {
            const idx = this.handlers[type].findIndex(ele => ele === handler);
            if (idx === -1) {
                return new Error('invalid event!!!')
            }
            this.handlers[type].splice(idx, 1);
            if (this.handlers[type].length === 0) {
                delete this.handlers[type];
            }
        }
    }
}

var event = new Event();
function load(){
    console.log('load: ',...arguments);
}
function load2(){
    console.log('load2: ',...arguments);
}
event.addEventListener('load', load)
event.addEventListener('load', load2)

event.dispatchEvent('load', 'hello, world');

event.removeEventListener('load', load);
event.dispatchEvent('load', 'hello, world');

补充: 观察者模式

class Subject{
    constructor(){
        this.state = 0;
        this.observers = [];
    }

    getState(){
        return this.state;
    }

    setState(value){
        this.state = value;
        this.notifyAllOvservers();
    }

    attach(observer){
        this.observers.push(observer);
    }

    notifyAllOvservers(){
        this.observers.forEach(observer => {
            observer.update();
        })
    }
}

class Observer{
    constructor(name, subject) {
        this.name = name;
        this.subject = subject;
        this.subject.attach(this);
    }

    update(){
        console.log(`${this.name} update, state: ${this.subject.getState()}`)
    }
}

let s = new Subject();
let o1 = new Observer('o1', s);
let o2 = new Observer('o2', s);
let o3 = new Observer('o3', s);
s.setState(1);
s.setState(2);

14.给一个DOM结构,实现getElementById方法

function _getElementById(dom, id) {
    let res = [];
    helper(dom, id, res);
    return res[0];
}

function helper(dom, id, res){
    if (dom.getAttribute('id') == id) {
        res.push(dom);
    }
    if (dom.children) {
        for(let i = 0; i < dom.children.length; i++) {
            helper(dom.children[i], id, res);
        }
    }
}

15.输出DOM结构

// 输出DOM结构
var str = '';
var el = document.documentElement;
var empty;
var level = 0;

function output(el, level) {
    if (el) {
        if (level > 0) {
            empty = new Array(level).fill(' ');
            str += empty.join('');
        }
        str += el.tagName;
        str += '\n'; // 换行
    }
    if (el.children) {
        for(let i = 0; i < el.children.length; i++) {
            output(el.children[i], level+1);
        }
    }
}

output(el, level);
console.log(str)

16.实现一个红绿灯效果

const timeout = (time) => {
    return new Promise(resolve => setTimeout(resolve, time))
}

var el = document.querySelector('body')
function change(){
    timeout(2000).then(() => {
        el.style.backgroundColor = 'red';
        return timeout(3000)
    }).then(() => {
        el.style.backgroundColor = 'yellow';
        return timeout(1000)
    }).then(() => {
        el.style.backgroundColor = 'green';
        change()
    })
}

change();

18.实现一个限流器

// https://blog.csdn.net/zz_jesse/article/details/107293743?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~first_rank_v2~rank_v25-5-107293743.nonecase&utm_term=js%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E5%B8%A6%E5%B9%B6%E5%8F%91%E9%99%90%E5%88%B6%E7%9A%84%E5%BC%82%E6%AD%A5%E8%B0%83%E5%BA%A6%E5%99%A8
class Scheduler {
    // TODO
    list = [];
    constructor(){}
    add(promiseCreator) {
        this.list.push(promiseCreator)
    }
    max = 2;
    startIndex = 0;

    enter(){
        for(var i = 0; i < this.max; i++){
            this.run();
        }
    }

    run(){
        if (this.list.length == 0 || this.startIndex >= this.max) return;
        this.startIndex++;
        this.list.shift()().then(() => {
            this.startIndex--;
            this.enter();
        })
    }
}

const scheduler = new Scheduler();

const timeout = time => {
    return 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)

scheduler.enter();

// 2 3 1 4

19.实现一个sleep函数

function sleep(ms) {
    return new Promise((resolve) => {
        setTimeout(() => {
            console.log('sleep...')
            resolve()
        }, ms);
    })
}

async function test(){
    console.log('1');
    await sleep(400)
    console.log('2')
}

test();

20.实现ES6中的map函数

Array.prototype._map = function(cb) {
    if (Array.isArray(this)) {
        return this.reduce((prev, cur, index, arr) => {
            prev.push(cb(cur, index, arr))
            return prev;
        }, []);
    } else {
        throw new Error('不是数组!')
    }
}

let arr = [1,2,3,4];
let res = arr._map((ele, index, num) => {
    console.log(index, num)
    return ele * 2;
})
console.log(res)

0 [ 1, 2, 3, 4 ]
1 [ 1, 2, 3, 4 ]
2 [ 1, 2, 3, 4 ]
3 [ 1, 2, 3, 4 ]
[ 2, 4, 6, 8 ]