2022 Web 前端面试题及答案之JavaScript 篇

开发 前端
给大家分享一篇面试相关文章,希望大家在 2022 年,摸鱼时间越来越多,薪资越涨越快!

给大家分享一篇面试相关文章,希望大家在 2022 年,摸鱼时间越来越多,薪资越涨越快!

1、事件循环机制

阿里面试题1:

  1. <script type="text/javascript"
  2.  var p =new Promise(resolve=>{ 
  3.   console.log(4) 
  4.   resolve(5) 
  5.  }) 
  6.  function f1(){ 
  7.   console.log(1) 
  8.  } 
  9.  function f2(){ 
  10.   setTimeout(()=>{ 
  11.    console.log(2) 
  12.   },0) 
  13.   f1() 
  14.   console.log(3) 
  15.   p.then(res=>{ 
  16.    console.log(res) 
  17.   }) 
  18.  } 
  19.  f2() 
  20. </script> 
  21. // 运行结果 4 1 3 5 2 
  22. // 如果已经了解事件运行机制,就可以跳过该问题了 

 事件循环机制,event-loop 。包含三部分:调用栈、消息队列、微任务队列。

事件循环开始的时候,会从全局一行一行的执行代码,遇到函数调用的时候,就会压入调用栈中,当函数执行完成之后,弹出调用栈。

  1. // 如:代码会一行一行执行,函数全部调用完成之后清空调用栈 
  2. function f1(){ 
  3.  console.log(1) 
  4. function f2(){ 
  5.  f1() 
  6.  console.log(2) 
  7. f2() 
  8. // 执行结果 1 2 

 如果遇到 fetch、setInterval、setTimeout 异步操作时,函数调用压入调用栈时,异步执行内容会被加入消息队列中,消息队列中的内容会等到调用栈清空之后才会执行。

  1. // 如: 
  2. function f1(){ 
  3.  console.log(1) 
  4. function f2(){ 
  5.  setTimeout(()=>{ 
  6.   console.log(2) 
  7.  },0) 
  8.  f1() 
  9.  console.log(3) 
  10. f2() 
  11. // 执行结果 :1 3 2 

 遇到 promise、async、await 异步操作时,执行内容会被加入微任务队列中,会在调用栈清空之后立即执行。

调用栈加入的微任务队列会立即执行。

  1. 如 
  2. let p =new Promise(resolve=>{ 
  3.  console.log('立即执行'
  4.  resolve(1) //在 then 调用中执行 
  5. }) 

 微任务队列中内容优先执行,所以比消息队列中的内容执行得早。

了解这些知识后,再试一下最前面的那道面试题,应该就没什么问题了。

2、你对作用域的认识有多少?

阿里面试题2:

  1. <script type="text/javascript"
  2.  function fn(a,c){ 
  3.   console.log(a) 
  4.   var a = 12 
  5.   console.log(a) 
  6.   console.log(c) 
  7.   function a(){ } 
  8.   if(false){ 
  9.    var d = 34 
  10.   } 
  11.   console.log(d) 
  12.   console.log(b) 
  13.   var b = function(){} 
  14.   console.log(b) 
  15.   function c(){} 
  16.   console.log(c) 
  17.  } 
  18.  fn(1,2) 
  19. </script> 
  20. // 运行结果: 
  21. /* 
  22. function a(){} 
  23. 12 
  24. function c(){} 
  25. undefined 
  26. undefined 
  27. function (){} 
  28. function c(){} 
  29. */ 

 作用域通俗地讲,就是指一个变量的作用范围。下面分别介绍下全局作用域和函数作用域的概念。

全局作用域

  • 页面打开时被创建,页面关闭时被销毁。
  • 编写在 script 标签下的变量和函数,作用域为全局,页面的任意位置都可以访问
  • 有全局对象 window ,代表浏览器窗口,全局作用下的变量和函数作为 window 的属性和方法

函数作用域(局部)

  • 函数是被调用时创建的,执行完毕之后销毁。
  • 函数每调用一次,变量和函数就会重新创建一次,它们之间是相互独立的
  • 在函数作用域内可以访问到全局变量或函数,但是在函数外无法访问函数作用域内的变量
  • 函数作用域内访问变量,会在自身作用域内寻找,若没有则会向上一级作用域内查找,一直到全局作用域。

讲这些概念看完,发现还不会做上边的面试题,接下来就学习学习作用域的预编译,看看函数执行的时候都干了些啥?

函数在被调用的时候会先进行预编译:

全局作用域预编译:

  • 创建上下文 GO 对象。
  • 找变量声明,将变量名作为 GO 对象的属性名,值为 undefined
  • 找函数式声明,将值赋予函数体

函数作用域预编译:

  • 创建上下文 AO 对象
  • 将形参和实参作为 AO 对象的属性,赋值为 undefined
  • 实参和形参相统一
  • 在函数体内找函数声明,将值赋予函数体。

了解预编译过程之后,我们将上面的面试题进行解析,分析下运行结果是怎么来的?

fn 函数调用的时候,先进行预编译,

第一阶段:生成一个 AO 对象

第二阶段:找到形参和实参,作为 AO 对象的属性名,值为 udefined 。

  1. AO{ 
  2. a : undefined, 
  3. b : undefined, 
  4. c : undefined, 
  5. d : undefined 

 第三阶段:实参和形参相统一,之后,AO对象改变为:

  1. AO{ 
  2. a : 1, 
  3. b : undefined, 
  4. c : 2, 
  5. d : undefined 

 第四阶段:找到函数声明,将值赋给变量,AO改变为:

  1. AO{ 
  2. a : function a(){ } , 
  3. b : undefined, 
  4. c : function c(){ }, 
  5. d : undefined 

 这下结合函数的预编译过程以及函数作用域概念,再尝试一下面试题,简单了吗?

3、为什么会有闭包?它解决了什么问题?

实例3:

  1. var liArr = document.getElementsByTagName('li'
  2. for(var i=0;i<liArr.length;i++){ 
  3.  liArr[i].onclick = function(){ 
  4.   console.log(liArr[i]) 
  5.  } 

 这是一个非常常见的实际应用,我们是想要点击元素然后操作对应的元素,但是点击之后发现打印出来的是 undefined 。我们应该能想到 i 变成了 liArr.length ,所以找不到对应元素,这个问题该如何解决呢?

说闭包时,必须介绍作用域。

上面介绍全局作用域和函数作用域,js内部变量的访问是由内向外的,内部可以访问到外部的变量,但是外部无法访问函数内的变量,如果我们在外部访问函数内的变量就需要使用闭包。

闭包就是函数嵌套函数,通过函数内的函数访问变量的规则,实现外部访问函数内的变量。

闭包的特点:

  • 函数嵌套函数。
  • 函数内部可以引用函数外部的参数和变量。
  • 参数和变量不会被垃圾回收机制回收。

那么上述实例该如何使用闭包解决该问题呢?

实例3:闭包解决问题

  1. var liArr = document.getElementsByTagName('li'
  2. for(var i=0;i<liArr.length;i++){ 
  3.  (function(i){ 
  4.   liArr[i].onclick = function(){ 
  5.    console.log('点击元素',liArr[i]) 
  6.   } 
  7.  })(i)  

 闭包优点:

  • 保护变量安全,实现封装,防止变量声明冲突和全局污染。
  • 在内存当中维持一个变量,可以做缓存。
  • 匿名函数自执行函数可以减少内存消耗。

防抖和节流就是闭包的经典应用。

4、防抖和节流,你了解多少?

在实际应用中,常见的就是窗口的 resize、输入框搜索内容、scroll 等操作,如果这些操作触发频率太高,就会加重浏览器的负担,同时用户体验也较差。该如何优化该操作呢?

防抖函数是什么呢?

当持续触发事件,一定时间内没有再触发事件,事件处理函数才会执行一次,如果在设定的时间到来之前又触发了事件,就会重新计时。

实例4:我们想要制作一个输入框搜索,计划输入完成后两秒再执行,打印出输入的值。

  1. function debounce(val){ 
  2.  var timer 
  3.  clearTimeout(timer) 
  4.  timer = setTimeout(function(){ 
  5.   console.log(val) 
  6.  },2000) 
  7. var input = document.getElementById('input'
  8. input.addEventListener('keyup',function(e){ 
  9.  debounce(e.target.value) 
  10. }) 

 实际运行结果:我们发现输入之后,延时两秒之后打印出结果。

2022 Web 前端面试题及答案 之 javaScript 篇

并非我们想要的结果,这是什么原因呢?

因为函数每次重新调用的时候 timer 会重新创建,调用完成之后就会被销毁,所以每次重新调用函数的时候,clearTimeout 内的 timer 都是 undefined 。所以我们需要把 timer 始终保持在内存当中,所以就需要使用闭包。

使用闭包修改上述实例4:

  1. function debounce(delay){ 
  2.  var timer 
  3.  return function(val){ 
  4.   clearTimeout(timer) 
  5.   timer = setTimeout(function(){ 
  6.    console.log(val) 
  7.   },delay) 
  8.  } 
  9. var debounceFun = debounce(2000) 
  10. var input = document.getElementById('input'
  11. input.addEventListener('keyup',function(e){ 
  12.  debounceFun(e.target.value) 
  13. }) 

防抖函数常见的实际应用:使用 echart 的时候,浏览器 resize 时,需要重新绘制图表大小,还有典型的输入框搜索应用。

节流函数是什么?

当持续触发事件的时候,保证一段时间内只调用一次事件处理函数,一段时间内,只允许做一件事情。

实例5:滚动条实现一段时间内执行一次处理,执行回调。

  1. var throttle = function(func, delay) {             
  2.  var timer = null;             
  3.  return function() {                 
  4.   var context = this;                
  5.   var args = arguments;                 
  6.   if (!timer) {                     
  7.    timer = setTimeout(function() {                         
  8.     func.apply(context, args);                         
  9.      timer = null;                     
  10.     }, delay);                 
  11.    }             
  12.  }         
  13. }         
  14. function handle() {             
  15.  console.log('执行回调');         
  16. }         
  17. window.addEventListener('scroll', throttle(handle, 1000));  

防抖和节流主要是用来限制触发频率较高的事件,再不影响效果的前提条件下,降低事件触发频率,减小浏览器或服务器的压力,提升用户体验效果。

5、数组去重有几种方法?

这是一个非常常见的面试题,你知道几种方式呢?

  1. var arr = [1,2,3,4,5,1,2,3,4] 
  2. function unique(arr){ 
  3.   //添加去重的方法的内容 
  4. unique(arr) 

方法1: Set 方法

  1. return Array.from(new Set(arr)) 
  2.  
  3. // 或 
  4.  
  5. return [...new Set(arr)] 

 new Set 返回的数据不是数组,所以使用 Aray.from 方法将类数组转为真正的数组,或把 ...new Set(arr) 放入数组中。

方法2:使用两次循环

  1. for(var i=0,len=arr.length;i<len;i++){ 
  2.  for(var j=i+1,len=arr.length;j<len;j++){ 
  3.   if( arr[i]===arr[j] ){ 
  4.    arr.splice(i,1) 
  5.    j--; 
  6.    len-- 
  7.   } 
  8.  } 
  9. return arr 

方法3:indexOf 实现

arr.indexOf(item) 返回 item 元素在 arr 数组中第一次出现所在位置的下标。

  1. let arr1 = [] 
  2. for(var i=0;i<arr.length;i++){ 
  3.  if( arr1.indexOf(arr[i]) === -1 ){ 
  4.   arr1.push(arr[i]) 
  5.  } 
  6. return arr1 

方法4:includes 实现

  1. let arr1 = [] 
  2. for(var i=0;i<arr.length;i++){ 
  3.  if( !arr1.includes(arr[i]) ){ 
  4.   arr1.push(arr[i]) 
  5.  } 
  6. return arr1 

方法5:filter 实现

array.indexOf(item,start) start 表示开始检索的位置。

  1. return arr.filter(( item, index )=>{ 
  2.  return arr.indexOf( item, 0 ) == index 
  3. }) 

 

责任编辑:姜华 来源: 今日头条
相关推荐

2022-07-27 08:27:34

Call前端

2021-02-02 06:12:39

JavaScript 前端面试题

2023-08-27 15:57:28

前端开发

2022-05-08 19:36:35

Web前端时间复杂度

2022-02-09 07:40:42

JavaScript前端面试题

2019-02-21 14:12:26

前端面试题Vue

2023-05-19 08:21:40

MarginCSS

2012-06-28 14:35:49

Web

2012-06-26 11:09:07

Web

2024-02-26 15:35:44

2009-02-16 13:03:43

华为面试

2009-06-16 13:41:19

Hibernate面试Hibernate面试

2020-11-12 10:20:40

前端面试web

2022-05-08 19:58:10

JSONPJavaScript

2021-10-27 11:00:30

C++语言面试

2020-11-06 09:05:18

前端web开发

2019-05-15 16:45:13

SpringBoot面试题Java

2018-03-08 18:40:47

Java百度面试题

2022-07-08 08:21:26

JSbind 方法

2012-02-02 09:45:24

Web
点赞
收藏

51CTO技术栈公众号