常见手写前端面试题,看看自己能写出来多少

简介: 手写前端面试题,面试必备
  • 每到跳槽的时候,总会遇到一些公司让手写几道面试题,从而看你对JavaScript掌握的程度

实现防抖函数

  • 防抖函数原理:在事件被触发n秒后再执⾏回调,如果在这n秒内⼜被触发,则重新计时。
  • 使用场景: 防⽌多次提交按钮,只执⾏最后提交的⼀次,表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似

      const debounce = (fn, delay) => {
        let timer = null;
        return (...args) => {
          clearTimeout(timer);
          timer = setTimeout(() => {
            fn.apply(this, args);
          }, delay);
        };
      };

实现节流函数

  • 节流函数原理:规定在⼀个单位时间内,只能触发⼀次函数。如果这个单位时间内触发多次函数,只有⼀次⽣效。
  • 适⽤场景: 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动,缩放场景:监控浏览器resize.

    const throttle = (fn, delay = 500) => {
      let flag = true;
      return (...args) => {
        if (!flag) return;
        flag = false;
        setTimeout(() => {
          fn.apply(this, args);
          flag = true;
        }, delay);
      };
    };
    

深克隆

  • 简易版

    1. 他⽆法实现对函数 、RegExp等特殊对象的克隆
    2. 会抛弃对象的constructor,所有的构造函数会指向Object
    3. 对象有循环引⽤,会报错
      const newObj = JSON.parse(JSON.stringify(oldObj));
  • 最终版本

    1. ⼀些特殊情况没有处理: 例如Buffer对象、Promise、Set、Map
      const clone = (parent) => {
        const isType = (obj, type) => {
          if (typeof obj !== 'object') return false;
          const typeString = Object.prototype.toString.call(obj);
          let flag;
          switch (type) {
            case 'Array':
              flag = typeString === '[object Array]';
              break;
            case 'Date':
              flag = typeString === '[object Date]';
              break;
            case 'RegExp':
              flag = typeString === '[object RegExp]';
              break;
            default:
              flag = false;
          }
          return flag;
        };
        // 处理正则
        const getRegExp = (re) => {
          var flags = '';
          if (re.global) flags += 'g';
          if (re.ignoreCase) flags += 'i';
          if (re.multiline) flags += 'm';
          return flags;
        };
        // 维护两个储存循环引⽤的数组
        const parents = [];
        const children = [];
        const _clone = (parent) => {
          if (parent === null) return null;
          if (typeof parent !== 'object') return parent;
          let child, proto;
          if (isType(parent, 'Array')) {
            // 对数组做特殊处理
            child = [];
          } else if (isType(parent, 'RegExp')) {
            // 对正则对象做特殊处理
            child = new RegExp(parent.source, getRegExp(parent));
            if (parent.lastIndex) child.lastIndex = parent.lastIndex;
          } else if (isType(parent, 'Date')) {
            // 对Date对象做特殊处理
            child = new Date(parent.getTime());
          } else {
            // 处理对象原型
            proto = Object.getPrototypeOf(parent);
            // 利⽤Object.create切断原型链
            child = Object.create(proto);
          }
          // 处理循环引⽤
          const index = parents.indexOf(parent);
          if (index != -1) {
            // 如果⽗数组存在本对象,说明之前已经被引⽤过,直接返回此对象
            return children[index];
          }
          parents.push(parent);
          children.push(child);
          for (let i in parent) {
            // 递 归
            child[i] = _clone(parent[i]);
          }
          return child;
        };
        return _clone(parent);
      };
    

实现Event

  class EventEmeitter {
      constructor() {
        this._events = this._events || new Map(); // 储存事件/回调键值对
        this._maxListeners = this._maxListeners || 10; // 设⽴监听上限
      }
  }
  // 触发名为type的事件
  EventEmeitter.prototype.emit = function (type, ...args) {
    let handler;
    // 从储存事件键值对的this._events中获取对应事件回调函数
    handler = this._events.get(type);
    if (args.length > 0) {
      handler.apply(this, args);
    } else {
      handler.call(this);
    }
    return true; JS笔试题
    134
  };
  // 监听名为type的事件
  EventEmeitter.prototype.addListener = function (type, fn) {
    // 将type事件以及对应的fn函数放⼊this._events中储存
    if (!this._events.get(type)) {
      this._events.set(type, fn);
    }
  };
  // 触发名为type的事件
  EventEmeitter.prototype.emit = function (type, ...args) {
    let handler;
    handler = this._events.get(type);
    if (Array.isArray(handler)) {
      // 如果是⼀个数组说明有多个监听者,需要依次此触发⾥⾯的函数
      for (let i = 0; i < handler.length; i++) {
        if (args.length > 0) {
          handler[i].apply(this, args);
        } else {
          handler[i].call(this);
        }
      }
    } else {
      // 单个函数的情况我们直接触发即可
      if (args.length > 0) {
        handler.apply(this, args);
      } else {
        handler.call(this);
      }
    }
    return true;
  };
  // 监听名为type的事件
  EventEmeitter.prototype.addListener = function (type, fn) {
    const handler = this._events.get(type); // 获取对应事件名称的函数清单
    if (!handler) {
      this._events.set(type, fn);
    } else if (handler && typeof handler === "function") {
      // 如果handler是函数说明只有⼀个监听者
      this._events.set(type, [handler, fn]); // 多个监听者我们需要⽤数组储存
    } else {
      handler.push(fn); // 已经有多个监听者,那么直接往数组⾥push函数即可
    }
  };
  EventEmeitter.prototype.removeListener = function (type, fn) {
    const handler = this._events.get(type); // 获取对应事件名称的函数清
    // 如果是函数,说明只被监听了⼀次
    if (handler && typeof handler === "function") {
      this._events.delete(type, fn);
    } else {
      let postion;
      // 如果handler是数组,说明被监听多次要找到对应的函数
      for (let i = 0; i < handler.length; i++) {
        if (handler[i] === fn) {
          postion = i;
        } else {
          postion = -
          1;
        }
      }
      // 如果找到匹配的函数,从数组中清除
      if (postion !== -1) {
        // 找到数组对应的位置,直接清除此回调
        handler.splice(postion, 1);
        // 如果清除后只有⼀个函数,那么取消数组,以函数形式保存JS笔试题
        135
        if (handler.length === 1) {
          this._events.set(type,
            handler[0]);
        }
      } else {
        return
        this;
      }
    }
  }

实现instanceOf

  // 模 拟 instanceof
  function instance_of(L, R) {
    //L 表示左表达式,R 表示右表达式
    var O = R.prototype; // 取 R 的显示原型
    L = L.proto; // 取 L 的隐式原型
    while (true) {
      if (L === null) return false;
      if (O === L)
        // 这⾥重点:当 O 严格等于 L 时,返回 true
        return true;
      L = L.proto;
    }
  }

模拟new

  • new操作符做了这些事:

    • 它创建了⼀个全新的对象
    • 它会被执⾏[[Prototype]](也就是proto)链接
    • 它使this指向新创建的对象
    • 通过new创建的每个对象将最终被[[Prototype]]链接到这个函数的prototype对象上
    • 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调⽤将返回该对象引⽤
    // objectFactory(name, 'cxk', '18')
      function objectFactory() {
        const obj = new Object();
        const Constructor = [].shift.call(arguments);
        obj. proto = Constructor.prototype;
        const ret = Constructor.apply(obj, arguments);
        return typeof ret === "object" ? ret : obj;
      }

实现⼀个call

  • 将函数设为对象的属性
  • 指定this到函数并传⼊给定参数执⾏函数
  • 如果不传⼊参数,默认指向为 window

      Function.prototype.myCall = function (context) {
        //此处没有考虑context⾮object情况
        context.fn = this;
        let args = [];
        for (let i = 1, len = arguments.length; i < len; i++) {
          args.push(arguments[i]);
        }
        context.fn(...args);
        let result = context.fn(...args);
        delete context.fn;
        return result;
      };
    

实现apply⽅法

  • apply原理与call很相似

      Function.prototype.myapply = function (context, arr) {
        var context = Object(context) || window;
        context.fn = this;
        var result;
        if (!arr) {
          result = context.fn();
        } else {
          var args = [];
          for (var i = 0, len = arr.length; i < len; i++) {
            args.push("arr[" + i + "]");
          }
          result = eval("context.fn(" + args + ")");
        }
        delete context.fn;
        return result;
      };

实现bind

  • 返回⼀个函数,绑定this,传递预置参数
  • bind返回的函数可以作为构造函数使⽤。故作为构造函数时应使得this失效,但是传⼊的参数依然有效

      if (!Function.prototype.bind) {
        Function.prototype.bind = function (oThis) {
          if (typeof this !== 'function') {
            throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
          } 
          var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () { }, fBound = function () {
              return fToBind.apply(this instanceof fBound
                ? this
                : oThis,
                aArgs.concat(Array.prototype.slice.call(arguments)));
            };
          if (this.prototype) {
            fNOP.prototype = this.prototype;
          }
          fBound.prototype = new fNOP();
          return fBound;
        };
      }

模拟Object.create

  • Object.create()⽅法创建⼀个新对象,使⽤现有的对象来提供新创建的对象的proto。
  function create (proto) {
    function F () { }
    F.prototype = proto;
    return new F();
  }

实现类的继承

function Parent (name) {
  this.parent = name
}
Parent.prototype.say = function () {
  console.log(`${this.parent}: 你打篮球的样⼦像kunkun`)
}
function Child (name, parent) {
  // 将⽗类的构造函数绑定在⼦类上
  Parent.call(this, parent)
  this.child = name
}
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function () {
  console.log(`${this.parent}好,我是练习时⻓两年半的${this.child}`);
}
// 注意记得把⼦类的构造指向⼦类本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打篮球的样⼦像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是练习时⻓两年半的cxk

实现JSON.parse

var json = '{"name":"cxk", "age":25}';
var obj = eval("(" + json + ")");

实现Promise

var PromisePolyfill = (function () {
  // 和reject不同的是resolve需要尝试展开thenable对象
  function tryToResolve (value) {
    if (this === value) {
      throw TypeError('Chaining cycle detected for promise!')
    }
    if (value !== null &&
      (typeof value === 'object' || typeof value === 'function')) {
      try {
        var then = value.then
        var thenAlreadyCalledOrThrow = false
        if (typeof then === 'function') {
          then.bind(value)(
            function (value2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              tryToResolve.bind(this, value2)()
            }.bind(this),
            function (reason2) {
              if (thenAlreadyCalledOrThrow) return
              thenAlreadyCalledOrThrow = true
              resolveOrReject.bind(this, 'rejected', reason2)()
            }.bind(this)
          )
        } else {
          resolveOrReject.bind(this, 'resolved', value)()
        }
      } catch (e) {
        if (thenAlreadyCalledOrThrow) return
        thenAlreadyCalledOrThrow = true
        resolveOrReject.bind(this, 'rejected', e)()
      }
    } else {
      resolveOrReject.bind(this, 'resolved', value)()
    }
  }
  function resolveOrReject (status, data) {
    if (this.status !== 'pending') return
    this.status = status
    this.data = data
    if (status === 'resolved') {
      for (var i = 0; i < this.resolveList.length; ++i) {
        this.resolveList[i]()
      }
    } else {
      for (i = 0; i < this.rejectList.length; ++i) {
        this.rejectList[i]()
      }
    }
  }
  function Promise (executor) {
    if (!(this instanceof Promise)) {
      throw Error('Promise can not be called without new !')
    }
    if (typeof executor !== 'function') {
      throw TypeError('Promise resolver ' + executor + ' is not a function')
    }
    this.status = 'pending'
    this.resolveList = []
    this.rejectList = []
    try {
      executor(tryToResolve.bind(this), resolveOrReject.bind(this, 'rejected'))
    } catch (e) {
      resolveOrReject.bind(this, 'rejected', e)()
    }
  }
  Promise.prototype.then = function (onFullfilled, onRejected) {
    if (typeof onFullfilled !== 'function') {
      onFullfilled = function (data) {
        return data
      }
    }
    if (typeof onRejected !== 'function') {
      onRejected = function (reason) {
        throw reason
      }
    }
    var executor = function (resolve, reject) {
      setTimeout(function () {
        try {
          var value = this.status === 'resolved'
            ? onFullfilled(this.data)
            : onRejected(this.data)
          resolve(value)
        } catch (e) {
          reject(e)
        }
      }.bind(this))
    }
    if (this.status !== 'pending') {
      return new Promise(executor.bind(this))
    } else {
      // pending
      return new Promise(function (resolve, reject) {
        this.resolveList.push(executor.bind(this, resolve, reject))
        this.rejectList.push(executor.bind(this, resolve, reject))
      }.bind(this))
    }
  }
  // for prmise A+ test
  Promise.deferred = Promise.defer = function () {
    var dfd = {}
    dfd.promise = new Promise(function (resolve, reject) {
      dfd.resolve = resolve
      dfd.reject = reject
    })
    return dfd
  }
  // for prmise A+ test
  if (typeof module !== 'undefined') {
    module.exports = Promise
  }
  return Promise
})()
PromisePolyfill.all = function (promises) {
  return new Promise((resolve, reject) => {
    const result = []
    let cnt = 0
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(value => {
        cnt++
        result[i] = value
        if (cnt === promises.length) resolve(result)
      }, reject)
    }
  })
}
PromisePolyfill.race = function (promises) {
  return new Promise((resolve, reject) => {
    for (let i = 0; i < promises.length; ++i) {
      promises[i].then(resolve, reject)
    }
  })
}
目录
相关文章
|
3月前
|
缓存 前端开发 中间件
[go 面试] 前端请求到后端API的中间件流程解析
[go 面试] 前端请求到后端API的中间件流程解析
|
21天前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
55 1
|
2月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
3月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
1月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
52 2
|
3月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
39 0
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
3月前
|
存储 JavaScript 前端开发
|
3月前
|
Web App开发 存储 缓存