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