javaScript手写面试题

266 阅读4分钟

前言

这是在诸多面试里总结出来的前端的一些手写代码题,希望对你们有所帮助;

因为全部都是手写代码题,这篇文章就没有太多废话来讲解,需要的解释都在代码的注释里。

手写题集

防抖节流

防抖:

<!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
}