前端常见面试题

  • A+
所属分类:Web前端
摘要

题目要求很明确,要求最终输出“searchText1”,并且在500ms后输出“searchText7”,很明显就是关于同一个函数在短时间内重复调用,如何限制其调用频率的功能实现。


1.关于防抖/节流

题目要求很明确,要求最终输出“searchText1”,并且在500ms后输出“searchText7”,很明显就是关于同一个函数在短时间内重复调用,如何限制其调用频率的功能实现。

 1     function throttle(action, threshold) {  2         // TODO:  3     }  4   5     let timer = true;  6     const triggerScroll = throttle((val) => {  7         console.log(val);  8     }, 300);  9  10     triggerScroll("searchText1"); 11     triggerScroll("searchText2"); 12     triggerScroll("searchText3"); 13     triggerScroll("searchText4"); 14     triggerScroll("searchText5"); 15     triggerScroll("searchText6"); 16  17     setTimeout(() => {}, 500); 18  19     // 期望输出结果: 20     // searchText1 21     // 500ms 后输出 searchText7

首先,拿到题目后我们先运行一下,运行后报错,显示“triggerScroll is not a function”;由于题目中throttle函数没有返回值,所以triggerScroll接收到的throttle返回值为undefined,首先我们要在throttle函数中return一个函数,也就是throttle在调用时传进去的第一个参数。

1  function throttle(action, threshold) { 2     return action 3  }

这时再运行,控制台打印“searchText1-6”;这时我们只需要把防抖的通用模板套进去就可以实现了,我这里用的防抖,其实使用节流函数也是可以实现的,原理相同,我这里就不展示了,完整代码如下:

 1     function throttle(action, threshold) {  2         return action  3     }  4   5     let timer = true;  6     const triggerScroll = throttle((val) => {  7         if (timer) {  8             timer = false;  9             setTimeout(() => { 10                 timer = true; 11                 console.log(val); 12             }, 300) 13         } else { 14             return false; 15         } 16     }, 300); 17  18     triggerScroll("searchText1"); 19     triggerScroll("searchText2"); 20     triggerScroll("searchText3"); 21     triggerScroll("searchText4"); 22     triggerScroll("searchText5"); 23     triggerScroll("searchText6"); 24  25     setTimeout(() => { 26         triggerScroll("searchText7"); 27     }, 500);

另附上防抖和节流的通用模板:

//防抖 let timeid = null; function debounce() {     if (timeid) {         clearTimeout(timeid);     }     timeid = setTimeout(() => {         //这里写操作         console.log(Math.random());     }, 500);  } window.onscroll = debounce;//窗口滚动事件防抖

//节流 let timeid = null; function debounce(func, wait = 0) {     if (!timeid) {         timeid = setTimeout(() => {             console.log(Math.random());             timeid = null;         }, 500);     } } window.onscroll = debounce;


 2.js预编译以及运行流程

最终输出结果为“1 1 2 0”。这一题也会涉及到闭包,作用域等知识点。

 1 var i = 0;  2 function fn() {  3     i = 1;  4     console.log(i);  5     var i;  6     return function () {  7         console.log(i);  8         i += 1;  9     } 10 } 11  12 var fun = fn();//1 13 fun();//1 14 fun();//2 15 console.log(i);//0

首先我们需要了解js运行流程:首先进行语法解析;先通篇的检查js语法,若语法有误,则报错。其实进行预编译;即发生在代码执行之前,为代码的执行做的准备工作,一般表现于变量提升,即把变量的声明提前,但是赋值还是在响应位置赋值,再者就是把整个函数声明提前。最后js运行流程进入最后一步即解析执行。

搞懂js运行流程再区分作用域,即全局作用域和函数作用域,全局作用域顾名思义就是全局声明的变量或函数等。函数作用域也就是函数内部的作用域,在函数内部中声明的变量或函数是不会传到全局作用域的。

搞懂这两个概念这一题就能够迎刃而解了,首先全局声明了一个变量i并赋值为0,在声明一个fun接受fn执行后的返回值,所以fn会执行一次,局部变量i在fn函数中重新声明,变量提升,这时函数fn中使用的i都为函数作用域中声明的i,并且重新赋值为1,输出结果1。这里不会影响到全局变量i的值。再观望整体代码,全局变量i未被重新赋值,所以最后一行i输出为0.

再往下走,fu函数的返回值也是一个函数,所以fun接收到的是一个函数,fun函数被执行两次,注意到在fn函数中,i被重新声明过,所以这里的返回值函数即fun函数的作用域为局部作用域,使用的变量i为局部变量,即再fn函数中声明的i,所以输出1。输出之后i自增1,即 i = i+1,局部作用域i的值在fun函数弟一次执行后变为2,再次执行后,输出i为2。


3.值类型和引用类型

这一题主要涉及到值类型和引用类型的存储方式,即堆内存和栈内存。

1 function fn(obj) { 2     obj.name = "name"; 3     obj = new Object(); 4     obj.name = "newName"; 5 } 6 var xt = new Object(); 7 fn(xt); 8 console.log(xt.name);//name

首先题目中声明了一个函数fn和一个对象xt,调用fn函数,并把xt传入函数。这是第二行的obj和xt都为引用类型,新增了obj的属性name值也为name。由于此时obj和xt指向的同一片内存空间,所以xt也会新增name属性,并设置值为name。故最后一个输出name。

在第三行中可以看到obj被重新赋值,即重新开辟了一片内存空间,这时obj依然为新增name属性而不是修改name属性,并将name属性设置为newName,所以这是obj新增的属性不会影响到对象xt的属性。


4.同步和异步

这一题主要是考察异步代码和同步代码执行的先后顺序以及js任务队列。

 1    for (let index = 0; index < 5; index++) {  2         setTimeout(() => {  3             console.log(index);  4         }, 200);  5     }  6   7     for (let j = 0; j < 5; j++) {  8         setTimeout(() => {  9             console.log(j); 10         }, 200); 11     }

首先可以看到题目中有两个循环体,两个循环体中分别有两个延时200ms的延时器。做题之前我们应该先了解到JavaScript对于同步代码和异步代码的处理方式,js会先执行同步代码,遇到异步代码(例:延时器、计时器、Promise等)会先放置任务队列,等待触发条件依次执行。这个概念了解清楚后这题就能够迎刃而解了。

首先两个循环体为同步代码,会先执行。此时第一个循环体会将循环出的五个延时器放置任务队列,等待200ms后执行。第二个循环体也是如此,所以输出为“0 1 2 3 4  0 1 2 3 4”。


5.宏任务和微任务

此题主要是考察对于同步异步,以及宏任务和微任务的理解。

//写出执行结果 setTimeout(() => {     console.log(1); }, 0);  new Promise((res) => {     console.log(2);     for (var index = 0; index < 1000; index++) {         index = 9999 && res()      }     console.log(3); }).then(() => {     console.log(4); }) console.log(5);

此题最终输出结果为“2 3 5 4 1”;

解答此题我们需要先了解到Promise的特性。Promise是异步编程的一种解决方案,是解决恐怖回调/回调噩梦的一种方案,其就相当于一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。

创建promise对象,function就相当于容器,用于存放异步代码(不仅限于异步,同步也可以),此函数在页面加载时会正常执行,同步代码会进入主线程执行,异步代码会进入任务队列,未来才会得到结果。并且Promise有三种状态,进行时(pending默认值),已成功(fulfilled/resolved),已失败(rejected)一旦状态改变,就不会再变,任何时候都可以得到这个结果,成功后会调用.then()方法,then方法接受两个参数,皆为回调函数,第一个函数promise状态变为已成功时触发的,第二个函数promise状态变为已失败时触发。

了解了Promise后,我们还需要搞清楚宏任务和微任务的概念。在上面我们提到过,js会见异步代码放置任务队列,宏任务是由宿主(Node、浏览器)发起的,常见的就是计时器和延时器;而微任务是由js引擎发起的,常见的就时Promise;执行顺序为微任务先执行,宏任务后执行。

这时我们就可以来解答这个题目了,首先遇到延时器,为宏任务,放入任务队列,其实遇到promise,先执行promise的同步代码,输出2,再进入循环,第一次进入循环index被重新赋值为9999,并且将promise的状态改为已成功,再往下走输出3;

此时promise状态为已成功,触发then方法的第一个函数,但是promise响应为异步响应。并且为微任务,放置任务队列,排在宏任务之前。再往下走就输出5。

此时同步代码执行完毕,开始执行异步代码,先执行微任务,输出4,在执行延时器代码。输出5。


好啦。以上就是小谭今天分享的内容啦,也欢迎各位一起来讨论技术呀!文章中有不足的地方小谭也欢迎各位指正探讨,不胜感激!