• 前端面试题及答案整理(2022最新版)


    收集整理2022年最新前端面试题及答案,方便平时翻看记忆,欢迎各位大佬们补充。

    一般来说,把下面基础中的高频题写熟练就差不多了。当然去面大厂这些远远不够,还要再刷一些算法题

    基础

    高频

    1.手写 instanceof

    1. // 原理:验证当前类的原型prototype是否会出现在实例的原型链proto上,只要在它的原型链上,则结果都为true
    2. function myinstanceOf_(obj, class_name) {
    3. // let proto = obj.__proto__;
    4. let proto = Object.getPrototypeOf(obj)
    5. let prototype = class_name.prototype
    6. while (true) {
    7. if (proto == null) return false
    8. if (proto == prototype) return true
    9. // proto = proto.__proto__;
    10. proto = Object.getPrototypeOf(proto)
    11. }
    12. }

    ​2.手写 new 操作符

    1. function myNew(){
    2. //1.创建一个新的对象
    3. let obj=new Object();
    4. //获得构造函数
    5. let con = [].shift.call(arguments); //[]为Array构造函数的实例 将类数组转化为真正的数组
    6. //2.新对象的隐式原型__proto__链接到构造函数的显式原型prototype
    7. obj.__proto__ = con.prototype;
    8. //3.构造函数内部的 this 绑定到这个新创建的对象 执行构造函数
    9. let result = con.apply(obj, arguments)
    10. //4.如果构造函数没有返回非空对象,则返回创建的新对象
    11. return typeof result == 'object' ? result:obj;
    12. }
    13. var test_create = myNew(Car, 'a', 'b', 'c');
    14. console.log(test_create)

    3.手写 call、apply、bind 函数

    • call(thisArg, ...args)
    1. // 给函数的原型添加 _call 方法,使得所有函数都能调用 _call
    2. // thisArg 就是要绑定的那个this;...args 扩展操作符传参,适合不定长参数,args是一个数组
    3. Function.prototype._call = function(thisArg,...args){
    4. // 1.获取需要执行的函数
    5. fn = this
    6. // 2.将 thisArg 转成对象类型(防止它传入的是非对象类型,例如123数字)
    7. thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
    8. // 3.使用 thisArg 调用函数,绑定 this
    9. thisArg.fn = fn
    10. let result = thisArg.fn(...args)
    11. delete thisArg.fn
    12. // 4.返回结果
    13. return result
    14. }
    • apply(thisArg, argsArray)
    1. Function.prototype._apply = function(thisArg,argArray){
    2. // 1.获取需要执行的函数
    3. fn = this
    4. // 2.将 thisArg 转成对象类型(防止它传入的是非对象类型,例如123数字)
    5. thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
    6. // 判断一些边界情况
    7. argArray = argArray || []
    8. // 3.使用 thisArg 调用函数,绑定 this
    9. thisArg.fn = fn
    10. // 将传递过来的数组(可迭代对象)拆分,传给函数
    11. let result = thisArg.fn(...argArray)
    12. delete thisArg.fn
    13. // 4.返回结果
    14. return result
    15. }
    • bind(thisArg, ...args)
    1. Function.prototype._call = function(thisArg,...args){
    2. fn = this
    3. thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
    4. thisArg.fn = fn
    5. let result = thisArg.fn(...args)
    6. delete thisArg.fn
    7. return result
    8. }
    9. // 利用 call 模拟 bind
    10. Function.prototype._bind = function(thisArg,...args){
    11. let fn = this // 需要调用的那个函数的引用
    12. // bind 需要返回一个函数
    13. return function(){
    14. return fn._call(thisArg, ...args)
    15. }
    16. }

    4.手写深拷贝

    PS:浅拷贝也可以用一样的模板,当然深拷贝考得多

    1. function deepCopy(object) {
    2. if (!object || typeof object !== "object") return object;
    3. let newObject = Array.isArray(object) ? [] : {};
    4. for (let key in object) {
    5. if (object.hasOwnProperty(key)) {
    6. newObject[key] = deepCopy(object[key]);
    7. }
    8. }
    9. return newObject;
    10. }

    进阶:解决循环引用的深拷贝

    1. function deepClone(obj, hash = new WeakMap()) {
    2. if (!object || typeof object !== "object") return object;
    3. // 是对象的话就要进行深拷贝,遇到循环引用,将引用存储起来,如果存在就不再拷贝
    4. if (hash.get(obj)) return hash.get(obj);
    5. let cloneObj = Array.isArray(object) ? [] : {};
    6. hash.set(obj, cloneObj);
    7. for (let key in obj) {
    8. if (obj.hasOwnProperty(key)) {
    9. // 实现一个递归拷贝
    10. cloneObj[key] = deepClone(obj[key], hash);
    11. }
    12. }
    13. return cloneObj;
    14. }

    5.手写防抖节流

    1. function debounce(func, delay) {
    2. // 这里使用了闭包,所以 timer 不会轻易被销毁
    3. let timer = null
    4. // 生成一个新的函数并返回
    5. return function (...args) {
    6. // 清空定时器
    7. if (timer) {
    8. clearTimeout(timer)
    9. }
    10. // 重新启动定时器
    11. timer = setTimeout(() => {
    12. func.call(this, ...args)
    13. }, delay)
    14. }
    15. }
    16. function throttle(func, delay) {
    17. let timer = null
    18. // 在 delay 时间内,最多执行一次 func
    19. return function (...args) {
    20. if (!timer) {
    21. timer = setTimeout(() => {
    22. func.call(this, ...args)
    23. // 完成一次计时,清空,待下一次触发
    24. timer = null
    25. }, delay)
    26. }
    27. }
    28. }

    6.手写Ajax请求

    1. function ajax(url) {
    2. // 创建一个 XHR 对象
    3. return new Promise((resolve,reject) => {
    4. const xhr = new XMLHttpRequest()
    5. // 指定请求类型,请求URL,和是否异步
    6. xhr.open('GET', url, true)
    7. xhr.onreadystatechange = funtion() {
    8. // 表明数据已就绪
    9. if(xhr.readyState === 4) {
    10. if(xhr.status === 200){
    11. // 回调
    12. resolve(JSON.stringify(xhr.responseText))
    13. }
    14. else{
    15. reject('error')
    16. }
    17. }
    18. }
    19. // 发送定义好的请求
    20. xhr.send(null)
    21. })
    22. }

    7.手写数组去重

    1. // 1.Set + 数组复制
    2. fuction unique1(array){
    3. // Array.from(),对一个可迭代对象进行浅拷贝
    4. return Array.from(new Set(array))
    5. }
    6. // 2.Set + 扩展运算符浅拷贝
    7. function unique2(array){
    8. // ... 扩展运算符
    9. return [...new Set(array)]
    10. }
    11. // 3.filter,判断是不是首次出现,如果不是就过滤掉
    12. function unique3(array){
    13. return array.filter((item,index) => {
    14. return array.indexOf(item) === index
    15. })
    16. }
    17. // 4.创建一个新数组,如果之前没加入就加入
    18. function unique4(array){
    19. let res = []
    20. array.forEach(item => {
    21. if(res.indexOf(item) === -1){
    22. res.push(item)
    23. }
    24. })
    25. return res
    26. }

    进阶:如果数组内有数组和对象,应该怎么去重(此时对象的地址不同,用Set去不了重)

    需要开通正版 WebStorm 的可以联系我,56元一年,正版授权激活,官网可查有效期,有需要的加我微信:poxiaozhiai6,备注:915。

    8.手写数组扁平

    1. // 方法1-3:递归
    2. function flat1(array){
    3. // reduce(): 对数组的每一项执行归并函数,这个归并函数的返回值会作为下一次调用时的参数,即 preValue
    4. // concat(): 合并两个数组,并返回一个新数组
    5. return array.reduce((preValue,curItem) => {
    6. return preValue.concat(Array.isArray(curItem) ? flat1(curItem) : curItem)
    7. },[])
    8. }
    9. function flat2(array){
    10. let res = []
    11. array.forEach(item => {
    12. if(Array.isArray(item)){
    13. // res.push(...flat2(item))
    14. // 如果遇到一个数组,递归
    15. res = res.concat(flat2(item))
    16. }
    17. else{
    18. res.push(item)
    19. }
    20. })
    21. return res
    22. }
    23. function flat3(array){
    24. // some(): 对数组的每一项都运行传入的函数,如果有一项返回 TRUE,则这个方法返回 TRUE
    25. while(array.some(item => Array.isArray(item))){
    26. // ES6 增加了扩展运算符,用于取出参数对象的所有可遍历属性,拷贝到当前对象之中:
    27. array = [].concat(...array)
    28. console.log(...array)
    29. }
    30. return array
    31. }
    32. // 方法4、5:先转成字符串,再变回数组
    33. function flat4(array){
    34. //[1,[2,3]].toString() => 1,2,3
    35. return array.toString().split(',').map(item => parseInt(item))
    36. }
    37. function flat5(array){
    38. return array.join(',').split(',').map(item => Number(item))
    39. }

    9.手写数组乱序

    1. // 方法1: sort + Math.random()
    2. function shuffle1(arr){
    3. return arr.sort(() => Math.random() - 0.5);//
    4. }
    5. // 方法2:时间复杂度 O(n^2)
    6. // 随机拿出一个数(并在原数组中删除),放到新数组中
    7. function randomSortArray(arr) {
    8. let backArr = [];
    9. while (arr.length) {
    10. let index = parseInt(Math.random() * arr.length);
    11. backArr.push(arr[index]);
    12. arr.splice(index, 1);
    13. }
    14. return backArr;
    15. }
    16. // 方法3:时间复杂度 O(n)
    17. // 随机选一个放在最后,交换
    18. function randomSortArray2(arr) {
    19. let lenNum = arr.length - 1;
    20. for (let i = 0; i < lenNum; i++) {
    21. let index = parseInt(Math.random() * (lenNum + 1 - i));
    22. [a[index],a[lenNum - i]] = [a[lenNum - i],a[index]]
    23. }
    24. return arr;
    25. }

    10.手写 Promise.all()、Promise.race()

    PS: 有能力的可以去写下 Promise 和其他的 Promise 方法

    1. function myAll(promises){
    2. // 问题关键:什么时候要执行resolve,什么时候要执行 reject
    3. return new Promise((resolve,reject) => {
    4. values = []
    5. // 迭代数组中的 Promise,将每个 promise 的结果保存到一个数组里
    6. promises.forEach(promise => {
    7. // 如果不是 Promise 类型要先包装一下
    8. // 调用 then 得到结果
    9. Promise.resolve(promise).then(res => {
    10. values.push(res)
    11. // 如果全部成功,状态变为 fulfilled
    12. if(values.length === promises.length){
    13. resolve(values)
    14. }
    15. },err => { // 如果出现了 rejected 状态,则调用 reject() 返回结果
    16. reject(err)
    17. })
    18. })
    19. }
    20. )
    21. }

    11.手撕快排

    PS: 常见的排序算法,像冒泡,选择,插入排序这些最好也背一下,堆排序归并排序能写则写。万一考到了呢,要是写不出就直接回去等通知了。

    1. const _quickSort = array => {
    2. // 补全代码
    3. quickSort(array, 0, array.length - 1)
    4. // 别忘了返回数组
    5. return array
    6. }
    7. const quickSort = (array, start, end) => {
    8. // 注意递归边界条件
    9. if(end - start < 1) return
    10. // 取第一个数作为基准
    11. const base = array[start]
    12. let left = start
    13. let right = end
    14. while(left < right){
    15. // 从右往左找小于基准元素的数,并赋值给右指针 array[right]
    16. while(left < right && array[right] >= base) right--
    17. array[left] = array[right]
    18. // 从左往右找大于基准元素的数,并赋值给左指针 array[left]
    19. while(left < right && array[left] <= base) left++
    20. array[right] = array[left]
    21. }
    22. // 双指针重合处,将基准元素填到这个位置。基准元素已经事先保存下来了,因此不用担心上面的赋值操作会覆盖掉基准元素的值
    23. // array[left] 位置已经确定,左边的都比它小,右边的都比它大
    24. array[left] = base
    25. quickSort(array, start, left - 1)
    26. quickSort(array, left + 1, end)
    27. return array
    28. }

    12.手写 JSONP

    1. // 动态的加载js文件
    2. function addScript(src) {
    3. const script = document.createElement('script');
    4. script.src = src;
    5. script.type = "text/javascript";
    6. document.body.appendChild(script);
    7. }
    8. addScript("http://xxx.xxx.com/xxx.js?callback=handleRes");
    9. // 设置一个全局的callback函数来接收回调结果
    10. function handleRes(res) {
    11. console.log(res);
    12. }
    13. // 接口返回的数据格式,加载完js脚本后会自动执行回调函数
    14. handleRes({a: 1, b: 2});

    13.手写寄生组合继承

    PS: 组合继承也要能写出来

    1. function Parent(name) {
    2. this.name = name;
    3. this.say = () => {
    4. console.log(111);
    5. };
    6. }
    7. Parent.prototype.play = () => {
    8. console.log(222);
    9. };
    10. function Children(name,age) {
    11. Parent.call(this,name);
    12. this.age = age
    13. }
    14. Children.prototype = Object.create(Parent.prototype);
    15. Children.prototype.constructor = Children;
    16. // let child = new Children("111");
    17. // // console.log(child.name);
    18. // // child.say();
    19. // // child.play();

    14.数组/字符串操作题

    可以自己找些基础的练一下,就不一一列举了

    15.手写​二分查找​

    1. // 迭代版
    2. function search(nums, target) {
    3. // write code here
    4. if(nums.length === 0) return -1
    5. let left = 0,right = nums.length - 1
    6. // 注意这里的边界,有等号
    7. while(left <= right){
    8. let mid = Math.floor((left + right) / 2)
    9. if(nums[mid] < target) left = mid + 1
    10. else if(nums[mid] > target) right = mid - 1
    11. else return mid
    12. }
    13. return -1
    14. }
    15. // 递归版
    16. function binary_search(arr, low, high, key) {
    17. if (low > high) {
    18. return -1;
    19. }
    20. var mid = parseInt((high + low) / 2);
    21. if (arr[mid] == key) {
    22. return mid;
    23. } else if (arr[mid] > key) {
    24. high = mid - 1;
    25. return binary_search(arr, low, high, key);
    26. } else if (arr[mid] < key) {
    27. low = mid + 1;
    28. return binary_search(arr, low, high, key);
    29. }
    30. };

    16.手写函数柯里化

    1. function sum(x,y,z) {
    2. return x + y + z
    3. }
    4. function hyCurrying(fn) {
    5. // 判断当前已经接收的参数的个数,和函数本身需要接收的参数是否一致
    6. function curried(...args) {
    7. // 1.当已经传入的参数 大于等于 需要的参数时,就执行函数
    8. if(args.length >= fn.length){
    9. // 如果调用函数时指定了this,要将其绑定上去
    10. return fn.apply(this, args)
    11. }
    12. else{
    13. // 没有达到个数时,需要返回一个新的函数,继续来接收参数
    14. return function(...args2) {
    15. //return curried.apply(this, [...args, ...args2])
    16. // 接收到参数后,需要递归调用 curried 来检查函数的个数是否达到
    17. return curried.apply(this, args.concat(args2))
    18. }
    19. }
    20. }
    21. return curried
    22. }
    23. var curryAdd = hyCurry(add1)
    24. curryAdd(10,20,30)
    25. curryAdd(10,20)(30)
    26. curryAdd(10)(20)(30)

    其他

    1.手写事件委托

    2.手写组合函数

    3.常见DOM操作

    4.手写数组常见方法 Array.filter/map/fill/reduce

    5.手写Object.create()

    6.手写Object.is()

    7.手写Object.freeze()

    8.手写列表转树

    9.数字千分位分割

    10.下划线转驼峰

    11.大数相加

    场景模拟题

    高频

    1.实现 sleep 函数

    1. async function test() {
    2. console.log('开始')
    3. await sleep(4000)
    4. console.log('结束')
    5. }
    6. function sleep(ms) {
    7. return new Promise(resolve => {
    8. setTimeout(() => {
    9. resolve()
    10. }, ms)
    11. })
    12. }
    13. test()

    2.setTimeout 实现 setInterval

    1. function setInterval(fn, time){
    2. var interval = function(){
    3. // time时间过去,这个异步被执行,而内部执行的函数正是interval,就相当于进了一个循环
    4. setTimeout(interval, time);
    5. // 同步代码
    6. fn();
    7. }
    8. //interval被延迟time时间执行
    9. setTimeout(interval,time);
    10. }

    3.异步循环打印 1,2,3

    1. var sleep = function (time, i) {
    2. return new Promise(function (resolve, reject) {
    3. setTimeout(function () {
    4. resolve(i);
    5. }, time);
    6. })
    7. };
    8. var start = async function () {
    9. for (let i = 1; i <= 3; i++) {
    10. let result = await sleep(1000, i);
    11. console.log(result);
    12. }
    13. };
    14. start();

    4.循环打印红、黄、绿

    1. function red() {
    2. console.log('red');
    3. }
    4. function green() {
    5. console.log('green');
    6. }
    7. function yellow() {
    8. console.log('yellow');
    9. }
    10. const task = (timer, light) => {
    11. new Promise((resolve, reject) => {
    12. setTimeout(() => {
    13. if (light === 'red') {
    14. red()
    15. }
    16. else if (light === 'green') {
    17. green()
    18. }
    19. else if (light === 'yellow') {
    20. yellow()
    21. }
    22. resolve()
    23. }, timer)
    24. })
    25. }
    26. const taskRunner = async () => {
    27. await task(3000, 'red')
    28. await task(2000, 'green')
    29. await task(2100, 'yellow')
    30. taskRunner()
    31. }
    32. taskRunner()

    其他

    Promise 并发控制相关的题目,例如实现有并行限制的 Promise 调度器

    更多 Promise 的面试题在这里:要就来45道Promise面试题一次爽到底,面大厂的兄弟可以看看

    进阶

    1.手写 Promise(重要)

    2.手写发布-订阅模式

    3.手写观察者模式

    4.手写双向绑定

    5.手写 Event Bus

    6.手写 LRU

  • 相关阅读:
    【代码审计-PHP】phpStudy(新版) + PhpStorm + XDebug动态调试
    机器学习西瓜书+南瓜书吃瓜教程第三章学习笔记
    DES & 3DES 简介 以及 C# 和 js 实现【加密知多少系列】
    记录--编写一个能运行的简易ko
    esp32 gitignore文件
    C Primer Plus(6) 中文版 第13章 文件输入/输出 13.3 一个简单的文件压缩程序
    Codesys结构变量编程应用(STRUCT类型)
    代码随想录二刷 Day41
    3D可视化工厂是如何实现的?
    LeetCode2369. Check if There is a Valid Partition For The Array——动态规划
  • 原文地址:https://blog.csdn.net/qq_38140936/article/details/126866599