前言
这是在诸多面试里总结出来的前端的一些手写代码题,希望对你们有所帮助;
因为全部都是手写代码题,这篇文章就没有太多废话来讲解,需要的解释都在代码的注释里。
手写题集
防抖节流
防抖:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">点击</button>
</body>
<script>
const btn = document.getElementById('btn');
// 应该被调用函数
function clickdom(count) {
console.log('防抖函数',count);
}
// 防抖
function debounce(fn, delay) {
let timer = null; // 定义一个定时器变量
return function() {
let context = this; // 保存当前上下文
let args = arguments; // 保存当前参数
if (timer) {
clearTimeout(timer); // 如果已经有定时器,则清除
}
timer = setTimeout(function() { // 设置一个新的定时器
fn.apply(context, args); // 在延迟后执行函数,并保持上下文和参数
}, delay);
};
}
let fn = debounce(clickdom,2000)
btn.addEventListener('click',fn)
</script>
</html>
节流:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id="btn">节流</button>
</body>
<script>
const btn = document.getElementById('btn');
// 应被调用的函数
function usefn(something){
console.log('节流函数',something)
}
// 节流
function throttle(fn, delay) {
let flag = true; // 定义一个标志变量
return function() {
let context = this; // 保存当前上下文
let args = arguments; // 保存当前参数
if (!flag) return; // 如果标志为false,则不执行函数
flag = false; // 将标志设为false
setTimeout(function() { // 设置一个延迟函数
fn.apply(context, args); // 在延迟后执行函数,并保持上下文和参数
flag = true; // 将标志设为true,可以再次触发函数
}, delay);
};
}
let fn = tro(usefn,2000)
btn.addEventListener('click',fn)
</script>
</html>
call,apply,bind
手写call:
// 手写 call
Function.prototype.myCall = function(context, ...args) {
context = context || window; // 如果没有传入上下文对象,则默认为window
let fn = Symbol(); // 定义一个唯一的属性名,避免覆盖已有属性
context[fn] = this; // 将当前函数作为上下文对象的属性
let result = context[fn](...args); // 使用扩展运算符执行函数调用,并传入参数
delete context[fn]; // 删除上下文对象的属性
return result; // 返回结果
};
手写apply:
// 手写 apply
Function.prototype.myApply = function(context, args) {
context = context || window; // 如果没有传入上下文对象,则默认为window
let fn = Symbol(); // 定义一个唯一的属性名,避免覆盖已有属性
context[fn] = this; // 将当前函数作为上下文对象的属性
let result; // 定义一个变量,用于存放结果
if (!args) { // 如果没有传入参数数组,则直接调用函数
result = context[fn]();
} else {
result = context[fn](...args); // 使用扩展运算符执行函数调用,并传入参数
}
delete context[fn]; // 删除上下文对象的属性
return result; // 返回结果
};
手写bind:
// 手写 bind
Function.prototype.myBind = function(context, ...args1) {
let self = this; // 将当前函数保存在一个变量中
return function(...args2) {
return self.call(context,...args1,...args2); / /使用call方法执行当前函数,并合并两个参数数组
};
};
手写promise及then(不包括then的链式调用)
class MyPromise{
constructor(executor){
this.state = 'pending'; //状态
this.value = undefined; //成功时的传值
this.reason = undefined //失败时回调的传值
this.resolveCallback = []; //成功的调用队列
this.rejectCallback = []; //失败时的调用队列
function resolve(value){
if(this.state == 'pending'){
this.state = 'fulfilled'
this.value = value
this.resolveCallback.forEach(item => { //变更状态后进行成功任务队列调用
item()
});
}
}
function reject(reason){
if(this.state == 'pending'){
this.state = 'rejected'
this.reason = reason
this.rejectCallback.forEach(item =>{ //变更状态后进行失败任务队列调用
item()
})
}
}
try{
executor(resolve,reject)
}catch(err){
throw reject(err)
}
}
then(onresolve,onreject){
if(this.state == 'fulfilled'){ //状态是成功直接调用成功回调
onresolve()
}else if(this.state == 'rejected'){ //状态是失败直接调用失败回调
onreject()
}else if(this.state == 'pending'){ //状态是pending,不进行回调调用,而是加入成功或者失败队列
this.resolveCallback.push(()=>{
onresolve(this.value)
})
this.rejectCallback.push(()=>{
onreject(this.reason)
})
}
}
}
发布订阅
这里只是简单的写了一下原理
class Observer{
constructor(){
this.event = {}
}
// 订阅函数
on(name,callback){
if(this.event[name]){
this.event[name].push(callback)
}else{
this.event[name] = [];
this.event[name].push(callback)
}
}
// 发布函数
emit(name){
if(this.event[name]){
this.event[name].forEach(item => {
item()
});
}
}
// 取消订阅
off(name,callback){
if(this.event[name]){
for(let i=0;i<this.event[name].length;i++){
if(callback == this.event[name][i]){
this.event[name].splice(i,1);
break;
}
}
}
}
// 一次订阅
once(name,callback){
let wrap = (...args)=>{
callback(...args);
this.off(name,wrap)
}
this.on(name,wrap);
}
}
手写深浅拷贝
深拷贝:
function deepCopy(obj){
let newObj = null;
if(typeof obj != 'object' || obj === null){ //如果是基本数据类型直接返回
newObj = obj;
}else{
if(Array.isArray(obj)){ //如果是数组,用push增加
newObj = [];
for(let item in obj){
newObj.push(deepCopy(obj[item]))
}
}else{ //如果是对象
newObj = {};
for(let item in obj){
newObj[item] = deepCopy(obj[item])
}
}
}
return newObj
}
浅拷贝:
// 手写浅拷贝函数
function shallowCopy(obj) {
// 判断参数是否为对象类型,如果不是则直接返回
if (typeof obj !== "object" || obj === null) return obj;
// 创建一个新对象,用于存储拷贝后的属性值
let newObj = {};
// 遍历原对象的属性,并将其复制到新对象上
for (let key in obj) {
// 判断是否为自身属性(非原型链上继承来的)
if (obj.hasOwnProperty(key)) {
// 如果是,则直接赋值即可(注意这里可能会拷贝引用地址)
newObj[key] = obj[key];
}
}
// 返回新对象
return newObj;
}
手写数组去重
利用Set:
let arr = [1,2,1,2,1,3];
setArr = new Set(arr);
arr = [...setArr]; //[1,2,3]
利用对象键名不可重复:
function noRepeat(arr){
let obj = {};
for(let i=0;i<arr.length;i++){
if(!obj[arr[i]]){
obj[arr[i]] = 1;
}
}
return Object.keys(obj)
}
利用includes,indexOf,Map(),reduce,filter等方法都可去重。
对象扁平化
// 手写对象扁平化函数
function flattenObject(obj) {
// 判断参数是否为对象类型,如果不是则直接返回
if (typeof obj !== "object" || obj === null) return obj;
// 创建一个新对象,用于存储扁平化后的属性值
let newObj = {};
// 定义一个辅助函数,用于递归遍历原对象
function helper(curObj, prefix) {
// 遍历当前对象的属性,并将其复制到新对象上
for (let key in curObj) {
// 判断是否为自身属性(非原型链上继承来的)
if (curObj.hasOwnProperty(key)) {
// 拼接当前属性名和前缀(如果有)
let newKey = prefix ? `${prefix}.${key}` : key;
// 判断当前属性值是否为对象类型,如果是则递归调用辅助函数
if (typeof curObj[key] === "object" && curObj[key] !== null) {
helper(curObj[key], newKey);
} else {
// 如果不是,则直接赋值即可
newObj[newKey] = curObj[key];
}
}
}
}
// 调用辅助函数,传入原对象和空字符串作为前缀
helper(obj, "");
// 返回新对象
return newObj;
}
手写instanceof函数
function myInstanceof(obj,Gouzao){
if(typeof obj !== 'object' && typeof obj !== 'function'){ //如果是基本类型直接返回false
return false
}
let pro = Object.getPrototypeOf(obj);
let proto = Gouzao.prototype
while(pro !== null){ // 不断拿更上一层的原型做对比
if(pro === proto){
return true
}
pro = Object.getPrototypeOf(pro)
}
return false
}