JavaScript 面试基础知识(中篇)
1. 什么是进程?什么又是线程
特性 | 进程 | 线程 |
---|---|---|
定义 | 程序的独立运行实例 | 进程内的最小执行单位 |
资源分配 | 拥有独立的资源和内存空间 | 共享所在进程的资源和内存 |
开销 | 创建、销毁、切换开销较大 | 开销较小 |
执行效率 | 并行执行效率相对低 | 在多核 CPU 中更易实现并发 |
稳定性 | 进程崩溃不影响其他进程 | 线程崩溃会影响所在进程 |
通信方式 | 需要进程间通信(IPC) | 可以直接访问进程中的共享数据 |
使用场景 | 高隔离需求的任务,独立运行的程序 | 多任务并发,如并行处理和任务分配 |
2. 并发与并行的区别
并发和并行都是处理多个任务的概念,但它们有一些关键的不同:
并发(Concurrency)
并发是指在同一时间段内处理多个任务。在并发的情况下,多个任务可以交替执行,但不一定同时执行。并发主要依靠时间片轮转等机制让任务看起来像是“同时”进行,但在某个时刻只有一个任务在执行。
- 实现方式:通过任务切换,在不同任务之间快速切换,使得用户感觉到多个任务同时进行。
- 应用场景:适用于单核或多核系统,尤其是 I/O 密集型任务,比如网络请求、文件读写等。
- 例子:假设一个单核处理器在短时间内快速交替执行任务 A 和任务 B,看起来像是它们在同时执行,但实际上是快速切换的结果。
并行(Parallelism)
并行是指在相同时间点上执行多个任务。并行需要多核 CPU 或多台处理器同时工作,真正地做到同时运行多个任务。
- 实现方式:在多核 CPU 或多台处理器上让不同任务在不同核上真正地同时执行。
- 应用场景:多核系统的计算密集型任务,比如图像处理、科学计算等。
- 例子:在四核 CPU 上同时运行四个独立的计算任务,每个任务在不同的核心上运行,真正地在同一时刻完成多个任务。
并发与并行的区别
特性 | 并发 | 并行 |
---|---|---|
定义 | 同一时间段内处理多个任务 | 同一时间点上执行多个任务 |
执行方式 | 任务交替执行,快速切换 | 任务在不同的处理器或核心上同时执行 |
系统需求 | 适用于单核或多核系统 | 需要多核系统或多处理器 |
适用场景 | I/O 密集型任务 | 计算密集型任务 |
示例 | 单核 CPU 交替执行多个任务 | 多核 CPU 上同时执行多个任务 |
总结
- 并发:任务交替执行,在同一时间段内处理多个任务。
- 并行:任务真正同时执行,在同一时刻完成多个任务。
并发可以在单核系统中实现,而并行需要多核或多处理器系统来实现。
3.什么是重绘(Repaint)和回流(Reflow)
重绘和回流是浏览器渲染页面的两个关键过程,主要影响页面性能和用户体验。理解它们的区别可以帮助优化页面渲染,提高性能。
1. 什么是重绘(Repaint)
重绘是指当元素的外观发生变化,但不影响布局时,浏览器重新渲染元素的过程。例如更改元素的颜色、背景图片或可见性等属性。
- 触发条件:只更改元素的外观(如颜色、背景等),不涉及尺寸、位置或结构变化。
- 性能影响:重绘的开销相对较小,因为浏览器只需更新元素的外观,而不需要重新计算布局。
- 示例:javascript
element.style.color = "red"; // 更改颜色,触发重绘 element.style.visibility = "hidden"; // 更改可见性,触发重绘
2. 什么是回流(Reflow)
回流(也称重排)是指当页面中元素的尺寸、位置、布局或结构发生变化时,浏览器需要重新计算元素的布局并重新渲染的过程。回流是一个更耗性能的过程,因为浏览器需要重新计算所有相关元素的位置和布局。
- 触发条件:任何改变元素布局或结构的操作(如添加、删除元素,改变元素尺寸、边距、边框、位置等)都会触发回流。
- 性能影响:回流的性能开销较大,因为不仅需要重新计算该元素,还可能会影响其他元素,甚至导致整个页面的重新布局。
- 示例:javascript
element.style.width = "200px"; // 更改宽度,触发回流 element.style.marginTop = "20px"; // 更改外边距,触发回流
如何减少重绘和回流
为了提升页面性能,减少不必要的重绘和回流,可以采取以下优化策略:
- 合并操作:合并多次对元素样式的更改,尽量减少单独更改样式的次数。例如,可以使用
class
一次性更改多个属性。 - 减少 DOM 操作:频繁的 DOM 操作会导致回流,建议减少直接操作 DOM。可以使用
DocumentFragment
或虚拟 DOM 来批量更新,降低操作频率。 - 避免频繁读取样式:读取样式会触发浏览器重新计算布局,建议将读取和修改样式的操作分开,避免频繁读取。
- 使用
transform
和opacity
属性:这些属性只会触发重绘,不会导致回流,适合做动画效果。 - CSS 动画优化:将动画应用于
transform
或opacity
属性,避免改变尺寸或位置等会导致回流的属性。 - 减少复杂选择器:复杂的 CSS 选择器会增加计算时间,使用更简单的选择器可以提升性能。
- 批量操作样式:通过
element.style.cssText
一次性应用多项样式,而不是多次单独应用。 - 使用
will-change
:对于需要动画或频繁变化的属性,可以通过will-change
属性提前通知浏览器进行优化处理。
重绘和回流的对比
特性 | 重绘(Repaint) | 回流(Reflow) |
---|---|---|
定义 | 仅更新元素的外观(如颜色),不影响布局 | 更新元素的布局、尺寸或结构,重新计算布局 |
触发条件 | 改变颜色、背景、可见性等 | 改变尺寸、位置、结构等 |
性能影响 | 开销较小,只需更新外观 | 开销较大,重新计算布局,可能影响整个页面 |
优化建议 | 尽量合并外观更新,减少样式变动频率 | 合并布局更新、减少 DOM 操作、使用 transform/opacity 属性 |
示例 | color 、background | width 、height 、display 、margin |
4. 浏览器存储都有哪些?
存储方式 | 用途 | 容量限制 | 有效期 | 访问方式 | 特点 |
---|---|---|---|---|---|
Cookie | 存储少量文本数据,如用户登录状态等 | 每个 Cookie 约 4 KB | 可设置过期时间,默认会话结束时失效 | 通过 JavaScript 访问,HTTP 请求/响应中也可使用 | 随每个 HTTP 请求发送,安全性较低,容易被篡改 |
LocalStorage | 存储长期数据,如用户设置、配置信息等 | 5-10 MB | 永久存储,除非手动清除 | 通过 JavaScript 访问 | 不随请求发送数据,支持跨页面存取数据 |
SessionStorage | 存储会话期间的数据,如一次会话中的临时数据 | 5-10 MB | 浏览器会话结束时失效 | 通过 JavaScript 访问 | 仅在当前窗口或标签页有效,窗口关闭时数据清除 |
IndexedDB | 存储结构化数据,适用于大数据存储和查询 | 没有严格限制,取决于浏览器和设备 | 永久存储,除非手动删除 | 通过 JavaScript API 访问 | 支持事务操作,存储结构化数据,适合大数据存储 |
Web SQL Database(已弃用) | 存储结构化数据,基于 SQL 的数据库存储 | 5 MB 或更大 | 永久存储,除非手动删除 | 通过 SQL 查询语句访问 | 已弃用,未来不再支持,建议使用 IndexedDB |
Cache API | 存储资源(文件、图像、JS、CSS 等),主要用于离线缓存 | 容量通常较大 | 可设置过期时间,过期后删除 | 通过 JavaScript API 访问 | 适用于 PWA 离线支持,自动管理缓存空间 |
明确一点,localStorage 是同步的
是的,硬盘确实是一个 IO 设备,而大部分与硬盘相关的操作系统级 IO 操作确实是异步进行的,以避免阻塞进程。不过,在 Web 浏览器环境中,localStorage 的 API 是设计为同步的,即使底层的硬盘读写操作有着 IO 的特性。 js 代码在访问 localStorage 时,浏览器提供的 API 接口通常会处于 js 执行线程上下文中直接调用。这意味着尽管硬盘是 IO 设备,当一个 js 执行流程访问 localStorage 时,它将同步地等待数据读取或写入完成,该过程中 js 执行线程会阻塞。 这种同步 API 设计意味着开发者在操作 localStorage 时不需要考虑回调函数或者 Promise 等异步处理模式,可以按照同步代码的方式来编写。不过,这也意味着如果涉及较多数据的读写操作时,可能对性能产生负面影响,特别是在主线程上,因为它会阻塞 UI 的更新和其他 js 的执行。
5. 谈谈三种事件模型
事件模型
DOM0 级模型
: ,这种模型不会传播,所以没有事件流的概念,但是现在有的浏览器支持以冒泡的方式实现,它可以在网页中直接定义监听函数,也可以通过 js 属性来指定监听函数。这种方式是所有浏览器都兼容的。IE 事件模型
: 在该事件模型中,一次事件共有两个过程,事件处理阶段,和事件冒泡阶段。事件处理阶段会首先执行目标元素绑定的监听事件。然后是事件冒泡阶段,冒泡指的是事件从目标元素冒泡到 document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过attachEvent
来添加监听函数,可以添加多个监听函数,会按顺序依次执行。DOM2 级事件模型
: 在该事件模型中,一次事件共有三个过程,第一个过程是事件捕获阶段。捕获指的是事件从 document 一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行。后面两个阶段和 IE 事件模型的两个阶段相同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数可以指定事件是否在捕获阶段执行。
事件委托
事件委托指的是把一个元素的事件委托到另外一个元素上。一般来讲,会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件上,然后在外层元素上去执行函数。
阻止默认事件:
event.stopPropagation 阻止事件默认行为:
event.preventDefault()
6.自定义事件
目前实现自定义事件的方式共两种
- Event()
const sendEvent = new Event("sendMsg");
document.addEventListener("sendMsg", print);
document.dispatchEvent(sendEvent);
function print() {
console.log("hello custom event");
}
// result:hello custom event。
- CustomEvent()
const sendEvent = new CustomEvent("sendMsg", {
detail: {
name: "good luck",
},
});
document.addEventListener("sendMsg", print);
document.dispatchEvent(sendEvent);
function print(e) {
console.log(e.detail.name);
}
区别:
通过上面两个简单的例子我们可以看出,Event 和 CustomEvent 最大的区别在于,CustomEvent 可以传递数据
7. 前端模块化的理解
JavaScript 模块化规范对比
特性 | CommonJS | AMD (Asynchronous Module Definition) | CMD (Common Module Definition) | ES6 模块 |
---|---|---|---|---|
使用场景 | Node.js 服务端 | 浏览器(前端异步加载) | 浏览器(前端异步加载) | 浏览器、Node.js 等多环境 |
导入语法 | require | require | require | import |
导出语法 | module.exports / exports | define | define | export |
加载方式 | 同步加载(阻塞式) | 异步加载 | 懒加载,按需加载 | 静态加载,支持动态 import() |
依赖处理 | 编写时就确定依赖,加载整个模块 | 依赖提前加载 | 依赖在使用时加载 | 依赖提前加载,可静态分析 |
是否支持静态分析 | 否 | 否 | 否 | 是 |
适用场景 | 服务端开发,适合同步加载 | 浏览器,适合异步加载 | 浏览器,按需加载 | 现代浏览器和 Node.js 的模块化方案 |
实现库 | Node.js 标准模块系统 | RequireJS | Sea.js | 原生支持,无需额外库 |
ES6 模块与 CommonJS 模块有什么异同?
- CommonJS 是对模块的浅拷⻉,ES6 Module 是对模块的引⽤,即 ES6 Module 只存只读,不能改变其值,也就是指针指向不能变,类似 const;
- import 的接⼝是 read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向,可以对 commonJS 对重新赋值(改变指针指向),但是对 ES6 Module 赋值会编译报错。
ES6 Module 和 CommonJS 模块的共同点:
- CommonJS 和 ES6 Module 都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变。
9. 什么是尾调用,使用尾调用有什么好处?
尾调用指的是函数的最后一步调用另一个函数。使用尾调用的话,因为已经是函数的最后一步,所以这时可以不必再保留当前的执行上下文,从而节省了内存,这就是尾调用优化。但是 ES6 的尾调用优化只在严格模式下开启,正常模式是无效的。
10. JSON 与普通文本、XML 对比
特性 | JSON | 普通文本 | XML |
---|---|---|---|
数据格式 | 键值对结构 | 自由文本,无结构约束 | 标签嵌套结构 |
可读性 | 简洁清晰,易于阅读 | 视内容而定,结构性较差 | 结构复杂,语法较冗长 |
数据体积 | 较小,适合传输 | 取决于内容 | 通常较大,包含冗余标签 |
结构化支持 | 支持对象和数组结构 | 不支持 | 支持层次化的嵌套结构 |
数据类型支持 | 支持字符串、数字、布尔值、数组、对象等 | 通常为字符串 | 仅支持字符串 |
解析效率 | 较高,直接映射到数据结构 | 无需解析 | 相对较低 |
可扩展性 | 无注释支持,不支持复杂结构 | 不支持扩展 | 支持注释及自定义标签 |
跨语言兼容性 | 高,多数编程语言支持解析 | 较低 | 较高,多数编程语言支持解析 |
安全性 | 安全性较好,但需防止 XSS 等安全问题 | 取决于内容 | 安全性较好,易处理恶意数据 |
典型应用场景 | 前后端数据交换、配置文件、日志格式 | 简单数据记录、消息日志 | 复杂数据结构的文档、配置文件 |
总结
- JSON:轻量、结构化,适合网络传输和数据交换。
- 普通文本:无结构约束,适合简单记录。
- XML:灵活性高,适合复杂文档和配置数据。
11. Map 与 Object 的区别
特性 | Map | Object |
---|---|---|
键的类型 | 键可以是任意类型(对象、函数、基本数据类型等) | 键只能是字符串或 Symbol 类型 |
键值对的插入顺序 | 保留插入顺序,迭代时按插入顺序 | 没有顺序,属性顺序可能与插入顺序不同 |
键值对数量 | 使用 size 属性获取元素数量 | 使用 Object.keys(obj).length 获取元素数量 |
性能 | 在频繁增删数据时性能更高 | 通常较慢,适合少量数据操作 |
遍历方式 | 使用 forEach() 或迭代器 map.keys() 等方法 | 使用 for...in 或 Object.keys() 方法 |
默认属性 | 没有默认属性,所有键值都是用户定义的 | 继承自原型链,可能带有 toString 等属性 |
序列化支持 | 不支持 JSON.stringify() 序列化 | 支持 JSON.stringify() 序列化 |
常见应用场景 | 需要键值对存储,键类型灵活,数据量较大时更优 | 适合少量简单数据存储,如 JSON 结构 |
API 和方法支持 | 提供丰富的 API,如 set 、get 、has 等 | 无专用 API,需自行实现功能 |
垃圾回收 | 不支持弱引用 | 不支持弱引用 |
总结
- Map 更适合频繁增删操作和需要使用非字符串键的场景。
- Object 更适合轻量级的数据存储,例如 JSON 数据结构。
12. 为什么函数的 arguments 参数是类数组而不是数组?如何遍历类数组?
argument
s 是一个对象,它的属性是从 0 开始依次递增的数字,还有callee
和length
等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach
, reduce
等,所以叫它们类数组。
要遍历类数组,有三个方法:
function foo() {
Array.prototype.forEach.call(arguments, (a) => console.log(a));
// Array.prototype.slice(arguments)
}
function foo() {
const arrArgs = Array.from(arguments);
arrArgs.forEach((a) => console.log(a));
}
function foo() {
const arrArgs = [...arguments];
arrArgs.forEach((a) => console.log(a));
}
13. 什么是 DOM 和 BOM?
- DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。
- BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的法和接口。BOM 的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。
14. AJAX、Axios 和 Fetch 的区别
特性 | AJAX | Axios | Fetch |
---|---|---|---|
定义 | AJAX(Asynchronous JavaScript and XML)是一种用于网页与服务器异步通信的技术。 | Axios 是一个基于 Promise 的 HTTP 客户端库,用于浏览器和 Node.js 中发送 HTTP 请求。 | Fetch 是浏览器内置的 API,用于发送网络请求,基于 Promise。 |
是否基于 Promise | 否,传统的 AJAX 使用回调函数处理异步请求。 | 是,基于 Promise,支持异步操作。 | 是,基于 Promise,简化了处理异步请求的方式。 |
浏览器支持 | 所有现代浏览器均支持,但老版本 IE 需要使用 XMLHttpRequest 。 | 所有现代浏览器支持。 | 所有现代浏览器支持,但 IE 不支持。 |
请求方式 | 通过 XMLHttpRequest 或者 jQuery 的 $.ajax() 实现。 | 基于 XMLHttpRequest ,对其进行了封装,提供更简单的 API。 | 使用 window.fetch() ,语法简单且直观。 |
处理响应数据的方式 | 需要手动解析响应数据,通常通过回调函数处理。 | 自动将响应数据转换为 JSON,支持 .then() 和 .catch() 处理。 | 自动将响应数据转换为流,使用 .then() 和 .catch() 处理。 |
请求和响应拦截器 | 需要手动实现,通常使用 XMLHttpRequest 或 jQuery 插件来拦截请求和响应。 | 提供请求和响应拦截器,方便进行统一处理(如 token、错误处理)。 | 没有内建的拦截器,需要额外的实现。 |
错误处理 | 通过回调函数处理错误,需要手动检查状态码。 | 通过 .catch() 处理 Promise 错误,支持更简洁的错误处理。 | 通过 .catch() 处理 Promise 错误,需要手动检查响应状态码。 |
跨域请求 | 需要使用 CORS 或 JSONP 等技术来处理跨域问题。 | 支持跨域请求(通过设置 withCredentials )。 | 支持跨域请求(通过 CORS 配置)。 |
请求取消 | 可以通过 XMLHttpRequest.abort() 手动取消请求。 | 提供 cancelToken 机制,支持取消请求。 | 通过 AbortController 实现取消请求。 |
默认支持 JSON | 需要手动设置响应数据类型。 | 默认支持 JSON 格式的数据。 | 需要通过 .json() 方法手动解析响应数据。 |
简易性 | 需要更多的配置和代码。 | 相对简单,API 使用直观。 | API 简单,易于使用。 |
总结
- AJAX 是早期的技术,通过
XMLHttpRequest
实现异步请求,但使用起来相对复杂。 - Axios 是一个流行的基于 Promise 的库,简化了 HTTP 请求的过程,提供了更强大的功能,如请求拦截器和取消请求等。
- Fetch 是现代浏览器内置的 API,简洁、基于 Promise,但仍需要手动处理一些细节(如解析 JSON 和检查响应状态码)。
fetch 的优点:
- 语法简洁,更加语义化
- 基于标准 Promise 实现,支持 async/await
- 更加底层,提供的 API 丰富(request, response)
- 脱离了 XHR,是 ES 规范里新的实现方式
fetch 的缺点:
- fetch 只对网络请求报错,对 400,500 都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,- 只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
- fetch 默认不会带 cookie,需要添加配置项: fetch(url, {credentials: 'include'})
- fetch 不支持 abort,不支持超时控制,使用 setTimeout 及 Promise.reject 的实现的超时控制并不能阻止请- 求过程继续在后台运行,造成了流量的浪费
- fetch 没有办法原生监测请求的进度,而 XHR 可以
Axios
Axios 是一种基于 Promise 封装的 HTTP 客户端,其特点如下:
- 浏览器端发起 XMLHttpRequests 请求
- node 端发起 http 请求
- 支持 Promise API
- 监听请求和返回
- 对请求和返回进行转化
- 取消请求
- 自动转换 json 数据
- 客户端支持抵御 XSRF 攻击
选择使用哪种技术取决于项目的需求:
- AJAX 适用于老旧项目或对浏览器支持有特殊要求的场景。
- Axios 更适合需要请求拦截、响应拦截以及较复杂的功能的项目。
- Fetch 是最简单、最符合现代 JavaScript 风格的方案,适用于现代浏览器且无需额外引入库的项目。
15. 浏览器渲染进程有哪些线程
浏览器的渲染进程主要包含五种线程:
GUI 渲染线程:这个线程负责渲染浏览器页面,解析 HTML、CSS,构建 DOM 树、CSSOM 树、渲染树,以及绘制页面。当界面需要重绘或由于某种操作引发回流时,该线程就会执行。值得注意的是,GUI 渲染线程和 JS 引擎线程是互斥的,当 JS 引擎执行时,GUI 线程会被挂起,GUI 更新会被保存在一个队列中,等待 JS 引擎空闲时立即执行。
JS 引擎线程:也称为 JS 内核,这个线程负责处理 Javascript 脚本程序,解析 Javascript 脚本,并运行代码。JS 引擎线程一直在等待任务队列中任务的到来,然后进行处理。在一个 Tab 页中,无论什么时候都只有一个 JS 引擎线程在运行 JS 程序。
定时器线程:浏览器定时器(如 setTimeout、setInterval)内部的实现就是由这个线程来控制的。当定时器的时间到达时,会将回调任务推送到任务队列中等待 JS 引擎线程执行。
事件触发线程:当一个事件被触发时,例如用户点击了一个按钮,这个线程就会将这个事件添加到待处理事件队列的尾部,等待 JS 引擎线程的处理。
异步 http 请求线程:在 XMLHttpRequest 在连接后(包括请求发出和请求完成),是由这个线程来通知事件触发线程,然后事件触发线程将回调函数添加到待处理事件队列中,等待 JS 引擎线程的执行。
这些线程协同工作,确保浏览器的渲染进程能够高效、准确地处理各种任务和事件。
16.浏览器的垃圾回收机制
引用计数(Reference Counting)
定义:引用计数(Reference Counting)算法通过跟踪每个对象被引用的次数来确定对象是否为垃圾。 每个对象都有一个引用计数器,引用计数的过程如下:
- 当一个对象被创建时,其引用计数器初始化为 1。
- 当该对象被其他对象引用时,引用计数器加 1。
- 当该对象不再被其他对象引用时,引用计数器减 1。
- 当引用计数器减至 0 时,意味着该对象不再被引用,可以被垃圾收集器回收。
优势:
实时回收:引用计数可以在对象不再被引用时立即回收,不需要等待垃圾收集器的运行。这可以减少内存占用和提高程序的性能。
简单高效:引用计数是一种简单的垃圾收集算法,实现起来相对容易,不需要复杂的算法和数据结构。
存在的问题:
循环引用:当两个或多个对象相互引用时,它们的引用计数都不为零,即使它们已经不再被其他对象引用,也无法被回收。这导致内存泄漏,因为这些对象仍然占据内存空间,却无法被释放。
计数开销:维护每个对象的引用计数需要占用额外的内存空间,而且每次添加、删除引用都需要更新计数,增加了额外的开销。
标记-清除(Mark and Sweep)
定义:标记-清除(Mark and Sweep)算法通过标记不再使用的对象,然后清除这些对象的内存空间,以便后续的内存分配使用。
它分为两个阶段:标记阶段和清除阶段。
标记阶段:
- 在标记阶段,垃圾回收器会对内存中的所有对象进行遍历,从根对象开始(通常是全局对象)递归地遍历对象的引用关系。对于每个被访问到的对象,垃圾回收器会给它打上标记,表示该对象是可达的,即不是垃圾。这个过程确保了所有可达对象都会被标记。
清除阶段:
- 在清除阶段,垃圾回收器会遍历整个内存,对于没有标记的对象,即被判定为垃圾的对象,会被立即回收,释放内存空间。这样,只有被标记的对象会被保留在内存中,而垃圾对象会被清除。
优势:
简单有效:标记-清除算法相对简单,容易实现。它可以准确地找到不再被引用的对象,并回收内存。 处理循环引用:标记-清除算法能够处理循环引用的情况。当对象之间存在循环引用时,即使它们不再被任何其他对象引用,引用计数算法也无法将它们识别为垃圾,而标记-清除算法可以通过遍历的方式找到并清除这些对象。
存在的问题:
垃圾回收过程中的停顿:标记-清除算法会暂停程序的执行,进行垃圾回收操作。当堆中对象较多时,可能会导致明显的停顿,影响用户体验。
内存碎片化:标记-清除算法会在回收过程中产生大量的不连续的、碎片化的内存空间。这可能导致后续的内存分配难以找到足够大的连续内存块,从而使得内存的利用率降低。
17. 说一说从输入 URL 到页面呈现发生了什么?
1.浏览器接受 URL 开启网络请求线程(涉及到:浏览器机制,线程与进程等)
2.开启网络线程到发出一个完整的 http 请求(涉及到:DNS 查询,TCP/IP 请求,5 层网络协议等)
3.从服务器接收到请求到对应后台接受到请求(涉及到:负载均衡,安全拦截,后台内部处理等)
4.后台与前台的 http 交互(涉及到:http 头,响应码,报文结构,cookie 等)
5.缓存问题(涉及到:http 强缓存与协商缓存,缓存头,etag,expired,cache-control 等)
6.浏览器接受到 http 数据包后的解析流程(涉及到 html 词法分析,解析成 DOM 树,解析 CSS 生成 CSSOM 树,合并生成 render 渲染树。然后 layout 布局,painting 渲染,复合图层合成,GPU 绘制,外链处理等)
7.css 可视化模型(涉及到:元素渲染规则,如:包含块,控制框,BFC,IFC 等)
8.JS 引擎解析过程(涉及到:JS 解析阶段,预处理阶段,执行阶段生成执行上下文,VO(全局对象),作用域链,回收机制等)
- 浏览器通过 DNS 服务器得到域名的 IP 地址,向这个 IP 地址请求得到 HTML 文本
- 浏览器渲染进程解析 HTML 文本,构建 DOM 树
- 解析 HTML 的同时,如果遇到内联样式或者样式文件,则下载并构建样式规则,如果遇到 JavaScript 脚本,则会下载执行脚本
- DOM 树和 CSSOM 构建完成之后,渲染进程将两者合并成渲染树(render tree)
- 渲染进程开始对渲染树进行布局,生成布局树(layout tree)
- 渲染树对布局树进行绘制,生成绘制记录
18. 浏览器是如何解析代码的?
解析 HTML
HTML 是逐行解析的,浏览器的渲染引擎会将 HTML 文档解析并转换成 DOM 节点。
- 将 HTML 解析成许多 Tokens
- 将 Tokens 解析成 object
- 将 object 组合成一个 DOM 树
解析 CSS
- 浏览器会从右往左解析 CSS 选择器
- 我们知道 DOM 树与 CSSOM 树合并成 render 树,实际上是将 CSSOM 附着到 DOM 树上,因此需要根据选择器提供的信息对 DOM 树进行遍历。
19. DOMContentLoaded 与 load 的区别?
- DOMContentLoaded:仅当 DOM 解析完成后触发,不包括样式表,图片等资源。
- Load:当页面上所有的 DOM,样式表,脚本,图片等资源加载完毕事触发。
20. 什么是 JavaScript 中的包装类型?
在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:
"abc".length; // 3
"abc".toUpperCase(); // "ABC"
21. DOM 事件中 target 和 currentTarget 的区别
- event.target 返回触发事件的元素
- event.currentTarget 返回绑定事件的元素
22.requestAnimationFrame
的优势和使用场景
requestAnimationFrame
是一个用于执行动画和其他视觉效果的 JavaScript 方法。它允许你在浏览器下一次重绘之前执行指定的函数,通常用于创建流畅的动画效果,并且能够更好地与浏览器的渲染流程同步,提高性能。
优势
高效的动画渲染:
requestAnimationFrame
是浏览器为动画渲染提供的 API,能够根据屏幕刷新率自动调整更新频率,通常在 60 FPS(每秒 60 帧)时调用,从而使动画更加流畅,避免了过多的绘制和卡顿现象。性能优化: 由于它会在浏览器的空闲时间进行渲染,避免了无意义的重复渲染,减少了 CPU 和 GPU 的负担。它还会在浏览器窗口被最小化时停止调用,节省资源。
同步显示帧:
requestAnimationFrame
确保动画帧与浏览器的绘制周期同步,使得动画呈现更加平滑,避免了setTimeout
或setInterval
的不准确性(特别是在不同设备和屏幕刷新率上)。自动暂停: 浏览器会自动停止执行不在可见区域内的页面动画,从而节省资源,这对于长时间执行动画的页面尤其重要。
更精确的时间控制:
requestAnimationFrame
提供的时间戳(timestamp
参数)比setTimeout
或setInterval
更精确,可以更好地控制动画的进度。
使用场景
- 平滑动画:在需要进行连续的动画渲染时,如移动元素、滚动效果、渐变变化等。
- 游戏开发:在浏览器中创建基于帧的动画(如角色移动、特效)。
- 图形绘制:在图形绘制应用中,
requestAnimationFrame
是进行实时渲染的理想选择。
代码示例
let animationFrameId;
function animate() {
// 动画逻辑,如更新位置、样式
// 更新动画内容
// 请求下一帧动画
animationFrameId = requestAnimationFrame(animate);
}
// 启动动画
requestAnimationFrame(animate);
// 取消动画
cancelAnimationFrame(animationFrameId);