JavaScript面试高频手写题

108 阅读3分钟

手写发布订阅模式

虽然js已经朝着函数式和数据响应式的方向狂奔而去,但学习过的面向对象思想怎么能轻易抛弃!设计模式要学,要清清楚楚地学。

发布订阅模式是什么?

发布订阅模式就是在一个事件中转站上,用户可以通过发布/订阅去发出或接收特定的事件。一个中转站一般约定有一个on/一个emit/一个off。 任务队列会区分不同的任务,所以我们用哈希map映射的方式去存储 on:把指定事件放进任务队列。

const eventHub = {
    map: [],
    on: (name, fn) => {
        // into queue
        eventHub.map[name] = eventHub.map[name] || []
        eventHub.map[name].push(fn)
    },
    emit: (name, data) => {},
    off: (name, fn) => {
        // remove from queue
        if(!eventHub.map[name]) {return}
        const index = eventHub.map[name].indexOf(fn)
        if(index < 0) {return}
        eventHub.map[name].splice(index, 1)
    }
    
}

手写AJAX

四行代码:

  1. 创建对象
  2. 设置请求方法和路径
  3. 监听onreadystatechange事件,根据状态码调用相应回调
  4. 发送消息体

const ajax = (method, url, data, success, fail) => { 
    // 1. 创建对象
    var request = new XMLHttpRequest();
    // 2. 设置请求方法和路径
    request.open(method, url);  
    // 3. 监听`onreadystatechange`事件,根据状态码调用相应回调
    request.onreadystatechange = function () {  
        if(request.readyState === 4) {  
            if(request.status >= 200 && request.status < 300 || request.status === success(request)  
            } else {  
                fail(request)  
            }  
        }  
    };  
    // 4. 发送消息体
    request.send();  
}  

手写深拷贝

方法一:JSON

const y = JSON.parse(JSON.stringify(x));

缺点:

  1. 不支持Date/正则/undefined/函数等数据类型
  2. 不支持引用(即环状结构 )

方法二:递归

我们需要根据不同数据类型进行不同的拷贝,这里统一用instance of进行判断。

const deepClone(a) {
    if (a instanceof Object) {
        let result = undefined;
        // Data type: Object
        if (a instanceof Function) {
            if (a.prototype) { // function
                result = function(){
                    return a.apply(this, arguments)
                }
            }
            else { // arrow function
                result = (...args) => {
                    return a.call(undefined, ...args)
                }
            }
        }
        else if (a instanceof Array) {
            result = []
        }
        else if (a instanceof Date) {
            result = new Date(a-0)
        }
        else if (a instanceof RegExp) {
            result = new RegExp(a.source, a.flags)
        }
        else {
            result = {}
        }
        for (let key in a) {
            result[key] = deepClone(a[key])
        }
    }
    else {
        // Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
        return a
    }
}

以上代买没有考虑环形结构如a.self = a的情况就会进入死循环。所以我们要用一个map来存放缓存。为什么用map?因为普通Object的key是String或Symbol,而这里需要key是Object。

const cache = new Map();
const deepClone(a) {
    // cache
    if (cache.get(a)) { return cache.get(a) }
    if (a instanceof Object) {
        let result = undefined;
        // Data type: Object
        if (a instanceof Function) {
            if (a.prototype) { // function
                result = function(){
                    return a.apply(this, arguments)
                }
            }
            else { // arrow function
                result = (...args) => {
                    return a.call(undefined, ...args)
                }
            }
        }
        else if (a instanceof Array) {
            result = []
        }
        else if (a instanceof Date) {
            result = new Date(a-0)
        }
        else if (a instanceof RegExp) {
            result = new RegExp(a.source, a.flags)
        }
        else {
            result = {}
        }
        
        // cache
        cache.set(a, result); 
        
        for (let key in a) {
            result[key] = deepClone(a[key])
        }
    }
    else {
        // Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
        return a
    }
}

缺点一:不适用于考虑iframe中的对象。

缺点二:cache没有清空机制。所以我们需要把cache放在参数里,在每次递归都实用它。


const deepClone(a, cache) {
    // cache
    if (!cache) { cache = new Map(); }
    if (cache.get(a)) { return cache.get(a) }
    if (a instanceof Object) {
        let result = undefined;
        // Data type: Object
        if (a instanceof Function) {
            if (a.prototype) { // function
                result = function(){
                    return a.apply(this, arguments)
                }
            }
            else { // arrow function
                result = (...args) => {
                    return a.call(undefined, ...args)
                }
            }
        }
        else if (a instanceof Array) {
            result = []
        }
        else if (a instanceof Date) {
            result = new Date(a-0)
        }
        else if (a instanceof RegExp) {
            result = new RegExp(a.source, a.flags)
        }
        else {
            result = {}
        }
        
        // cache
        cache.set(a, result); 
        
        for (let key in a) {
            result[key] = deepClone(a[key], cache)
        }
    }
    else {
        // Data type: String / Number / Bool / Undefined / Null / Symbol / Bigint
        return a
    }
}

测试代码:

const a = {  
    number: 11, 
    bool: false, 
    str: 'yeah', 
    empty: undefined, 
    emptyObj: null,  
    array: [  
        {name: 'chypre', age: 99},  
        {name: 'pepe', age: 100}  
    ],  
    date: new Date(2023,0,1,22,33,0),  
    regex: /\.(j|t)sx/i,  
    obj: { name:'chypre', age: 99},  
    f1: (a, b) => a * b,  
    f2: function(a, b) { return a * b }

}  
a.self = a

数组去重

方法一:使用Set()

let array = [1,1,2,2,3,3,3,4];
let unique = function(a) {
    return Array.from(new Set(a))
} 

let unique2 = function(a) {
    return [...new Set(a)]
}

方法二:计数排序

let array = [1,1,2,2,3,3,3,4];
let unique = function(a) {
    let map = {}
    for (let i=0; i<a.length; i++) {
        let number = a[i]
        if (mumber in map) {
            continue
        }
        map[number] = true
    }
    
    return Object.keys(map)
}

缺点:只支持字符串/如果同时存在字符串和数字,会合二为一。

方法三:使用Map

let uniq = function(a){  
    let map = new Map()  
    for(let i=0;i<a.length;i++){  
        let number = a[i]
        if(number === undefined) {continue}  
        if(map.has(number)){  
            continue  
        }  
        map.set(number, true)  
    }  
    return [...map.keys()]  
}