手写面试题合集

本文详细介绍了如何手写深拷贝算法、创建简单的AJAX请求、监听数组变化、实现防抖和节流功能,以及使用发布/订阅模式和数组去重方法。还演示了call、bind和apply的使用以及自定义Promise的实现。
摘要由CSDN通过智能技术生成

本文整理了一下常见的手写面试题,将持续更新,如有需求可评论联系~

1、手写深拷贝

 /* 手写深拷贝 */
  function deepCopy1(obj) {
    return JSON.parse(JSON.stringify(obj));
  }

  function deepCopy2(obj) {
    //null 也是 object,但是null 没有 constructor
    if (typeof obj === "object" && obj !== null) {
      var result = obj.constructor === Array ? [] : {};
      for (const key in obj) {
        result[key] =
          typeof obj[key] === "object" ? deepCopy2(obj[key]) : obj[key];
      }
      return result;
    } else {
      var result = obj;
      return result;
    }
  }

  const obj1 = {
    name: "姓名",
    str: [1, 2, 3, 4, 5],
    attr: {
      type: "name",
    },
    strUndefined: undefined,
    strNull: null,
  };

  const obj2 = obj1;
  const obj3 = deepCopy1(obj1);
  const obj4 = deepCopy2(obj1);
  
  console.log(obj2, obj2 === obj1);
  
  //第一种实现深拷贝的方式,会把属性值为undefined的属性从对象去除掉
  console.log(obj3, obj3 === obj1); 
  
  console.log(obj4, obj4 === obj1);

2、手写ajax

function sendAjax(obj) {
    //get方式传入的时候,将data内容进行拼接
    function splitStr(data) {
      let str = "";
      for (const key in data) {
        str += key + "=" + data[key];
      }
      return str;
    }

    //原生Ajax实现步骤分析
    // 一、声明XMLHttpRequest
    var xhr = new XMLHttpRequest();
    //二、初始化HTTP请求参数,只初始化并不会发送
    if (obj.method.toUpperCase() === "GET") {
      //get方法
      xhr.open(
        obj.method,
        obj.url + "?" + splitStr(obj.data), //路径拼接
        typeof obj.async === "boolean" ? obj.async : true
      );
      // 三、发送请求
      xhr.send();
    } else if (obj.method.toUpperCase() === "POST") {
      //post方法
      xhr.open(
        obj.method,
        obj.url,
        typeof obj.async === "boolean" ? obj.async : true
      );
      xhr.setRequestHeader("content-type", "application/json"); //设置请求头,以表单方式提交
      xhr.send(obj.data); //发送请求
    }
    //四、监听发送
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4 && xhr.status === 200) {
        //success回调
        obj.success(xhr.responseText);
      } else if (xhr.readyState === 4 && xhr.status !== 200) {
        obj.error();
      }
    };
  }

  //调用方法
  sendAjax({
    method: "post",
    data: {
      name: "姓名",
      age: 18,
      age2: 19,
    },
    url: "http://127.0.0.1:5500/",
    async: false,
    success: function (result) {
      console.log(result, "发送成功了");
    },
    error: () => {
      console.log("发生了错误");
    },
  });

3、手写代码,监听数组变化,并返回数组长度

var arr = [];
//Object.getOwnPropertyNames方法返回一个数组,成员是参数对象自身的全部属性的属性名,不管该属性是否可遍历。
//Object.keys的行为不同,Object.keys只返回对象自身的可遍历属性的全部属性名。
console.log(Object.getOwnPropertyNames(Array.prototype), "arr");
Object.keys([]); // []
Object.getOwnPropertyNames([]); // [ 'length' ]

Object.keys(Object.prototype); // []
Object.getOwnPropertyNames(Object.prototype);
// ['hasOwnProperty',
//  'valueOf',
//  'constructor',
//  'toLocaleString',
//  'isPrototypeOf',
//  'propertyIsEnumerable',
//  'toString']

//Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__


const arrayMethods = Object.create(Array.prototype);
console.log(arrayMethods, "arrayMethods");

var a = { m: 1 };
var b = Object.create(a);
console.log(b.__proto__ === a, b, b.m); // true {} 1

// 获取Array的原型,并创建一个新的对象指向这个原型
// const arrayMethods = Object.create(Array.prototype)


//手写代码,监测数组变化,并返回数组长度
// 创建一个新的原型,这就是改造之后的数组原型
const ArrayProto = [];
// 重新构建Array原型里面的虽有方法
Object.getOwnPropertyNames(Array.prototype).forEach((method) => {
  if (typeof Array.prototype[method] === "function") {
    ArrayProto[method] = function () {
      console.log("监听到数组触发了" + method + "事件", arguments);
      let len = this.length;
      let result = Array.prototype[method].apply(this, arguments);
      console.log(len, this.length);
      if (len !== this.length) {
        return this.length;
      }
      return result;
    };
  }
});
let list = [1, 2, 3];

//将数组的原型链指向重新构造的原型
list.__proto__ = ArrayProto;
// 执行push事件
console.log(list.push(1), list.pop(2), list.slice(1), list.unshift(1));

4、手写防抖和节流

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>
<body>
  <input type="text" />
</body>
</html>
<script>
function debounce(fn, delay) {
  // 1. 创建一个变量,记录上一次定时器
  let timer = null;
  // 2. 触发事件时执行函数
  return function (...args) {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      console.log(this);
      //可以通过显示绑定apply,来获取事件的调用者,如果不需要直接fn(args)
      fn.apply(this, args);
      // fn();
      timer = null;
    }, delay);
  };
}

function throttle(fn, interval) {
  let startTime = 0;

  return function (...args) {
    const nowTime = new Date().getTime();
    const waitTime = interval - (nowTime - startTime);
    if (waitTime <= 0) {
      fn.apply(this, args);
      startTime = nowTime;
    }
  };
}

const inputDOM = document.querySelector("input");

let counter = 1;
function searchChange() {
  console.log(`${counter++}次请求`, this.value, this);
}

inputDOM.oninput = debounce(searchChange, 500);
</script>

5、手写实现发布/订阅模式

class Subject {
    //定义被观察者
    constructor() {
      this.observers = [];
    }
    addObserver(observer) {
      //订阅
      this.observers.push(observer);
    }
    removerObserver(observer) {
      //取消订阅
      let index = this.observers.indexOf(observer);
      if (index !== -1) {
        this.observers.splice(index, 1);
      }
    }
    notify() {
      //通知
      this.observers.forEach((observer) => {
        observer.update();
      });
    }
  }
  class Observer {
    // 定义观察者
    update() {
      console.log("subject更新了");
    }
    subscribeTo(subject) {
      subject.addObserver(this);
    }
  }
  let subject = new Subject(); //被观察者
  let observer1 = new Observer(); //观察者
  observer1.subscribeTo(subject); //观察者进行订阅
  let observer2 = new Observer(); //观察者
  observer2.subscribeTo(subject); //观察者进行订阅
  subject.notify();

6、数组去重

/* 数组去重 */
// 数组去重比较常用的方法,我理解是有三种方法,元素、索引、es6的set。基本上都是基于此三种来实现
//另外,如果元素是数组或对象,是不能被去重的,因为对象和数组是引用类型,

// 1、es6的set方法
var arr = [
  true,
  9,
  null,
  true,
  2,
  5,
  "我",
  "我",
  2,
  [1],
  undefined,
  null,
  undefined,
  { name: "姓名" },
  { name: "姓名" },
  [1],
];
console.log(arr);
function noRepeat(arr) {
  var newArr = [...new Set(arr)]; //利用了Set结构不能接收重复数据的特点
  return newArr;
}
var arr2 = noRepeat(arr);
console.log(arr2);

//2、filter方法去重
function noRepeat2(arr) {
  var newArr = arr.filter(function (item, index) {
    return arr.indexOf(item) === index; // 因为indexOf 只能查找到第一个
  });
}

var arr3 = noRepeat(arr);
console.log(arr3, "arr3");

//3、使用includes
function noRepeat3(arr) {
  let newArr = [];
  for (i = 0; i < arr.length; i++) {
    if (!newArr.includes(arr[i])) {
      newArr.push(arr[i]);
    }
  }
  return newArr;
}
console.log(noRepeat3(arr), "includes");

7、手写call、bind、apply

/* 手写apply、call、bind */
  /* 1、apply,call,bind都是可以改变this的指向
      2、apply,call会执行调用的函数,bind返回一个新函数。
      3、apply第二个参数要求是数组,call,bind则没有限制数据类型,它会把剩余的参数一起传给函数,
      bind还会把新函数调用时传入的参数一起合并,传给新函数。
      4、他们都是绑定在Function的prototype上。
     */

  // 1、apply
  Function.prototype.apply = function (context, args) {
    // 不传默认是全局,window
    context = context || window;
    // args不传时默认是空数组,防止下面用spread操作符时报错
    args = args ? args : [];
    // 把this存到context.fn,这里的this是调用的函数
    console.log(this, "this");
    context.fn = this;
    // 执行调用的函数,this指向context,参数用spread操作符扩展
    const res = context.fn(...args);
    // 删除,不污染context
    delete context.fn;
    // 返回res
    return res;
  };

  function fn(a, b, c) {
    console.log(this.name);
    console.log(a, b, c);
  }
  let obj = { name: "码上游" };
  fn.apply(obj, [1, 2, 3]); //改变fn的this指向,让其指向obj,最后再调用fn函数
  // 输出  码上游  1 2 3

  //2、call
  Function.prototype.call = function (context, ...args) {
    // 不传默认是全局,window
    context = context || window;
    // args不传时默认是空数组,防止下面用spread操作符时报错
    args = args ? args : [];
    // 把this存到context.fn,这里的this是调用的函数
    context.fn = this;
    // 执行调用的函数,this指向context,参数用spread操作符扩展
    const res = context.fn(...args);
    // 删除,不污染context
    delete context.fn;
    // 返回res
    return res;
  };

  //3、bind

  /*   instanceof 是用来判断左侧对象是否是右侧构造函数的实例化对象,
    或则说左侧对象能否通过其隐式原型 **[[proto]]**在原型链上一层层向上查找到右侧函数的原型对象,
    即函数原型对象出现在实例对象的原型链上就返回 true。
    通俗的理解: 右侧是不是左侧的爸爸、爷爷、祖宗,只要左侧对象继承自右侧函数就为 true */

  Function.prototype.bind = function (context, ...args) {
    // 不传默认是全局,window
    context = context || window;
    // 把this存到fn,这里的this是调用的函数
    let fn = this;
    return function newFn(...fnArgs) {
      let res;
      // 要考虑新函数是不是会当作构造函数
      if (this instanceof newFn) {
        // 如果是构造函数则调用new 并且合并参数args,fnArgs
        res = new fn(...args, ...fnArgs);
      } else {
        // 当作普通函数调用 也可以用上面定义的_call
        res = fn.call(context, ...args, ...fnArgs);
      }
      return res;
    };
  };

8、手写promise

 //手写Promise
function MyPromise(executor) {
  this.status = "pending";
  this.result = undefined;
  this.cb = [];
  var _this = this;
  function resolve(res) {
    if (_this.status !== "pending") {
      return;
    }
    _this.status = "fulfilled";
    _this.result = res;
    _this.cb.forEach((item) => {
      item.successCB && item.successCB(_this.result);
    });
  }
  function reject(res) {
    if (_this.status !== "pending") {
      return;
    }
    _this.status = "rejected";
    _this.result = res;
    _this.cb.forEach((item) => {
      item.failCB && item.failCB(_this.result);
    });
  }
  executor(resolve, reject);
}

MyPromise.prototype.then = function (successCB, failCB) {
  if (!successCB) {
    successCB = (value) => {
      return value;
    };
  }
  if (!failCB) {
    failCB = (error) => {
      return error;
    };
  }

  return new MyPromise((resolve, reject) => {
    if (this.status === "fulfilled") {
      var result = successCB && successCB(this.result);

      if (result instanceof MyPromise) {
        //如果result还是一个promise
        result.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        resolve(result);
      }
    }

    if (this.status === "failed") {
      var result = failCB && failCB(this.result);
      if (result instanceof MyPromise) {
        //如果result还是一个promise
        result.then(
          (res) => {
            resolve(res);
          },
          (err) => {
            reject(err);
          }
        );
      } else {
        reject(result);
      }
    }

    if (this.status === "pending") {
      //收集回调
      this.cb.push({
        successCB: () => {
          var result = successCB && successCB(this.result);
          if (result instanceof MyPromise) {
            //如果result还是一个promise
            result.then(
              (res) => {
                resolve(res);
              },
              (err) => {
                reject(err);
              }
            );
          } else {
            resolve(result);
          }
        },
        failCB: () => {
          var result = failCB && failCB(this.result);
          if (result instanceof MyPromise) {
            //如果result还是一个promise
            result.then(
              (res) => {
                resolve(res);
              },
              (err) => {
                reject(err);
              }
            );
          } else {
            reject(result);
          }
        },
      });
    }
  });
};

MyPromise.prototype.catch = function (failCB) {
  this.then(undefined, failCB);
};

//测试调用
new MyPromise((resolve, reject) => {
  console.log("五秒后执行-MyPromise");
  setTimeout(() => {
    // resolve("MyPromise返回的数据是成功的");
    reject("MyPromise返回的数据是失败的");
  }, 5000);
})
  .then(
    (res) => {
      console.log(res, "成功");
    },
    (err) => {
      console.log(err, "失败");
    }
  )
  .catch((err) => {
    console.log("MyPromise的catch的错误", err);
  });

new Promise((resolve, reject) => {
  console.log("五秒后执行-Promise");
  setTimeout(() => {
    // resolve("Promise返回的数据是成功的");
    reject("Promise返回的数据是失败的");
  }, 5000);
})
  .then(
    (res) => {
      console.log(res, "成功");
    },

    //如果没有下面的reject函数,那就会执行catch函数,获取到失败信息
    (err) => {
      console.log(err, "失败");
    }
  )
  .catch((err) => {
    console.log("Promise的catch的错误", err);
  });

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值