【面试题】2023年最新前端面试题-JS运行篇:闭包、内存管理、同步、异步、任务队列、event loop

原文见:语雀(https://www.yuque.com/deepstates/interview/vx9mdq

执行上下文、作用域
this
谈谈This对象的理解。
● this总是指向函数的直接调用者(而非间接调用者);
● 如果有new关键字,this指向new出来的那个对象;
● 在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;

this指向问题?(网易)
this的值的情况:
1、绑定规则
● new绑定 / 构造器调用模式:指向实例对象。如果手动地设置了返回对象,与this绑定的默认对象就会被丢弃。
● 显式绑定或硬绑定 / 间接调用模式(修改this的指向)
○ 显式绑定 / apply、call:apply、call调用时,指向传入的第一个参数
○ 硬绑定 / bind:this将永久地被绑定到了bind的第一个参数
● 隐式绑定 / 方法调用模式:指向调用对象。如执行obj.b(),this指向obj
● 默认绑定 / 函数调用模式
○ 默认地,指向全局变量window(严格模式下指向undefined)
○ 箭头函数:没有this。this与封闭词法环境的this保持一致。
2、类中的this:同普通函数一样,取决于如何被调用。如果要让类中的 this 值总是指向这个类实例,可在构造函数中绑定类方法
3、事件处理
● 作为一个DOM事件处理函数:指向触发事件的元素(一些浏览器在使用非addEventListener的函数动态地监听函数时不遵守这个规定)
● 作为一个内联事件处理函数:指向监听器所在的DOM元素

修改this的指向?(网易)
call, apply, bind

词法环境
作用域链
说说你对作用域链的理解? / 什么是作用域链? (网易)
全局函数无法查看局部函数的内部细节,但局部函数可以查看其上层的函数细节,直至全局细节。 当需要从局部函数查找某一属性或方法时,如果当前作用域没有找到,就会上溯到上层作用域查找, 直至全局函数,这种组织形式就是作用域链。
闭包
为什么要用闭包?/闭包的作用?
利用闭包可以突破作用域链,将函数内部的变量和方法传递到外部。

什么是闭包?(网易)
● 闭包的定义:闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域,将函数内部的变量和方法传递到外部。
● 解释清楚为什么 JavaScript 中的所有函数都是闭包的
● 可能的关于[[Environment]]属性和词法环境原理的技术细节。

闭包的特征?
1、函数嵌套函数
2.、函数内部可以引用外部的参数和变量

闭包应用场景? / 实际开发中闭包的应用?
闭包实际应用中主要用于封装变量,收敛权限
参考:
● 高阶函数#应用;
○ 防抖
○ 节流
○ 惰性函数
○ 级联函数
○ 柯里化
● es5 for循环事件监听
● 函数里使用了定时器
● 封装许多高级的功能集:减少闭包使用可以用立即执行函数传递变量
● 事件监听
● 用闭包模拟私有方法

闭包会产生什么问题?(恒生)
闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。
● 一般来讲,当函数执行完毕后,局部活动对象就会销毁,内存仅保存全局作用域。
● 但是,闭包的情况不同,闭包的词法环境包含了这个闭包创建时所能访问的所有局部变量。closure 函数执行完毕后,其环境记录不会销毁,因为匿名函数仍然保持对它的词法环境的引用。直到匿名函数被销毁后,closure 函数的词法环境才会被销毁。

内存空间:堆、栈、队列
栈和队列的区别?
1、队列先进先出,栈先进后出。
2、栈的插入和删除操作都是在一端进行的,而队列的操作却是在两端进行的。
3、栈只允许在表尾一端进行插入和删除,而队列只允许在表尾一端进行插入,在表头一端进行删除

栈和堆的区别?
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。
2、堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。

内存管理
知识点参考:
内存泄漏
哪些操作会造成内存泄漏?
● 意外的全局变量
● 被遗忘的计时器或回调函数
● 脱离DOM的引用
● 闭包

js内存泄漏的解决方式
参考:https://www.yuque.com/webfront/js/womwg5#LCpEf
1、global variables:对未声明的变量的引用在全局对象内创建一个新变量。在浏览器中,全局对象就是 window。
function foo(arg) {
bar = ‘some text’; // 等同于window.bar = ‘some text’;
}

(1)解决:
①创建意外的全局变量

function foo() {
this.var1 = ‘potential accident’
}

②可以在 JavaScript 文件开头添加 “use strict”,使用严格模式。这样在严格模式下解析 JavaScript 可以防止意外的全局变量。
③在使用完之后,对其赋值为 null 或者重新分配。
2、被忘记的 Timers 或者 callbacks
(1)解决:
3、闭包:一个可以访问外部(封闭)函数变量的内部函数。
(1)解决:
4、DOM 引用
(1)解决:

闭包保存的变量是只有引入的变量,还是父函数内声明的所有变量?如果只有引入的变量,那为什么会有内存溢出问题?(bilibili)
● 不是内存溢出,是内存泄漏吧
● 标记清除、引用计数的实现方式
● 202205:v8的gc更新过很多次了,现在不会有内存问题了(循环引用都被解决了)

垃圾回收
Javascript垃圾回收方法?
1、标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
2、引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值的引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,
也就是说只要涉及BOM及DOM就会出现循环引用问题。

同步、异步
同步和异步的区别是什么?分别举一个同步和异步的例子?
1、同步会阻塞代码执行,而异步不会。
2、alert是同步,setTimeout是异步。
● 同步:指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
● 异步:指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。

何时需要异步?
1、在可能发生等待的情况,等待也是占线程的一种
2、等待过程中不能像alert一样阻塞程序进行
3、因此,“等待的情况”都需要异步

任务队列:宏任务/微任务
什么是任务队列?
一、任务队列(task queue)主要分两种:
1、宏任务(macrotask):在新标准中叫task
(1)主要包括:script(整体代码),setTimeout,setInterval,setImmediate,I/O,ui rendering
2、微任务(microtask):在新标准中叫jobs
(1)主要包括:process.nextTick, Promise,MutationObserver(html5新特性)
二、扩展:
1、同步任务:在主线程上,排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;
2、异步任务:不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

哪些语句会放入异步任务队列中?
1、定时任务:setTimeout、setInterval
2、网络请求:ajax请求、动态加载
console.log(‘start’);
var img = document.createElement(‘img’);
img.onload = function() {
console.log(‘loaded’);
}
img.src = ‘https://ss0.baidu.com/60NW/a.jpg’;
console.log(‘end’);
// 打印出来的是start, end, loaded
3、事件绑定:dom事件
4、ES6中的promise.then中的函数
Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

何时被放入任务队列?
1、类似onclick等,由浏览器内核的DOM binding模块处理,事件触发时,回调函数添加到任务队列中;
2、setTimeout等,由浏览器内核的Timer模块处理,时间到达时,回调函数添加到任务队列中;
3、Ajax,由浏览器内核的Network模块处理,网络请求返回后,添加到任务队列中。
ajax加载完成,即ajax什么时候success,就什么时候把ajax中的函数放入到异步队列中

setTimeout、Promise、Async/Await 的区别
参考:https://gongchenghuigch.github.io/2019/09/14/awat/

事件循环机制
定时器的执行顺序或机制?
因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。所以即使把定时器的时间设置为0还是会先执行当前的一些代码。
function test(){
var aa = 0;
var testSet = setInterval(function(){
aa++;
console.log(123);
if(aa<10){
clearInterval(testSet);
}
},20);
var testSet1 = setTimeout(function(){
console.log(321)
},1000);
for(var i=0;i<10;i++){
console.log(‘test’);
}
}
test()

输出结果:

test //10次
undefined
123
321

什么是event loop?
事件循环。
1、js实现异步的具体解决方案:event-loop
2、运行栈:执行同步任务的
3、浏览器js引擎遇到了setTimeout,识别了这是一个异步任务,不会将其放入运行栈,而是把它拿走,拿走了之后也没有立马放到异步任务队列中,按延迟时间放入到任务队列中。同步任务没有正在执行的东西,就会读异步任务,把任务放到运行栈中,执行完了又去读异步任务,把任务放到运行栈中,如此循环。

event-loop流程?

  1. 在主线程执行同步任务的时候,会形成一个执行栈;
  2. 当主线程执行完执行栈中的同步任务,会先去执行微任务队列,执行过程中遇到微任务,直接放在队列的最后,直到执行完当前的微任务;
  3. 然后主线程去执行宏任务队列中的一个事件(如果最先来的事件还没返回结果,那就去看第二先来的事件是否准备就绪,如果准备就绪就先执行它,否则继续向下找);在处理宏任务的过程中遇到微任务,会放入微任务队列中,
  4. 当该宏任务处理完成之后,会再去执行微任务队列中的事件,执行完成之后才会去宏任务队列中执行,这样就形成了EventLoop,重复这样的过程直到执行完所有的任务。

了解v8引擎吗,一段js代码如何执行的?
● 在执行一段代码时,JS 引擎会首先创建一个执行栈
● 然后JS引擎会创建一个全局执行上下文,并push到执行栈中, 这个过程JS引擎会为这段代码中所有变量分配内存并赋一个初始值(undefined),
● 在创建完成后,JS引擎会进入执行阶段,这个过程JS引擎会逐行的执行代码,即为之前分配好内存的变量逐个赋值(真实值)。
○ 如果这段代码中存在function的声明和调用,那么JS引擎会创建一个函数执行上下文,并push到执行栈中,其创建和执行过程跟全局执行上下文一样。但有特殊情况,即当函数中存在对其它函数的调用时,JS引擎会在父函数执行的过程中,将子函数的全局执行上下文push到执行栈,这也是为什么子函数能够访问到父函数内所声明的变量。
○ 还有一种特殊情况是,在子函数执行的过程中,父函数已经return了,这种情况下,JS引擎会将父函数的上下文从执行栈中移除,与此同时,JS引擎会为还在执行的子函数上下文创建一个闭包,这个闭包里保存了父函数内声明的变量及其赋值,子函数仍然能够在其上下文中访问并使用这边变量/常量。当子函数执行完毕,JS引擎才会将子函数的上下文及闭包一并从执行栈中移除。

JS引擎是单线程的,那么它是如何处理高并发的呢?即当代码中存在异步调用时JS是如何执行的?
比如setTimeout或fetch请求都是non-blocking的,当异步调用代码触发时,JS引擎会将需要异步执行的代码移出调用栈,直到等待到返回结果,JS引擎会立即将与之对应的回调函数push进任务队列中等待被调用,当调用(执行)栈中已经没有需要被执行的代码时,JS引擎会立刻将任务队列中的回调函数逐个push进调用栈并执行。这个过程我们也称之为事件循环。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值