高频JS面试手写题(持续更新中。。。)

235 阅读6分钟

1.手写call方法

  var obj = {
      name: 456,
    };
    
 function say() {
  console.log(this.name);
}

Function.prototype.myCall = function (context, ...args) {
//将当前被调用的方法定义在cxt.func上.(为了能以对象调用形式绑定this)
//新建一个唯一的Symbol变量避免重复
  let ctx = context || window;
  let func = Symbol();
  ctx[func] = this;
  args = args ? args : [];
  //以对象调用形式调用func,此时this指向cxt 也就是传入的需要绑定的this指向
  const res = args.length > 0 ? ctx[func](...args) : ctx[func]();
  //删除防止传入对象被污染
  delete ctx[func];
  return res;
};

 say.myCall(obj);//456
 

2.手写apply方法

var obj = {
  name: 456,
};

function say(args) {
  console.log(this.name);
}

Function.prototype.myApply = function (context, args = []) {
  let ctx = context || window;
  let func = Symbol();
  ctx[func] = this;

  const res = args.length > 0 ? ctx[func](args) : ctx[func]();
  delete ctx[func];
  return res;
};

 say.myApply(obj);//456

3.手写bind

实现方法:

  • bind方法不会立即执行,需要返回一个待执行的函数;
  • 实现作用于绑定 apply
  • 当作为构造函数的时候,进行原型继承
 Function.prototype.myBind = function (context, ...args) {
      let fn = this;
      args = args ? args : [];
      return function newFunc(...args2) {
        return fn.apply(context, [...args, ...args2]);
      };
    };

4.实现new

  • 创建一个新对象
  • 把this指向这个对象
  • 返回这个对象
//构造函数
 function Parent() {
      this.name = "colin";
    }
    
  function myNew(Parent, ...args) {
      //创建一个基于构造函数prototype的新对象
      let newObj = Object.create(Parent.prototype);
      //把this指向创建的对象
      let retrunResult = Parent.apply(newObj, args);
      let isObject = typeof retrunResult === "object" && retrunResult !==null;
      let isFunction = typeof ctorReturnResult === "function";
      if (isObject || isFunction) {
        return retrunResult;
      }
      return newObj;
    }
    
     let p = myNew(Parent);

    console.log(p.name);//colin
    

5.实现instanceof

  • 实现instanceof用来判断a是A的实例,a instanceof A ,返回true为实例,否则返回false
  • instanceof 运算符用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性
  • 遍历左边变量的原型链,知道找到右边变量的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;
        }
        left = left.__proto__;
      }
    }

    let p = new Parent();
    console.log(myInstanceOf(p, Parent));//true

6.手写函数防抖debounce

适用于点击事件等场景,连续触发多次,只有一次生效

 function debounce(fn, time) {
      let timer = null;
      return (...args) => {
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn(...args);
        }, time);
      };
    }

7.手写函数节流throttle

适用于连续滚动等场景,连续触发,每个n时间生效一次

   function throttle(fn, time) {
      let flag = true;
      return (...args) => {
        if (flag) {
          flag = false;
          setTimeout(() => {
            flag = true;
            fn(...args);
          }, time);
        }
      };
    }

8.实现深拷贝

  • 判断类型,正则和日期直接返回新对象
  • 考虑循环引用的问题,判断如果hash中含有直接返回hash中的值
  • 空或者非对象类型,直接返回原始值
  • 新建一个相应的new obj.construction 加入hash
  • 遍历对象递归
    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);
      }

      let newObj = new obj.construction();
      hash.set(obj, newObj);

      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          newObj[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 newObj;
    }
    

9.数组扁平化

let arr=[1,2[3,4]];

arr.flat(Infinity) //1,2,3,4


//reduce实现

function flat(arr) {
return arr.reduce((prev, cur) => {
    return prev.contact(Array.isArray(cur) ? flat(cur) : cur);
}, []);
}
    

10.函数柯里化实现

function curry() {
      let _args = Array.from(arguments);

      var _adder = function () {
        _args.push(...arguments);
        return _adder;
      };
       // 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
      _adder.toString = function () {
        return _args.reduce(function (a, b) {
          return a + b;
        });
      };

      return _adder;
    }
   console.log(curry(1, 2)(4)); //7

11.随机生成数

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

12.数组乱序

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

13.对象数组去重

  • 写个函数把对象中的key排序,再转成字符串
  • 遍历数组利用Set 将字符串后的对象去重
输入: [{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}]
function objSort(obj) {
  let newObj = {};
  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;
}

14.模板引擎实现

  • 定义模板、数据
   let template = "<div>我是{{name}}</div>,年龄{{age}},性别{{sex}}";
    let data = {
      name: "colin",
      age: 18,
      sex: "男",
    };

  • 方法1:
function render(template, data) {
  const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
  if (reg.test(template)) {
    // 判断模板里是否有模板字符串
    const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
    template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
    return render(template, data); // 递归的渲染并返回渲染后的结构
  }
      return template; // 如果模板没有模板字符串直接返回
 }
 
  • 方法2:
function render(template, data) {
 let computed = template.replace(/\{\{(\w+)\}\}/g, function (match, key){
   return data[key];
 });
 return computed;
}
  • 打印模板:
console.log(render(template, data));//<div>我是colin</div>,年龄18,性别男

15.用setTimeOut实现setInterval (带清除定时器)

  function setIntervals(fn, t) {
      let timer = null;

      function interval() {
        fn();
        timer = setTimeout(interval, t);
      }

      interval();
      return {
        cancle: () => {
          clearTimeout(timer);
        },
      };
    }
    
    
    
    let timeObj = setIntervals(() => {
      console.log(6);
    }, 1000);
    
    //timeObj.cancle()//清除
    

16.用setInterval实现setTimeOut

   function mySetTimeOut(fn, t) {
      let timer = setInterval(() => {
        clearInterval(timer);
        fn();
      }, t);
    }
    
  mySetTimeOut(() => {
  console.log(9);
}, 3000);

17.冒泡排序

  • 时间复杂度 n^2
 function bubbleSort(arr) {
      let len = arr.length;
       //外层循环控制从头到尾 比较+交换  需要多少轮
      for (var i = 0; i < len; i++) {
      //内层循环用于完成每一轮遍历过程中重复的 比较+交换
        for (var j = 0; j < len - 1; j++) {
          let temp;
          if (arr[j] > arr[j + 1]) {
            temp = arr[j];
            arr[j] = arr[j + 1];
            arr[j + 1] = temp;
          }
        }
      }
      return arr;
    }
    console.log(bubbleSort([2, 1, 6, 4, 3]));// [1, 2, 3, 4, 6]

18.快速排序

  • 时间复杂度 n^2
    function selectSort(arr) {
      let len = arr.length;
       //缓存当前区间最小值的索引
      let minIndex;
        // i 是当前排序区间的起点
      for (var i = 0; i < len - 1; i++) {
      // i、j分别定义当前区间的上下界,i是左边界,j是右边界
        minIndex = i;
        for (var j = i; j < len; j++) {
          if (arr[j] < arr[minIndex]) {
            minIndex = j;
          }
        }

        if (minIndex !== i) {
          let temp;
          temp = arr[minIndex];
          arr[minIndex] = arr[i];
          arr[i] = temp;
        }
      }
      return arr;
    }
     console.log(selectSort([2, 1, 6, 4, 3]));//[1,2,3,4,6]

19.快速排序

 function quickSort(arr) {
      if (arr.length < 2) {
        return arr;
      }
      let current = arr[arr.length - 1];
      let left = arr.filter((itm, idx) => itm <= current && idx !== arr.length- 1);
      let right = arr.filter((itm, idx) => itm > current);
      return [...quickSort(left), current, ...quickSort(right)];
    }
     console.log(quickSort([2, 1, 6, 4, 3]));//[1,2,3,4,6]

20.类数组转化为数组方法

// 1.扩展运算符
[...arrayLike]
// 2.Array.from
Array.from(arrayLike)
// 3.Array.prototype.slice
Array.prototype.slice.call(arrayLike)
// 4.Array.apply
Array.apply(null, arrayLike)
// 5.Array.prototype.concat
Array.prototype.concat.apply([], arrayLike)

21.Object.is实现

Object.js 不会转换比较的两个值的类型,与===有些类似,但不同的是:

  • NAN在===中是不相同的,在Object.is是相等的
  • +0和-0在===是相等的,在Object.js是不等的
    Object.is = function (a, b) {
      if (a === b) {
        //这种情况只有一种是特殊的 即+0 -0
        //如果a!==0 返回true
        //如果x===0,需要判断+0 和-0    1/+0 ===Infinity 1/-0=== -Infinity

        return a !== 0 || 1 / a === 1 / b;
      }

      //a!==b 的情况下,有一种是两者相等的  即 a,b都是NaN

      return a !== a && b !== b;
    };
    
    console.log(Object.is(+0, -0));//false
    console.log(Object.is(NaN, NaN));//true

22.使用 requestAnimationFrame实现setInterval

  • 优点:requestAnimationFrame自带节流功能,基本可以保证在16.6毫秒内只执行一次,延时效果是精确的。
   function setInterval(callback, interval) {
      let timer;
      const now = Date.now;
      let startTime = now();
      let endTime = startTime;
      const loop = () => {
        timer = window.requestAnimationFrame(loop);
        endTime = now();
        if (endTime - startTime >= interval) {
          startTime = endTime = now();
          callback(timer);
        }
      };
      timer = window.requestAnimationFrame(loop);
      return timer;
    }
   

    let a=0
    setInterval((timer) => {
      console.log(1);
      a++;
      if(a==4){
        cancelAnimationFrame(timer)//取消定时器
      }
    }, 1000);

23.请求jsonp数据封装

 function fetchJsonp ({ url, data }) {
  return new Promise((resolve, reject) => {
    let container = document.getElementsByTagName("head")[0];
    let script = document.createElement('script');
    let params = [];
    for (let key in data) {
      params.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
    }
    url = url.indexOf("?") > 0 ? (url + "&") : (url + "?");
    url += params.join("&");
    script.src = url

    let fnName = data['callback']
    window[fnName] = function (res) {
      container.removeChild(script);
      resolve(res)
    }
    // 出错处理
    script.onerror = function () {
      reject()
      container.removeChild(scriptNode);
    }

    script.type = "text/javascript";
    container.appendChild(script)
  })

}

24.前端大文件切片上传

  • 通过File的slice方法对大文件进行切分,关键代码如下:
  let inputChange = async (e) => {
    var file = e.target.files[0]

    let size = 1024 * 50  //50KB 切片大小
    let fileChunks = [];
    let index = 0;//切片索引

    for (let cur = 0; cur < file.size; cur += size) {
      fileChunks.push({
        hash: index++,
        chunk: file.slice(cur, cur + size)
      })
    }
    console.log(fileChunks)
    const uploadPromise = fileChunks.map((item, index) => {
      let formData = new FormData()
      formData.append('filename', file.name)
      formData.append('hash', item.hash)
      formData.append('chunk', item.chunk)
      return axios({
        method: 'post',
        url: '/upload',
        data: formData
      })
    })
    await Promise.all([uploadPromise]);
    // 通知后端合并切片
    await axios({
      method: 'get',
      url: '/merge-image',
      params: {
        filename: file.name
      }
    });
  }