• 社招前端经典手写面试题合集


    实现千位分隔符

    // 保留三位小数
    parseToMoney(1234.56); // return '1,234.56'
    parseToMoney(123456789); // return '123,456,789'
    parseToMoney(1087654.321); // return '1,087,654.321'
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    function parseToMoney(num) {
      num = parseFloat(num.toFixed(3));
      let [integer, decimal] = String.prototype.split.call(num, '.');
      integer = integer.replace(/\d(?=(\d{3})+$)/g, '$&,');
      return integer + '.' + (decimal ? decimal : '');
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    正则表达式(运用了正则的前向声明和反前向声明):

    function parseToMoney(str){
        // 仅仅对位置进行匹配
        let re = /(?=(?!\b)(\d{3})+$)/g; 
       return str.replace(re,','); 
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    实现filter方法

    Array.prototype.myFilter=function(callback, context=window){
    
      let len = this.length
          newArr = [],
          i=0
    
      for(; i < len; i++){
        if(callback.apply(context, [this[i], i , this])){
          newArr.push(this[i]);
        }
      }
      return newArr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    实现节流函数(throttle)

    节流函数原理:指频繁触发事件时,只会在指定的时间段内执行事件回调,即触发事件间隔大于等于指定的时间才会执行回调函数。总结起来就是: 事件,按照一段时间的间隔来进行触发

    像dom的拖拽,如果用消抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多

    手写简版

    使用时间戳的节流函数会在第一次触发事件时立即执行,以后每过 wait 秒之后才执行一次,并且最后一次触发事件不会被执行

    时间戳方式:

    // func是用户传入需要防抖的函数
    // wait是等待时间
    const throttle = (func, wait = 50) => {
      // 上一次执行该函数的时间
      let lastTime = 0
      return function(...args) {
        // 当前时间
        let now = +new Date()
        // 将当前时间和上一次执行函数时间对比
        // 如果差值大于设置的等待时间就执行函数
        if (now - lastTime > wait) {
          lastTime = now
          func.apply(this, args)
        }
      }
    }
    
    setInterval(
      throttle(() => {
        console.log(1)
      }, 500),
      1
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    定时器方式:

    使用定时器的节流函数在第一次触发时不会执行,而是在 delay 秒之后才执行,当最后一次停止触发后,还会再执行一次函数

    function throttle(func, delay){
      var timer = null;
      returnfunction(){
        var context = this;
        var args = arguments;
        if(!timer){
          timer = setTimeout(function(){
            func.apply(context, args);
            timer = null;
          },delay);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    适用场景:

    • DOM 元素的拖拽功能实现(mousemove
    • 搜索联想(keyup
    • 计算鼠标移动的距离(mousemove
    • Canvas 模拟画板功能(mousemove
    • 监听滚动事件判断是否到页面底部自动加载更多
    • 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
    • 缩放场景:监控浏览器resize
    • 动画场景:避免短时间内多次触发动画引起性能问题

    总结

    • 函数防抖 :将几次操作合并为一次操作进行。原理是维护一个计时器,规定在delay时间后触发函数,但是在delay时间内再次触发的话,就会取消之前的计时器而重新设置。这样一来,只有最后一次操作能被触发。
    • 函数节流 :使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数。

    实现instanceOf

    思路:

    • 步骤1:先取得当前类的原型,当前实例对象的原型链
    • ​步骤2:一直循环(执行原型链的查找机制)
      • 取得当前实例对象原型链的原型链(proto = proto.__proto__,沿着原型链一直向上查找)
      • 如果 当前实例的原型链__proto__上找到了当前类的原型prototype,则返回 true
      • 如果 一直找到Object.prototype.__proto__ == nullObject的基类(null)上面都没找到,则返回 false
    // 实例.__ptoto__ === 类.prototype
    function _instanceof(example, classFunc) {
        // 由于instance要检测的是某对象,需要有一个前置判断条件
        //基本数据类型直接返回false
        if(typeof example !== 'object' || example === null) return false;
    
        let proto = Object.getPrototypeOf(example);
        while(true) {
            if(proto == null) return false;
    
            // 在当前实例对象的原型链上,找到了当前类
            if(proto == classFunc.prototype) return true;
            // 沿着原型链__ptoto__一层一层向上查
            proto = Object.getPrototypeof(proto); // 等于proto.__ptoto__
        }
    }
    
    console.log('test', _instanceof(null, Array)) // false
    console.log('test', _instanceof([], Array)) // true
    console.log('test', _instanceof('', Array)) // false
    console.log('test', _instanceof({}, Object)) // true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    实现bind方法

    bind 的实现对比其他两个函数略微地复杂了一点,涉及到参数合并(类似函数柯里化),因为 bind 需要返回一个函数,需要判断一些边界问题,以下是 bind 的实现

    • bind 返回了一个函数,对于函数来说有两种方式调用,一种是直接调用,一种是通过 new 的方式,我们先来说直接调用的方式
    • 对于直接调用来说,这里选择了 apply 的方式实现,但是对于参数需要注意以下情况:因为 bind 可以实现类似这样的代码 f.bind(obj, 1)(2),所以我们需要将两边的参数拼接起来
    • 最后来说通过 new 的方式,对于 new 的情况来说,不会被任何方式改变 this,所以对于这种情况我们需要忽略传入的 this

    简洁版本

    • 对于普通函数,绑定this指向
    • 对于构造函数,要保证原函数的原型对象上的属性不能丢失
    Function.prototype.myBind = function(context = window, ...args) {
      // this表示调用bind的函数
      let self = this;
    
      //返回了一个函数,...innerArgs为实际调用时传入的参数
      let fBound = function(...innerArgs) { 
          //this instanceof fBound为true表示构造函数的情况。如new func.bind(obj)
          // 当作为构造函数时,this 指向实例,此时 this instanceof fBound 结果为 true,可以让实例获得来自绑定函数的值
          // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
          return self.apply(
            this instanceof fBound ? this : context, 
            args.concat(innerArgs)
          );
      }
    
      // 如果绑定的是构造函数,那么需要继承构造函数原型属性和方法:保证原函数的原型对象上的属性不丢失
      // 实现继承的方式: 使用Object.create
      fBound.prototype = Object.create(this.prototype);
      return fBound;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    // 测试用例
    
    function Person(name, age) {
      console.log('Person name:', name);
      console.log('Person age:', age);
      console.log('Person this:', this); // 构造函数this指向实例对象
    }
    
    // 构造函数原型的方法
    Person.prototype.say = function() {
      console.log('person say');
    }
    
    // 普通函数
    function normalFun(name, age) {
      console.log('普通函数 name:', name); 
      console.log('普通函数 age:', age); 
      console.log('普通函数 this:', this);  // 普通函数this指向绑定bind的第一个参数 也就是例子中的obj
    }
    
    
    var obj = {
      name: 'poetries',
      age: 18
    }
    
    // 先测试作为构造函数调用
    var bindFun = Person.myBind(obj, 'poetry1') // undefined
    var a = new bindFun(10) // Person name: poetry1、Person age: 10、Person this: fBound {}
    a.say() // person say
    
    // 再测试作为普通函数调用
    var bindNormalFun = normalFun.myBind(obj, 'poetry2') // undefined
    bindNormalFun(12) // 普通函数name: poetry2 普通函数 age: 12 普通函数 this: {name: 'poetries', age: 18}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    注意: bind之后不能再次修改this的指向,bind多次后执行,函数this还是指向第一次bind的对象

    实现JSONP方法

    利用