2020年,初中级前端必会 JavaScript 面试题

251 阅读11分钟

2020年,初中级前端必会 JavaScript 面试题

人不狠话不多,尽量直接上代码。

实现节流函数 (throttle)

防抖函数原理:规定在一单位时间内,能触发一次函数,如果多次触发函数,只生效一次。

//节流函数
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>throttle-函数截流</title>
  </head>
  <body>
    <script>
      // 函数截流:规定时间内只触发一次
      window.onresize = throttle(function() {
        console.log('onresize')
      })
      function throttle(fn) {
        let timer = null
        return function() {
          if (!timer) {
            timer = setTimeout(() => {
              clearTimeout(timer)
              fn.apply(this, arguments)
              timer = null
            }, 500)
          }
        }
      }

      window.location.hash = 'hash字符串' // 用于设置 hash 值

      let hash = window.location.hash // 获取当前 hash 值

      // 监听hash变化,点击浏览器的前进后退会触发
      window.addEventListener(
        'hashchange',
        function(event) {
          let newURL = event.newURL // hash 改变后的新 url
          let oldURL = event.oldURL // hash 改变前的旧 url
        },
        false
      )
    </script>
  </body>
</html>

适用场景 :

  • 按钮提交场景: 防止多次提交按钮,只执行最后提交的一次
  • 服务端验证场景 : 表单验证需要服务端配合,只执行一段连续的输入事件的最后一次,还有搜索联想词功能类似

实现防抖函数 (debounce)

防抖函数原理 : 在事件被触发n秒后在执行回调,如果在这n秒内又被触发,则重新计时。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>debounce - 函数去抖</title>
  </head>
  <body></body>
</html>
<script>
  // 函数去抖:规定时间内只触发一次,多次触发则重新计算触发时间
  function debounce(fn,delay = 500) {
    let timer = null
    return function(...args) {
      if (timer) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          fn.apply(this, args)
          timer = null
        }, 500)
      } else {
        timer = setTimeout(() => {
          fn.apply(this, args)
        }, delay)
      }
    }
  }
</script>

适用场景:

  • 拖拽场景: 固定时间内只执行一次,防止超高频次触发位置变动
  • 缩放场景: 监控浏览器resize
  • 动画场景: 避免短时间内多次触发动画引起性能问题

深克隆 (deepclone)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>克隆等问题</title>
  </head>
  <body></body>
</html>
<script>
  let sym = Symbol('a symbole')
  function person(pname) {
    this.name = pname;
  }
  const Messi = new person('Messi');
  let obj = {
    a: say,
    b:Messi,
    name: '盖伦',
    age: 18,
    a: undefined,
    b: null,
    c: NaN,
    getName() {
      console.log(this.name)
    },
    skills: [
      {
        name: '大宝剑',
        hurt: 100
      },
      {
        name: '大旋转',
        hurt: 90
      }
    ],
    [sym]:'a symbole',
    fn:function(){
      console.log('21312')
    },
    dat:new Date(),
    reg:new RegExp()
  }

//克隆简单json类型对象

//局限性 : 
//1、他无法实现函数、RegExp等特殊对象的克隆
//2、会抛弃对象的constructor,所有的构造函数会指向Object
//3、对象有循环引用,会报错

console.log(JSON.parse(JSON.stringify(obj)));
 
console.log(deepClone(obj));

  function deepClone(obj) {
    const isType = target => type =>
      `[object ${type}]` === Object.prototype.toString.call(target)
    let result
    if (isType(obj)('Object')) {
      result = {}
    } else if (isType(obj)('Array')) {
      result = []
    } else {
      result = obj
    }
    for (const key in obj) {
      if (obj.hasOwnProperty(key)) {
        const copy = obj[key]
        if (isType(copy)('Object') || isType(copy)('Array')) {
          result[key] = deepClone(copy)
        } else {
          result[key] = copy
        }
      }
    }
    return result
  }

</script>

new 关键字实现

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>new关键字实现</title>
</head>
<body>
  new操作符做了这些事:

    他创建了一个全新的对象
    他会被执行[[Prototype]] (也就是__proto__) 链接
    它使this指向新创建的对象
    通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
    如果函数没有返回对象类型Object(包含Function,Array,Date,RegExg,Error),那么new表达式中的函数调用将返回对象引用


</body>
</html>
<script>

function New() {
  let Foo = Array.prototype.shift.call(arguments);//获取函数,arguments长度减一
  let res = Object.create(Foo.prototype);//创建新对象,并且链接Foo.prototype
  let result = Foo.apply(res,arguments);//执行构造函数,改变this指向
  // 确保 new 出来的是个对象 返回的值是什么就return什么
  return typeof result === 'object' ? result : res;
}

function Person(name,age) {
  this.name = name;
  this.age = age;
  this.getName = function () {
    return this.name;
  }
}

let obj1 = New(Person,'jack',18)
let obj2 = new Person('jack',18)

console.log(obj1);
console.log(obj2);

</script>

instanceOf 实现

实现原理: 只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false,告诉我们左边变量并非是右边变量的实例。

function new_instance_of(leftVaule, rightVaule) { 
    let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
    leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
    while (true) {
    	if (leftVaule === null) {
            return false;	
        }
        if (leftVaule === rightProto) {
            return true;	
        } 
        leftVaule = leftVaule.__proto__ 
    }
}

几种继承以及实现方式

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>继承以及实现方式</title>
  </head>
  <body></body>
</html>
<script>
  /* -------------------------- 原型链继承 ------------------ */
  function Parent(name) {
    this.name = name
    this.skills = ['大宝剑', '大旋转']
  }
  Parent.prototype.getName = function() {
    return this.name
  }

  function Child() {
    this.age = 18
  }

  Child.prototype = new Parent('陈先生')

  let child = new Child()
  let child2 = new Child()

  /* 
原型链继承问题:
    如果通过child2,修改了Parent的skills, 则child 都会受影响
*/

  console.log('child---', child)

  /* -------------------------- 构造函数继承 ------------------ */
  {
    function SuperClass(name) {
      this.name = name
      this.skills = ['大宝剑', '大旋转']
    }

    SuperClass.prototype.showSkills = function() {
      console.log(this.skills)
    }

    function SubClass(name) {
      SuperClass.call(this, name)
    }
    SubClass.prototype.getName = function() {
      console.log(this.name)
    }
    let sub1 = new SubClass('张三')
    let sub2 = new SubClass('李四')

    sub1.skills.push('天崩地裂')

    console.log('sub1---', sub1.getName())

    console.log('sub2---', sub2.getName())

    /* 
    构造函数继承问题:
        未能继承父类的原型(prototype),在子类构造函数中实现继承后,都会单独拥有一份,违背代码复用原则,
    */
  }
  /* -------------------------- 组合继承继承 ------------------ */
  {
    function SuperClass(name) {
      this.name = name
      this.skills = ['大宝剑', '大旋转']
    }
    SuperClass.prototype.showSkills = function() {
      console.log(this.skills)
    }

    function SubClass(name) {
      SuperClass.call(this, name)
      this.time = time
    }
    SubClass.prototype = new SuperClass()
    SubClass.prototype.getTime = function() {
      console.log(this.time)
    }
    /* 
    组合继承继承缺点:
        父类构构造函数执行了两次,并不完美
    */
  }

  {
    /* -------------------------- 原型式继承,寄生式继承 ------------------ */
    function inheritObject(o) {
      //原型式继承
      function F() {}
      F.prototype = o
      return new F()
    }

    function createBook(obj) {
      var o = new inheritObject(obj)
      o.getName = function() {
        console.log(name)
      }
      return o
    }
    var book = {
      name: 'js book',
      alikeBook: ['css', 'html', 'js']
    }
    var oBook = createBook(book)

    console.log('oBook---', oBook)
  }

  {
    /* -------------------------- 寄生组合式继承 ------------------ */
    function inheritPrototype(SubClass, SuperClass) {
      //复制父类
      var p = Object.create(SuperClass.prototype)
      //修正子类原型被重写导致的constructor属性变化
      p.constructor = SubClass
      //设置子类原型
      SubClass.prototype = p
    }
    function SuperClass(name) {
      this.name = name
      this.skills = ['大宝剑', '大旋转']
    }
    SuperClass.prototype.showSkills = function() {
      console.log(this.skills)
    }

    function SubClass(name) {
      SuperClass.call(this, name)
      this.time = '11111'
    }
    // SubClass.prototype = new SuperClass();
    SubClass.prototype.getTime = function() {
      console.log(this.name)
    }

    inheritPrototype(SubClass, SuperClass)

    var instance1 = new SubClass('张三')
    var instance2 = new SubClass('李四')
    console.log('instance1---', instance1)
    console.log('instance2---', instance2)
  }
</script>

实现数组 reduce-filter-map-find方法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>实现数组 reduce-filter-map-find方法</title>
  </head>
  <body></body>
</html>
<script>
  // reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
  Array.prototype._reduce = function(fn, initVal, context) {
    let arr = Array.prototype.slice.call(this)
    for (let i = 0; i < arr.length; i++) {
      initVal = fn.call(context, initVal, arr[i], i, this)
    }
    return initVal
  }

  // map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理的后值。
  Array.prototype._map = function(fn, context) {
    let arr = Array.prototype.slice.call(this)
    let ret = []
    for (let i = 0; i < arr.length; i++) {
      ret.push(fn.call(context, arr[i], i, this))
    }
    return ret
  }

  Array.prototype._map2 = function(fn, context) {
    //reduce实现
    let arr = Array.prototype.slice.call(this)
    return arr._reduce((init, cur, i) => {
      return [...init, fn.call(context, cur, i, this)]
    }, [])
  }

  Array.prototype._filter = function(fn, context) {
    let arr = Array.prototype.slice.call(this)
    let ret = []
    for (let i = 0; i < arr.length; i++) {
      fn.call(context, arr[i], i, this) && newArr.push(arr[i])
    }
    return ret
  }

  Array.prototype._filter2 = function(fn, context) {
    //reduce实现
    let arr = Array.prototype.slice.call(this) //获取数组,可以和简单原数组解耦
    return arr.reduce((init, cur, i) => {
      return fn.call(context, cur, index, this) ? [...init, cur] : [...init]
    }, [])
  }

  // find() 方法返回通过测试(函数内判断)的数组的第一个元素的值。
  Array.prototype._find = function(fn, context) {
    let arr = Array.prototype.slice.call(this)
    for (let i = 0; i < arr.length; i++) {
      if (fn.call(context, arr[i], i, this)) {
        return arr[i]
      }
    }
  }

  // some() 方法会依次执行数组的每个元素:
  // 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
  // 如果没有满足条件的元素,则返回false。
  Array.prototype._some = function(fn, context) {
    let arr = Array.prototype.slice.call(this)
    for (let i = 0; i < arr.length; i++) {
      if (fn.call(context, arr[i], i, this)) {
        return true
      }
    }
    return false
  }

  // every方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
  Array.prototype._every = function(fn, context) {
    let arr = Array.prototype.slice.call(this)
    for (let i = 0; i < arr.length; i++) {
      if (!fn.call(context, arr[i], i, this)) {
        return false
      }
    }
    return true
  }

  var arr = [2, 4, 6, 8]

  let result1 = arr._reduce(function(val, item, index, origin) {
    return val + item
  }, 0)

  console.log(result1) //20

  let result2 = arr._map2(function(item, index, origin) {
    return item * 5
  })

  console.log(result2)

  var myArr = [1, 2, 10, 100]

  // console.log(
  //   'myReduce:',
  //   myArr.myReduce((init=0,cur,i,arr)=>{
  //     return init+cur
  //   })
  // );
  // console.log(
  //   'myFilter',
  //   myArr.myFilter((item,i)=>{
  //     return item>5
  //   })
  // );
  // console.log(
  //   'myMap',
  //   myArr.myMap((item,i)=>{
  //     return item*5
  //   })
  // );
  // console.log(
  //   'myFind',
  //   myArr.myFind((item,i)=>{
  //     return item>10
  //   })
  // );
  // console.log(
  //   '_myMap',
  //   myArr._myMap((item,i)=>{
  //     return item*2
  //   })
  // );
  // console.log(
  //   '_myFilter',
  //   myArr._myFilter((item,i)=>{
  //     return item>5
  //   })
  // );
</script>

call,apply,bind函数的实现

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>call,apply,bind函数的实现</title>
  </head>
  <body></body>
</html>
<script>
  // 根据call的规则设置上下文对象,也就是this的指向。
  // 通过设置context的属性,将函数的this指向隐式绑定到context上
  // 通过隐式绑定执行函数并传递参数。
  // 删除临时属性,返回函数执行结果

  Function.prototype.myCall = function(context, ...arr) {
    if (context === null || context === undefined) {
      //这里如果context为0,false,’‘则上下文还是为window
      // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
      context = window
    } else {
      context = Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    //this指向需要借用的函数
    const specialPrototype = new Symbol('特殊属性Symbol') // 用于临时储存函数
    context[specialPrototype] = this // 函数的this指向隐式绑定到context上
    let result = context[specialPrototype](...arr) // 通过隐式绑定执行函数并传递参数
    delete context[specialPrototype] // 删除上下文对象的属性
    return result // 返回函数执行结果
  }

  // 传递给函数的参数处理,不太一样,其他部分跟call一样。
  // apply接受第二个参数为类数组对象, 这里用了JavaScript权威指南中判断是否为类数组对象的方法。

  Function.prototype.myApply = function(context) {
    if (context === null || context === undefined) {
      context = window // 指定为 null 和 undefined 的 this 值会自动指向全局对象(浏览器中为window)
    } else {
      context = new Object(context) // 值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的实例对象
    }
    // JavaScript权威指南判断是否为类数组对象
    function isArrayLike(o) {
      if (
        o && // o不是null、undefined等
        typeof o === 'object' && // o是对象
        isFinite(o.length) && // o.length是有限数值
        o.length >= 0 && // o.length为非负值
        o.length === Math.floor(o.length) && // o.length是整数
        o.length < 4294967296
      )
        // o.length < 2^32
        return true
      else return false
    }
    const specialPrototype = Symbol('特殊属性Symbol')
    context[specialPrototype] = this // 隐式绑定this指向到context上
    let args = arguments[1] // 获取参数数组
    let result
    // 处理传进来的第二个参数
    if (args) {
      // 是否传递第二个参数
      if (!Array.isArray(args) && !isArrayLike(args)) {
        throw new TypeError(
          'myApply 第二个参数不为数组并且不为类数组对象抛出错误'
        )
      } else {
        args = Array.from(args) // 转为数组
        result = context[specialPrototype](...args) // 执行函数并展开数组,传递函数参数
      }
    } else {
      result = context[specialPrototype]() // 执行函数
    }
    delete context[specialPrototype] // 删除上下文对象的属性
    return result // 返回函数执行结果
  }

  // 1.拷贝源函数:
  // 通过变量储存源函数
  // 使用Object.create复制源函数的prototype给fToBind

  // 2.返回拷贝的函数

  // 3.调用拷贝的函数:
  // new调用判断:通过instanceof判断函数是否通过new调用,来决定绑定的context
  // 绑定this+传递参数
  // 返回源函数的执行结果

  Function.prototype.myBind = function(objThis, ...params) {
    const thisFn = this // 存储源函数以及上方的params(函数参数),thisFn=>被绑定的函数
    // 对返回的函数 secondParams 二次传参
    let fToBind = function(...secondParams) {
      const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
      const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
      return thisFn.call(context, ...params, ...secondParams) // 用apply调用源函数绑定this的指向并传递参数,返回执行结果
    }
    fToBind.prototype = Object.create(thisFn.prototype) // 复制源函数的prototype给fToBind
    return fToBind // 返回拷贝的函数
  }

  var obj = {
    name: 'aaaaaaaa',
    getName() {
      console.log(this.name)
    }
  }

  function a(args) {
    console.log(this.name)
  }

  a.apply(obj, [1, 2, 3, 4])
  //   a._myApply(obj,[1,2,3,4]);
</script>

简单够用版Promise实现

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>简单够用版Promise实现</title>
</head>

<body>

</body>

</html>
<script>

  class MyPromise {
    constructor(fn) {
      if (typeof this !== 'object') {
        throw new TypeError('Promises must be constructed via new');
      }
      if (typeof fn !== 'function') {
        throw new TypeError('Promise constructor\'s argument is not a function');
      }
      this.doneList = [];
      this.failList = [];
      fn(this.resolve.bind(this), this.reject.bind(this));//把当前resolve,reject的this指向MyPromise
    }

    resolve() {
      let args = Array.prototype.slice.call(arguments);
      setTimeout(() => {
        this.doneList.forEach((item, key, arr) => {
          result = item.apply(null, args);
          arr.shift();
        });
      }, 0);
    }
    reject() {
      let args = Array.prototype.slice.call(arguments);
      this.failList.forEach((item, key, arr) => {
        setTimeout(() => {
          item.apply(null, args);
          arr.shift();
        }, 0);
      });
    }
    then(cb) {
      if (cb.constructor.name === 'Function') {
        this.doneList.push(cb);
        return this;//在同步执行时候返回this,转为链式
      } else if (cb.constructor.name === 'MyPromise') {
        this.doneList.push(cb)
        return cb;//在同步执行时候返回this,转为链式
      } else {
        throw new Error('缺少回调函数');
        return cb;//在同步执行时候返回this,转为链式
      }
    }
    fail(cb) {
      if (typeof cb === 'function') {
        this.failList.push(cb)
      } else {
        throw new Error('缺少回调函数');
      }
    }
  }
 </script>