一文帮你搞定 90% 的 JS 手写题!面试手写题不慌了

还在害怕手写题吗,文帮本文可以帮你扩展并巩固自己的搞定JS基础,顺便搞定90%的手手写手写题。在工作中还可以对常用的写题需求进行手写实现,比如深拷贝、面试防抖节流等可以直接用于往后的文帮项目中,提高项目开发效率。搞定不说废话了,手手写下面就直接上代码吧。写题

1.call的面试实现

 第一个参数为null或者undefined时,this指向全局对象window,文帮值为原始值的搞定指向该原始值的自动包装对象,如 String、手手写Number、写题Boolean  为了避免函数名与上下文(context)的面试属性发生冲突,使用Symbol类型作为唯一值  将函数作为传入的上下文(context)属性执行  函数执行完成后删除该属性  返回执行结果  Function.prototype.myCall = function(context,...args){       let cxt = context || window;      //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)      //新建一个唯一的Symbol变量避免重复      let func = Symbol()       cxt[func] = this;      argsargs = args ? args : []      //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向      const res = args.length > 0 ? cxt[func](...args) : cxt[func]();      //删除该方法,不然会对传入对象造成污染(添加该方法)      delete cxt[func];      return res;  } 

2.apply的实现

 前部分与call一样  第二个参数可以不传,但类型必须为数组或者类数组  Function.prototype.myApply = function(context,args = []){       let cxt = context || window;      //将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)      //新建一个唯一的源码库Symbol变量避免重复      let func = Symbol()      cxt[func] = this;      //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向      const res = args.length > 0 ? cxt[func](...args) : cxt[func]();      delete cxt[func];      return res;  } 

3.bind的实现

需要考虑:

 bind() 除了 this 外,还可传入多个参数;  bind 创建的新函数可能传入多个参数;  新函数可能被当做构造函数调用;  函数可能有返回值;

实现方法:

 bind 方法不会立即执行,需要返回一个待执行的函数;(闭包)  实现作用域绑定(apply)  参数传递(apply 的数组传参)  当作为构造函数的时候,进行原型继承  Function.prototype.myBind = function (context, ...args) {       //新建一个变量赋值为this,表示当前函数      const fn = this      //判断有没有传参进来,若为空则赋值[]      argsargs = args ? args : []      //返回一个newFn函数,在里面调用fn      return function newFn(...newFnArgs) {           if (this instanceof newFn) {               return new fn(...args, ...newFnArgs)          }          return fn.apply(context, [...args,...newFnArgs])      }  }      测试  let name = 小王,age =17;  let obj = {       name:小张,      age: this.age,      myFun: function(from,to){           console.log(this.name +  年龄  + this.age+来自 +from+去往+ to)      }  }  let db = {       name: 德玛,     age: 99  }  //结果  obj.myFun.myCall(db,成都,上海);     // 德玛 年龄 99  来自 成都去往上海  obj.myFun.myApply(db,[成都,上海]);      // 德玛 年龄 99  来自 成都去往上海  obj.myFun.myBind(db,成都,上海)();       // 德玛 年龄 99  来自 成都去往上海  obj.myFun.myBind(db,[成都,上海])();   // 德玛 年龄 99  来自 成都, 上海去往 undefined 

4.寄生式组合继承 

function Person(obj) {       this.name = obj.name      this.age = obj.age  }  Person.prototype.add = function(value){       console.log(value)  }  var p1 = new Person({ name:"番茄", age: 18}) function Person1(obj) {       Person.call(this, obj)      this.sex = obj.sex  }  // 这一步是继承的关键  Person1.prototype = Object.create(Person.prototype);  Person1Person1.prototype.constructor = Person1;  Person1.prototype.play = function(value){       console.log(value)  } var p2 = new Person1({ name:"鸡蛋", age: 118, sex: "男"}) 

5.ES6继承 

//class 相当于es5中构造函数  //class中定义方法时,前后不能加function,全部定义在class的protopyte属性中  //class中定义的所有方法是不可枚举的  //class中只能定义方法,不能定义对象,变量等  //class和方法内默认都是严格模式  //es5中constructor为隐式属性  class People{     constructor(name=wang,age=27){       this.name = name;      this.age = age;    }    eat(){       console.log(`${ this.name} ${ this.age} eat food`)    }  }  //继承父类  class Woman extends People{       constructor(name = ren,age = 27){         //继承父类属性       super(name, age);     }       eat(){         //继承父类方法        super.eat()       }   }   let wonmanObj=new Woman(xiaoxiami);   wonmanObj.eat();  //es5继承先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。   //es6继承是使用关键字super先创建父类的实例对象this,最后在子类class中修改this。源码下载 

6.new的实现

 一个继承自 Foo.prototype 的新对象被创建。  使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。  由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。  一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤  function Ctor(){       ....  }  function myNew(ctor,...args){       if(typeof ctor !== function){         throw myNew function the first param must be a function;      }      var newObj = Object.create(ctor.prototype); //创建一个继承自ctor.prototype的新对象      var ctorctorReturnResult = ctor.apply(newObj, args); //将构造函数ctor的this绑定到newObj中      var isObject = typeof ctorReturnResult === object && ctorReturnResult !== null;      var isFunction = typeof ctorReturnResult === function;      if(isObject || isFunction){           return ctorReturnResult;      }      return newObj;  } let c = myNew(Ctor); 

7.instanceof的实现

 instanceof 是用来判断A是否为B的实例,表达式为:A instanceof B,如果A是云南idc服务商B的实例,则返回true,否则返回false。  instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。  不能检测基本数据类型,在原型链上的结果未必准确,不能检测null,undefined  实现:遍历左边变量的原型链,直到找到右边变量的 prototype,如果没有找到,返回 false  function myInstanceOf(a,b){       let left = a.__proto__;      let right = b.prototype;      while(true){           if(left == null){               return false          }          if(left == right){               return true          }          leftleft = left.__proto__      }  }  //instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。  function myInstanceof(left, right) {       let proto = Object.getPrototypeOf(left), // 获取对象的原型      prototype = right.prototype; // 获取构造函数的 prototype 对象      // 判断构造函数的 prototype 对象是否在对象的原型链上      while (true) {           if (!proto) return false;          if (proto === prototype) return true;          proto = Object.getPrototypeOf(proto);      }  } 

8.Object.create()的实现

 MDN文档  Object.create()会将参数对象作为一个新创建的空对象的原型, 并返回这个空对象  //简略版  function myCreate(obj){       // 新声明一个函数      function C(){ };      // 将函数的原型指向obj      C.prototype = obj;      // 返回这个函数的实力化对象      return new C()  }  //官方版Polyfill  if (typeof Object.create !== "function") {       Object.create = function (proto, propertiesObject) {           if (typeof proto !== object && typeof proto !== function) {               throw new TypeError(Object prototype may only be an Object:  + proto);          } else if (proto === null) {               throw new Error("This browsers implementation of Object.create is a shim and doesnt support null as the first argument.");          }          if (typeof propertiesObject !== undefined) throw new Error("This browsers implementation of Object.create is a shim and doesnt support a second argument.");          function F() { }          F.prototype = proto;          return new F();      };  } 

9.实现 Object.assign 

Object.assign2 = function(target, ...source) {       if (target == null) {           throw new TypeError(Cannot convert undefined or null to object)      }      let ret = Object(target)       source.forEach(function(obj) {           if (obj != null) {               for (let key in obj) {                   if (obj.hasOwnProperty(key)) {                       ret[key] = obj[key]                  }              }          }      })      return ret  } 

10.Promise的实现

实现 Promise 需要完全读懂 Promise A+ 规范,不过从总体的实现上看,有如下几个点需要考虑到:

 Promise本质是一个状态机,且状态只能为以下三种:Pending(等待态)、Fulfilled(执行态)、Rejected(拒绝态),状态的变更是单向的,只能从Pending -> Fulfilled 或 Pending -> Rejected,状态变更不可逆  then 需要支持链式调用  class Promise {       callbacks = [];      state = pending;//增加状态      value = null;//保存结果      constructor(fn) {           fn(this._resolve.bind(this), this._reject.bind(this));      }      then(onFulfilled, onRejected) {           return new Promise((resolve, reject) => {               this._handle({                   onFulfilled: onFulfilled || null,                  onRejected: onRejected || null,                  resolve: resolve,                  reject: reject              });          });      }      _handle(callback) {           if (this.state === pending) {               this.callbacks.push(callback);              return;          }          let cb = this.state === fulfilled ? callback.onFulfilled : callback.onRejected;          if (!cb) { //如果then中没有传递任何东西              cb = this.state === fulfilled ? callback.resolve : callback.reject;              cb(this.value);              return;          }          let ret = cb(this.value);          cb = this.state === fulfilled ? callback.resolve : callback.reject;          cb(ret);      }      _resolve(value) {           if (value && (typeof value === object || typeof value === function)) {               var then = value.then;              if (typeof then === function) {                   then.call(value, this._resolve.bind(this), this._reject.bind(this));                  return;              }          }          this.state = fulfilled;//改变状态          this.value = value;//保存结果          this.callbacks.forEach(callback => this._handle(callback));      }      _reject(error) {           this.state = rejected;          this.value = error;          this.callbacks.forEach(callback => this._handle(callback));      }  } 

Promise.resolve

 Promsie.resolve(value) 可以将任何值转成值为 value 状态是 fulfilled 的 Promise,但如果传入的值本身是 Promise 则会原样返回它。  Promise.resolve(value) {     if (value && value instanceof Promise) {       return value;    } else if (value && typeof value === object && typeof value.then === function) {       let then = value.then;      return new Promise(resolve => {         then(resolve);      });    } else if (value) {       return new Promise(resolve => resolve(value));    } else {       return new Promise(resolve => resolve());   }  } 

Promise.reject

 和 Promise.resolve() 类似,Promise.reject() 会实例化一个 rejected 状态的 Promise。但与 Promise.resolve() 不同的是,如果给 Promise.reject() 传递一个 Promise 对象,则这个对象会成为新 Promise 的值。  Promise.reject = function(reason) {       return new Promise((resolve, reject) => reject(reason))  } 

Promise.all

 传入的所有 Promsie 都是 fulfilled,则返回由他们的值组成的,状态为 fulfilled 的新 Promise;  只要有一个 Promise 是 rejected,则返回 rejected 状态的新 Promsie,且它的值是第一个 rejected 的 Promise 的值;  只要有一个 Promise 是 pending,则返回一个 pending 状态的新 Promise;  Promise.all = function(promiseArr) {       let index = 0, result = []      return new Promise((resolve, reject) => {           promiseArr.forEach((p, i) => {               Promise.resolve(p).then(val => {                   index++                  result[i] = val                  if (index === promiseArr.length) {                       resolve(result)                  }              }, err => {                   reject(err)              })          })      })  } 

Promise.race

 Promise.race 会返回一个由所有可迭代实例中第一个 fulfilled 或 rejected 的实例包装后的新实例。  Promise.race = function(promiseArr) {       return new Promise((resolve, reject) => {           promiseArr.forEach(p => {               Promise.resolve(p).then(val => {                   resolve(val)              }, err => {                   rejecte(err)              })          })      })  } 

11.Ajax的实现 

function ajax(url,method,body,headers){       return new Promise((resolve,reject)=>{           let req = new XMLHttpRequest();          req.open(methods,url);          for(let key in headers){               req.setRequestHeader(key,headers[key])         }          req.onreadystatechange(()=>{               if(req.readystate == 4){                   if(req.status >= 200 && req.status <= 300){                       resolve(req.responeText)                  }else{                       reject(req)                  }              }          })          req.send(body)      })  } 

12.实现防抖函数(debounce)

 连续触发在最后一次执行方法,场景:输入框匹配  let debounce = (fn,time = 1000) => {       let timeLock = null      return function (...args){           clearTimeout(timeLock)          timeLock = setTimeout(()=>{               fn(...args)          },time)      }  } 

13.实现节流函数(throttle)

 在一定时间内只触发一次,场景:长列表滚动节流  let throttle = (fn,time = 1000) => {       let flag = true;      return function (...args){           if(flag){               flag = false;              setTimeout(()=>{                   flag = true;                  fn(...args)              },time)          }      }  } 

14.深拷贝(deepclone)

 判断类型,正则和日期直接返回新对象  空或者非对象类型,直接返回原值  考虑循环引用,判断如果hash中含有直接返回hash中的值  新建一个相应的new obj.constructor加入hash  遍历对象递归(普通key和key是symbol情况)  function deepClone(obj,hash = new WeakMap()){       if(obj instanceof RegExp) return new RegExp(obj);      if(obj instanceof Date) return new Date(obj);      if(obj === null || typeof obj !== object) return obj;      //循环引用的情况      if(hash.has(obj)){           return hash.get(obj)      }      //new 一个相应的对象      //obj为Array,相当于new Array()      //obj为Object,相当于new Object()      let constr = new obj.constructor();      hash.set(obj,constr);      for(let key in obj){           if(obj.hasOwnProperty(key)){               constr[key] = deepClone(obj[key],hash)          }      }      //考虑symbol的情况      let symbolObj = Object.getOwnPropertySymbols(obj)      for(let i=0;i<symbolObj.length;i++){           if(obj.hasOwnProperty(symbolObj[i])){               constr[symbolObj[i]] = deepClone(obj[symbolObj[i]],hash)          }      }      return constr  } 

15.数组扁平化的实现(flat) 

let arr = [1,2,[3,4,[5,[6]]]]  console.log(arr.flat(Infinity))//flat参数为指定要提取嵌套数组的结构深度,默认值为 1 //用reduce实现  function fn(arr){      return arr.reduce((prev,cur)=>{         return prev.concat(Array.isArray(cur)?fn(cur):cur)     },[])  } 

16.函数柯里化 

function sumFn(a,b,c){ return a+ b + c};  let sum = curry(sumFn);  sum(2)(3)(5)//10  sum(2,3)(5)//10   function curry(fn,...args){     let fnfnLen = fn.length,        argsargsLen = args.length;    //对比函数的参数和当前传入参数    //若参数不够就继续递归返回curry    //若参数够就调用函数返回相应的值    if(fnLen > argsLen){       return function(...arg2s){         return curry(fn,...args,...arg2s)      }    }else{       return fn(...args)    }  } 

17.使用闭包实现每隔一秒打印 1,2,3,4 

for (var i=1; i<=5; i++) {     (function (i) {       setTimeout(() => console.log(i), 1000*i)    })(i)  } 

18.手写一个 jsonp 

const jsonp = function (url, data) {       return new Promise((resolve, reject) => {           // 初始化url          let dataString = url.indexOf(?) === -1 ? ? :           let callbackName = `jsonpCB_${ Date.now()}`          url += `${ dataString}callback=${ callbackName}`          if (data) {               // 有请求参数,依次添加到url              for (let k in data) {                   url += `${ k}=${ data[k]}`              }          }          let jsNode = document.createElement(script)         jsNode.src = url          // 触发callback,触发后删除js标签和绑定在window上的callback          window[callbackName] = result => {               delete window[callbackName]              document.body.removeChild(jsNode)              if (result) {                   resolve(result)              } else {                   reject(没有返回数据)              }          }          // js加载异常的情况          jsNode.addEventListener(error, () => {               delete window[callbackName]              document.body.removeChild(jsNode)              reject(JavaScript资源加载失败)          }, false)          // 添加js节点到document上时,开始请求          document.body.appendChild(jsNode)      })  }  jsonp(http://192.168.0.103:8081/jsonp, {       a: 1,      b: heiheihei  })  .then(result => {       console.log(result)  })  .catch(err => {       console.error(err)  }) 

19.手写一个观察者模式 

class Subject{     constructor(name){       this.name = name      this.observers = []      this.state = XXXX    }    // 被观察者要提供一个接受观察者的方法    attach(observer){       this.observers.push(observer)    }    // 改变被观察着的状态    setState(newState){       this.state = newState     this.observers.forEach(o=>{         o.update(newState)      })    }  }  class Observer{     constructor(name){       this.name = name    }    update(newState){       console.log(`${ this.name}say:${ newState}`)    }  }  // 被观察者 灯  let sub = new Subject(灯)  let mm = new Observer(小明)  let jj = new Observer(小健)  // 订阅 观察者  sub.attach(mm)  sub.attach(jj)  sub.setState(灯亮了来电了) 

20.EventEmitter 实现 

class EventEmitter {       constructor() {           this.events = { };      }      on(event, callback) {           let callbacks = this.events[event] || [];          callbacks.push(callback);          this.events[event] = callbacks;          return this;      }      off(event, callback) {          let callbacks = this.events[event];          this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);          return this;      }      emit(event, ...args) {           let callbacks = this.events[event];          callbacks.forEach(fn => {               fn(...args);          });          return this;      }      once(event, callback) {           let wrapFun = function (...args) {               callback(...args);              this.off(event, wrapFun);          };          this.on(event, wrapFun);          return this;      }  } 

21.生成随机数的各种方法? 

function getRandom(min, max) {     return Math.floor(Math.random() * (max - min)) + min     } 

22.如何实现数组的随机排序? 

let arr = [2,3,454,34,324,32]  arr.sort(randomSort)  function randomSort(a, b) {     return Math.random() > 0.5 ? -1 : 1;  } 

23.写一个通用的事件侦听器函数。 

const EventUtils = {     // 视能力分别使用dom0||dom2||IE方式 来绑定事件    // 添加事件    addEvent: function(element, type, handler) {       if (element.addEventListener) {         element.addEventListener(type, handler, false);      } else if (element.attachEvent) {         element.attachEvent("on" + type, handler);      } else {         element["on" + type] = handler;      }    },    // 移除事件    removeEvent: function(element, type, handler) {       if (element.removeEventListener) {         element.removeEventListener(type, handler, false);      } else if (element.detachEvent) {         element.detachEvent("on" + type, handler);      } else {         element["on" + type] = null;      }    },   // 获取事件目标    getTarget: function(event) {       return event.target || event.srcElement;    },    // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event    getEvent: function(event) {       return event || window.event;    },   // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)    stopPropagation: function(event) {       if (event.stopPropagation) {         event.stopPropagation();      } else {         event.cancelBubble = true;      }    },    // 取消事件的默认行为    preventDefault: function(event) {       if (event.preventDefault) {         event.preventDefault();      } else {         event.returnValue = false;      }    }  }; 

24.使用迭代的方式实现 flatten 函数。 

var arr = [1, 2, 3, [4, 5], [6, [7, [8]]]]  /** * 使用递归的方式处理 * wrap 内保  存结果 ret * 返回一个递归函数 **/  function wrap() {       var ret = [];      return function flat(a) {           for (var item of              a) {                   if (item.constructor === Array) {                       ret.concat(flat(item))                  } else {                       ret.push(item)                  }          }          return ret      }  }   console.log(wrap()(arr)); 

25.怎么实现一个sleep

 sleep函数作用是让线程休眠,等到指定时间在重新唤起。  function sleep(delay) {     var start = (new Date()).getTime();    while ((new Date()).getTime() - start < delay) {       continue;    }  } function test() {     console.log(111);    sleep(2000);    console.log(222);  }  test() 

26.实现正则切分千分位(10000 => 10,000) 

//无小数点  let num1 = 1321434322222  num1.replace(/(\d)(?=(\d{ 3})+$)/g,$1,)  //有小数点  let num2 = 342243242322.3432423  num2.replace(/(\d)(?=(\d{ 3})+\.)/g,$1,) 

27.对象数组去重

输入: [{ a:1,b:2,c:3},{ b:2,c:3,a:1},{ d:2,c:2}]  输出: [{ a:1,b:2,c:3},{ d:2,c:2}]   首先写一个函数把对象中的key排序,然后再转成字符串  遍历数组利用Set将转为字符串后的对象去重  function objSort(obj){       let newObj = { }      //遍历对象,并将key进行排序      Object.keys(obj).sort().map(key => {           newObj[key] = obj[key]      })      //将排序好的数组转成字符串      return JSON.stringify(newObj)  }  function unique(arr){      let set = new Set();      for(let i=0;i<arr.length;i++){           let str = objSort(arr[i])          set.add(str)      }      //将数组中的字符串转回对象      arr = [...set].map(item => {           return JSON.parse(item)      })      return arr  } 

28.解析 URL Params 为对象 

let url = http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled;  parseParam(url)  /* 结果  {  user: anonymous,    id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型    city: 北京, // 中文需解码    enabled: true, // 未指定值得 key 约定为 true  }  */   function parseParam(url) {     const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来    const paramsArr = paramsStr.split(&); // 将字符串以 & 分割后存到数组中    let paramsObj = { };    // 将 params 存到对象中    paramsArr.forEach(param => {       if (/=/.test(param)) {  // 处理有 value 的参数        let [key, val] = param.split(=); // 分割 key 和 value        val = decodeURIComponent(val); // 解码        val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字        if (paramsObj.hasOwnProperty(key)) {  // 如果对象有 key,则添加一个值          paramsObj[key] = [].concat(paramsObj[key], val);        } else {  // 如果对象没有这个 key,创建 key 并设置值          paramsObj[key] = val;        }      } else {  // 处理没有 value 的参数        paramsObj[param] = true;      }    })   return paramsObj;  } 

29.模板引擎实现 

let template = 我是{ { name}},年龄{ { age}},性别{ { sex}};  let data = {     name: 姓名,    age: 18  }  render(template, data); // 我是姓名,年龄18,性别undefined   function render(template, data) {     const reg = /\{ \{ (\w+)\}\}/; // 模板字符串正则    if (reg.test(template)) {  // 判断模板里是否有模板字符串      const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段      templatetemplate = template.replace(reg, data[name]); // 将第一个模板字符串渲染      return render(template, data); // 递归的渲染并返回渲染后的结构    }    return template; // 如果模板没有模板字符串直接返回  } 

30.转化为驼峰命名 

var s1 = "get-element-by-id"  // 转化为 getElementById   var f = function(s) {       return s.replace(/-\w/g, function(x) {           return x.slice(1).toUpperCase();     })  } 

31.查找字符串中出现最多的字符和个数

 例: abbcccddddd -> 字符最多的是d,出现了5次  let str = "abcabcabcbbccccc";  let num = 0;  let char = ;   // 使其按照一定的次序排列  strstr = str.split().sort().join();  // "aaabbbbbcccccccc"  // 定义正则表达式  let re = /(\w)\1+/g;  str.replace(re,($0,$1) => {       if(num < $0.length){           num = $0.length;          char = $1;           }  });  console.log(`字符最多的是${ char},出现了${ num}次`);

32.图片懒加载 

let imgList = [...document.querySelectorAll(img)]  let length = imgList.length  const imgLazyLoad = function() {       let count = 0      return (function() {           let deleteIndexList = []          imgList.forEach((img, index) => {               let rect = img.getBoundingClientRect()              if (rect.top < window.innerHeight) {                   imgimg.src = img.dataset.src                  deleteIndexList.push(index)                  count++                  if (count === length) {                       document.removeEventListener(scroll, imgLazyLoad)                  }              }          })          imgListimgList = imgList.filter((img, index) => !deleteIndexList.includes(index))      })()  }  // 这里最好加上防抖处理  document.addEventListener(scroll, imgLazyLoad)  
滇ICP备2023000592号-31