0


前端面试题及答案整理(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

标签: javascript html5 css

本文转载自: https://blog.csdn.net/qq_38140936/article/details/126866599
版权归原作者 程序员·小季 所有, 如有侵权,请联系我们删除。

“前端面试题及答案整理(2022最新版)”的评论:

还没有评论