10个常考的前端手写题

1. 实现一个简单的 JavaScript 函数,用于判断一个对象是否为空:

判断对象是否为空的函数 这个函数通过遍历对象的所有属性来检查是否有任何实际定义的键。如果在循环中找到了一个键,则立即返回false,表示对象不为空。如果没有找到任何键,则在循环结束后返回true,表示对象为空。

function isEmptyObject(obj) { for (var key in obj) { if (obj.hasOwnProperty(key)) { return false;
        }
    }
    return true;
}

2. 手写数组去重:

数组去重 第一个实现是使用传统的方法,创建一个新的数组,并利用indexOf方法检查当前元素是否已存在于新数组中,不存在则添加进去。 第二个实现利用了ES6中的Set数据结构,它不允许重复值,所以可以直接将数组转换为Set再转回数组达到去重效果。

function unique(arr) { let res = [];
    for(let i = 0; i < arr.length; i++) { if(res.indexOf(arr[i]) === -1) { res.push(arr[i]);
        }
    }
    return res;
}
// 或者使用ES6的新特性Set
function uniqueES6(arr) { return [...new Set(arr)];
}

3. 实现bind函数:

实现bind函数 JavaScript中原生的bind函数可以创建一个新的函数,在调用时设置其this上下文并传递预设参数。这里的实现同样创建了一个新的函数,并在其内部调用了原函数,同时保证了this指向和传参的正确性。

Function.prototype.myBind = function(context, ...args) { const self = this;
    return function(...newArgs) { return self.apply(context, [...args, ...newArgs]);
    };
};

4. 实现数组的map方法:

实现map方法 Array.prototype.map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。这里我们模拟了这个行为,对原数组进行遍历,并将回调函数应用于每个元素,然后将结果推入新数组。

Array.prototype.myMap = function(callback, thisArg) { const newArray = [];
    for (let i = 0; i < this.length; i++) { newArray.push(callback.call(thisArg, this[i], i, this));
    }
    return newArray;
};

5. 实现简易版的深拷贝:

简易版深拷贝 实现了一个递归函数,用于复制给定对象的所有属性和嵌套对象。当遇到非对象或null类型的值时直接返回,否则创建一个新的对象并递归地复制原对象的所有属性。

function deepClone(obj) { if (typeof obj !== 'object' || obj === null) { return obj;
    }
    let cloneObj = Array.isArray(obj) ? [] : {};
    for(let key in obj) { if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key]);
        }
    }
    return cloneObj;
}

6. 实现防抖函数(debounce):

防抖函数(debounce) 防抖函数用于限制某个函数在一定时间内只能执行一次。例如在窗口 resize 或输入框连续输入事件中,防止短时间内多次触发。这里的实现是在每次调用时清除上一次的延时任务,然后重新设置一个延时任务,只有在指定时间间隔内没有再次调用时,才会执行原函数。

function debounce(func, wait) { let timeout;
    return function(...args) { clearTimeout(timeout);
        timeout = setTimeout(() => { func.apply(this, args);
        }, wait);
    };
}

7. 实现节流函数(throttle):

节流函数(throttle) 节流函数确保在一定时间内,只允许函数执行一次。与防抖不同的是,节流保证了在持续触发的情况下,至少每隔一定时间会执行一次函数。这里的实现是在触发函数时记录上一次执行的时间,如果当前时间与上次执行时间差大于设定的时间间隔,则执行函数

function throttle(func, delay) { let prev = Date.now();
    return function(...args) { const now = Date.now();
        if (now - prev >= delay) { func.apply(this, args);
            prev = now;
        }
    };
}

8. 实现 Promise 的 then 方法:

实现Promise的then方法 Promise的then方法接受两个回调函数作为参数,分别处理成功和失败的情况。这里模拟Promise的状态机,根据Promise当前状态异步执行相应的回调函数,并处理回调返回的新Promise。

MyPromise.prototype.then = function(onFulfilled, onRejected) { let self = this;
    return new MyPromise((resolve, reject) => { if (self.status === 'fulfilled') { setTimeout(() => { // 异步执行
                try { let x = onFulfilled(self.value);
                    resolvePromise(x, resolve, reject);
                } catch(e) { reject(e);
                }
            }, 0);
        } else if (self.status === 'rejected') { setTimeout(() => { try { let x = onRejected(self.reason);
                    resolvePromise(x, resolve, reject);
                } catch(e) { reject(e);
                }
            }, 0);
        } else { self.onResolvedCallbacks.push(() => { setTimeout(() => { try { let x = onFulfilled(self.value);
                        resolvePromise(x, resolve, reject);
                    } catch(e) { reject(e);
                    }
                }, 0);
            });
            self.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(self.reason);
                        resolvePromise(x, resolve, reject);
                    } catch(e) { reject(e);
                    }
                }, 0);
            });
        }
    });
};
function resolvePromise(value, resolve, reject) { if (value instanceof MyPromise) { value.then(resolve, reject);
    } else { resolve(value);
    }
}

9. 实现简易版 Ajax 请求:

简易版Ajax请求 实现了一个基于XMLHttpRequest的简易Ajax请求函数,返回一个Promise对象。当请求完成且状态码正常时解析响应内容并resolve,否则reject。

function ajax(url, method, data) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
        xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve(JSON.parse(xhr.responseText));
                } else { reject(xhr.statusText);
                }
            }
        };
        xhr.onerror = function() { reject(xhr.statusText);
        };
        xhr.send(JSON.stringify(data));
    });
}

10. 实现一个简易版的事件委托:

实现事件委托 事件委托是一种优化事件处理的方式,通过监听父级元素的事件,然后通过事件对象判断触发事件的具体子元素,从而减少绑定事件的数量。在这个实现中,当父元素接收到事件时,会向上遍历事件传播链,查找是否匹配特定选择器的目标元素,如果匹配就执行处理器函数。

function delegateEvent(element, selector, eventType, handler) { element.addEventListener(eventType, function(event) { let target = event.target;
        while (target && target !== this) { if (target.matches(selector)) { handler.call(target, event);
                break;
            }
            target = target.parentNode;
        }
    });
}