首页 > temp > JavaScript教程 >
-
前端常见面试题
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。
好啦。以上就是小谭今天分享的内容啦,也欢迎各位一起来讨论技术呀!文章中有不足的地方小谭也欢迎各位指正探讨,不胜感激!
来源:https://www.cnblogs.com/xt-2020/p/15406545.html