查看原文
其他

你是怎么做 MySQL 数据备份的?

编程导航 2023-07-23

The following article is from 面试鸭 Author 编程导航和鱼友们

大家好呀,今天继续来给大家分享一些优质面试题,希望对正在求职的小伙伴有所帮助~

后端

题目一

你是怎么做 MySQL 数据备份的?比如怎么恢复半个月前的数据?

官方解析

MySQL 数据备份是一个非常重要的工作,保证数据的安全性和可靠性。备份数据的方式有很多种,以下是其中一种基本的备份和恢复方式:

一、备份 MySQL 数据库

我们可以使用 mysqldump 工具来备份 MySQL 数据库,该工具可以生成 SQL 脚本文件,包含数据库中所有表和数据的语句。在终端中运行以下命令:

mysqldump -u [username] -p [database_name] > [backup_file].sql

其中,[username] 是 MySQL 用户名,[database_name] 是需要备份的数据库名称,[backup_file].sql 是备份的文件名。

该命令会将 SQL 脚本文件导出到当前目录下。

二、恢复 MySQL 数据库

如果需要恢复之前备份的数据,可以运行以下命令:

mysql -u [username] -p [database_name] < [backup_file].sql

其中,[username] 是 MySQL 用户名,[database_name] 是需要恢复的数据库名称,[backup_file].sql 是备份的文件名。

该命令会将备份文件中的 SQL 语句执行,从而将数据恢复到指定的数据库中。

如果需要恢复半个月前的数据,可以选择备份文件中的某个时间点之前的数据,并使用以上方法进行恢复。

此外,还有其他的备份方式,如使用 MySQL 自带的 mysqlbinlog 工具进行增量备份,或使用第三方备份软件进行备份。根据实际需求选择合适的备份方式,并将备份文件存放在可靠的位置。

鱼皮补充:mysql 备份操作是可以写在简历上的,推荐大家自己实践试试

鱼友的精彩回答

解州扯面的回答

MySQL 数据备份有多种方法,包括逻辑备份、物理备份、全备份和增量备份。你可以根据你的需求和环境选择合适的方式。如果你想要恢复半个月前的数据,你需要先确保你有半个月前的备份文件,然后使用相应的工具或命令进行还原。例如,如果你使用 mysqldump 命令进行了逻辑备份,那么你可以使用 source 指令或者 mysql 命令来导入备份文件。

逻辑备份

逻辑备份是使用 SQL 语句来导出和导入数据库的数据的方法。它的优点是简单易用,可以跨平台和存储引擎,可以部分备份和恢复。它的缺点是速度慢,占用空间大,可能影响数据库性能。

MySQL 提供了一个原生的逻辑备份工具叫做 mysqldump。你可以使用它来备份整个数据库实例、单个数据库或单张表。例如,如果你想要备份一个叫 ytt 的数据库,你可以在命令行窗口输入:

mysqldump -u 用户名 -p 密码 --database ytt > ytt.sql

这样就会生成一个包含 ytt 数据库所有数据的 SQL 文件。

如果你想要恢复这个文件到数据库中,你可以在命令行窗口输入:

mysql -u 用户名 -p 密码 < ytt.sql

或者

mysql -u 用户名 -p 密码 source ytt.sql

这样就会执行 SQL 文件中的语句,将数据还原到数据库中。

物理备份

物理备份是直接拷贝数据库的数据文件、配置文件和日志文件。它的优点是速度快,占用空间小,不影响数据库性能。它的缺点是恢复复杂,需要关闭数据库服务,可能导致数据不一致。

MySQL 有一个开源的物理热备工具叫做 Percona XtraBackup。它可以在不锁表的情况下备份 InnoDB、XtraDB 和 MyISAM 存储引擎的表,并且支持增量备份。你可以使用它来备份整个数据库实例或单个数据库。

例如,如果你想要全量备份一个叫 ytt 的数据库,你可以在命令行窗口输入:

innobackupex --user=用户名 --password=密码 --databases="ytt" /backup

这样就会在 /backup 目录下生成一个包含 ytt 数据库所有数据文件的目录。

如果你想要恢复这个目录到数据库中,你需要先准备好数据文件,然后拷贝到对应的数据目录中,并更改权限和所有者。具体步骤如下:

innobackupex --apply-log /backup/2023-02-27_00-00-00 cp -r /backup/2023-02-27_00-00-00/* /var/lib/mysql chown -R mysql:mysql /var/lib/mysql

请注意,在恢复之前,你需要停止 MySQL 服务,并确保数据目录为空。

全备份和增量备份

全备份是指备份数据库中的所有数据,不管数据是否有变化。它的优点是恢复简单,不需要其他文件。它的缺点是占用空间大,耗时长,影响数据库性能。

增量备份是指只备份上次全备份或增量备份后发生变化的数据,需要开启 binlog 日志功能. 它的优点是占用空间小,耗时短,不影响数据库性能,它的缺点是恢复复杂,需要依赖 binlog 日志文件和全备份文件。

例如,如果你想要每天进行一次全备份和每小时进行一次增量备份,你可以使用 mysqldump 工具和 binlog 工具来实现。

首先,在 my.cnf 文件中开启 binlog 功能,并设置每天生成一个日志文件:

[mysqld] log-bin=/var/lib/mysql/mysql-bin expire-logs-days=7 max_binlog_size=100M

然后,在 crontab 中设置定时任务:

`# 每天凌晨 0 点进行一次全备份 0 0 * * * mysqldump -u root -p password --all-databases > /backup/full_$(date +%Y%m%d).sql

每小时进行一次增量备份

0 * * * * mysqlbinlog --read-from-remote-server --host=localhost --user=root --password=password --stop-never-slave-server-id=10 mysql-bin.000001 > /backup/incremental_$(date +%Y%m%d%H).sql`

这样就可以在 /backup 目录下生成全备份和增量备份文件。

如果你想要恢复这些文件到数据库中,你需要先停止 MySQL 服务,并确保数据目录为空。然后按照以下步骤操作:

`# 恢复最近一次的全备份文件 mysql -u root -p password < /backup/full_20230227.sql

恢复之后产生的所有增量备份文件

mysqlbinlog /backup/incremental_202302270*.sql | mysql -u root -p password`

请注意,在恢复之前,你需要确认 binlog 文件名和路径,并按照时间顺序恢复。

题目二

什么是消息队列?消息队列有哪些应用场景?

官方解析

消息队列是一种异步通信机制,用于在应用程序之间传递消息。它可以将消息暂时存储在队列中,然后按照一定的顺序和条件将消息传递给消费者。

消息队列有以下几个主要的应用场景:

  • 异步处理:通过将任务转换为消息,异步地进行处理,可以提高系统的吞吐量和响应速度。
  • 系统解耦:在不同的系统或模块之间使用消息队列进行通信,可以实现系统的解耦,提高系统的灵活性和可扩展性。
  • 流量控制:消息队列可以对消息进行缓存和限流,保证系统的稳定性和高可用性。
  • 应用解耦:在同一个应用程序中,不同的模块之间使用消息队列进行通信,可以实现模块之间的解耦,提高代码的可维护性。
  • 日志处理:通过将日志转换为消息,可以实现日志的异步处理,提高系统的性能和可维护性。

常见的消息队列包括 Kafka、RabbitMQ、ActiveMQ、RocketMQ 等。

鱼皮补充:一般问到 “应用场景” 的题目,面试官其实更希望你结合自己的项目去说

鱼友的精彩回答

Gundam 的回答

消息队列是一种用于异步通信的机制,用于在不同的应用程序之间传递消息。消息队列通常由消息生产者、消息队列和消息消费者三部分组成。

消息生产者将消息发送到消息队列中,而消息消费者则从消息队列中接收消息。消息队列负责存储和管理消息,确保消息传递的可靠性和稳定性。在实现过程中,消息队列还会提供一些额外的功能,如消息过滤、消息路由、消息持久化等。

消息队列的特点:

  1. 异步通信:消息生产者和消息消费者之间采用异步通信模式,发送方无需等待接收方的响应即可继续执行。
  2. 解耦合:消息队列可以将消息生产者和消息消费者解耦合,使得它们之间的关系更加灵活。
  3. 可靠性:消息队列通常会提供一些保证消息传递可靠性的机制,如消息持久化、重试机制等。
  4. 缓冲:消息队列可以缓冲来自多个消息生产者的消息,使得消息消费者可以按照自己的节奏进行消费,从而有效地平衡生产者和消费者之间的处理速度。

消息队列的应用:

  1. 异步任务处理:通过将任务发送到消息队列中,异步处理任务,提高系统的并发性能和吞吐量。
  2. 解耦合系统:将不同的业务逻辑拆分成不同的服务,通过消息队列实现服务之间的通信,提高系统的可维护性和可扩展性。
  3. 流量削峰:将流量通过消息队列分散到不同的服务中,避免单个服务被高并发流量打垮。
  4. 日志收集:通过将日志消息发送到消息队列中,将日志收集和分析与业务逻辑解耦合,提高系统的可靠性和可维护性。
  5. 应用解耦:将不同的应用程序通过消息队列进行集成,实现应用之间的解耦合和数据交换。

行云的回答

消息队列基本架构:

  • 有称为生产者的客户端应用程序可以创建消息并将其传递到消息队列。
  • 另一个称为消费者的应用程序连接到队列并获取要处理的消息。
  • 存储在队列中的消息将被存储,直到消费者检索到它们为止。

消息队列的特点:

  • 提供异步通信协议,该协议是一种将消息放入消息队列并且不需要立即响应来继续处理的系统。
  • 电子邮件可能是异步通信的最佳示例。发送电子邮件后,发件人将继续处理其他事情,而无需接收者的立即响应。
  • 这种处理消息的方式使生产者与使用者脱钩,从而使他们不需要同时与消息队列进行交互。

应用场景:

  • 应用解耦合:

    • 场景:A 系统产生一条比较关键的数据,很多系统都需要 A 系统将这个数据发送过来。A 系统要时时刻刻考虑 BCDE 四个系统如果挂了该咋办?要不要重发,要不要把消息存起来?
    • 解决:如果使用 消息队列(MQ),A 系统产生一条数据,发送到 MQ 里面去,哪个系统需要数据自己去 MQ 里面消费。如果新系统需要数据,直接从 MQ 里消费即可;如果某个系统不需要这条数据了,就取消对 MQ 消息的消费即可。这样下来,A 系统压根儿不需要去考虑要给谁发送数据,不需要维护这个代码,也不需要考虑人家是否调用成功、失败超时等情况。
  • 异步:

    • 场景:以系统注册用户为例,正常情况下,注册用户信息需要查询数据库需要50ms,然后向用户发送邮件需要50ms,然后再向用户发送短信通知获取验证码又需要50ms,共计需要150ms,我们可以发现这些都是同步进行的,比较浪费时间。
    • 解决:如果采用 mq 之后,注册用户信息需要查询数据库需要50ms,然后其他操作写入到消息队列中,消息队列异步执行邮件以及短信服务,可能只需要20ms,这时共计需要 70ms 则可以完成任务,大大的缩短了任务的执行时间。
  • 流量削峰:

    • 场景:如果一个订单系统,每秒钟可以处理一万次下单,在平时正常的时候该系统可以应付正常的请求,正常时段我们下单之后就会收到结果。但是如果突然遇到高峰期,有十万次下单操作给到订单系统中,这时由于超出处理范围则会造成系统崩溃。
    • 解决:可以用到消息队列来做缓冲,将其流量削峰,将一秒内十万次下单存到消息队列中,然后订单系统每秒只拉取一万下单来做处理,将其他订单先积压在消息队列中,等高峰期过去后,系统就会慢慢把订单都处理完。

L Y J🌃的回答

消息队列:是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。

实现高性能、高可用、可伸缩和最终一致性架构。

使用较多的消息队列有 ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ。

1、 异步处理

可以将对实时性要求不高的场景进行异步处理,如用户注册后发送注册短信和注册邮件。

传统串行方式,将注册信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,再返回给客户。

并行方式,将注册信息写入数据库成功后,发送注册邮件和发送注册短信同时进行,三个任务完成后,返回给客户端。可以提高处理时间。

假如三个业务节点每个使用 50 毫秒,不考虑网络等其他开销,则串行方式的时间为 150 毫秒,并行的时间可能是 100 毫秒。

2、 应用解耦

用户下单后,订单系统需要通知库存系统。传统方法是:订单系统调用库存系统的接口。

假如库存系统没有办法完成访问,则整个操作无法完成。但是如果将消息写入消息队列,用户下单后,订单系统持续化处理,将返回用户订单下单成功,库存系统,将采用拉/推的方式,获取下单信息,库存系统根据下单的信息,进行库存操作。如此设计后,订单系统与库存系统将分离开来,订单系统写入消息队列,之后就不再关心其他后续操作,实现了解耦。

3、 流量削峰

在秒杀系统中使用广泛,一般因为流量过大,导致流量暴增,然后应用挂掉。解决方案为:在前段加入消息队列,可以控制活动的人数,缓解短时间内高流量压垮应用。

用户的请求,服务器接收后,首先写入消息队列,加入消息队列过长或者长度超过最大数量,则直接抛弃用户请求或者跳转到错误页面。

4、 日志处理,将消息队列使用在日志处理中,比如 Kafka 的应用,解决大量日志传输的问题,日志采集客户端,负责日志数据采集,定时写入 Kafka 队列;Kafka 消息队列,负责日志数据的接收、存储、转发;日志处理应用:订阅并消费 Kafka 队列中的日志数据。

5、 消息通讯,消息队列一般内置了高效地通信机制,可以实现高效的点对点的消息队列,或者聊天室等。

点对点通讯:客户端 A 和客户端 B 使用同一队列,进行消息通讯。

聊天室通讯:客户端A和客户端B...客户端N,订阅同一个主题,进行消息发布和接收,实现聊天室的效果。

题目三

设计模式是什么?为什么要学习和使用设计模式?

官方解析

设计模式是一套被反复使用、经过验证的、通用的解决特定问题的设计思想,是一种被设计师反复使用,经过时间验证的、解决特定问题的一种技术方案。

设计模式的主要作用在于:

  • 提高代码的可维护性、可扩展性、可读性,提高代码的质量;
  • 通过共享经验,提高开发人员的设计能力,缩短学习时间,增强团队合作效率;
  • 提高开发效率,缩短开发周期。

设计模式主要分为三大类:

  • 创建型模式:用于描述创建对象的方式;
  • 结构型模式:用于描述如何组合对象,形成更大的结构;
  • 行为型模式:用于描述对象之间的协作和职责分配。

在具体应用设计模式时,需要根据实际场景选用合适的设计模式。常用的设计模式包括单例模式、工厂模式、观察者模式、适配器模式、装饰器模式、策略模式等。

鱼皮补充:这题不用像报菜名一样说出所有设计模式,最好能举例你是如何在项目中应用它的

鱼友的精彩回答

一切总会归于平淡的回答

设计模式是指在软件开发中经常遇到的一些重复性问题,通过对这些问题的总结、抽象、归纳和提炼,得到的一些解决问题的通用方案。

学习和使用设计模式可以帮助开发人员提高代码的可重用性、可维护性、可扩展性和可读性,从而提高开发效率和代码质量。

设计模式的学习和使用,需要结合实际的业务场景进行理解和应用。以下是一些常见的现实业务场景和对应的设计模式:

  • 工厂模式:当需要创建多种具有相同特征的对象时,使用工厂模式可以将对象的创建与业务逻辑分离,降低代码的耦合度。例如,在电商平台上,不同种类的商品都需要进行库存管理和订单管理,可以使用工厂模式来创建对应的库存管理器和订单管理器。

  • 单例模式:当需要确保系统中某个类只有一个实例时,可以使用单例模式,保证全局唯一性,避免资源的浪费。例如,在 Web 应用中,有时需要保证所有请求都使用同一个数据库连接,可以使用单例模式来实现数据库连接池。

  • 观察者模式:当一个对象的状态发生改变需要通知其他对象时,可以使用观察者模式,将对象的状态与业务逻辑分离,提高系统的灵活性和可扩展性。例如,在多人在线游戏中,玩家的行为会影响其他玩家的状态,可以使用观察者模式来实现游戏中的事件处理和状态同步。

  • 策略模式:当需要在运行时根据不同的情况采用不同的算法时,可以使用策略模式,将算法与业务逻辑分离,提高代码的可维护性和扩展性。例如,在电商平台上,可以使用策略模式来实现不同的促销策略,例如满减、打折等。

面试官问到这个问题,可能想了解面试者是否熟悉常见的设计模式,并能够结合实际业务场景进行理解和应用,以提高代码质量和开发效率。同时,也想了解面试者是否有足够的设计能力和经验,能够在实际项目中使用设计模式来解决问题。

行云的回答

设计模式:是一套经过反复使用的代码设计经验,目的是为了重用代码、让代码更容易被他人理解、保证代码可靠性。

项目中合理地运用设计模式可以完美解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。

设计模式分为三大类:

  • 创建型模式:共5种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

  • 结构型模式:共7种:适配器模式、装饰器模式、代理模式、桥接模式、外观模式、组合模式、享元模式

  • 行为型模式:共11种:策略模式、模板方法模式、观察者模式、责任链模式、访问者模式、中介者模式、迭代器模式、命令模式、状态模式、备忘录模式、解释器模式

前端

题目一

JS 中数组是如何在内存中存储的?

官方解析

在 JavaScript 中,数组是一种特殊的对象,可以用于存储一组有序的数据。数组在内存中的存储方式与其他对象类似,都是存储在堆中。

JavaScript 中的数组是一种动态数组,可以自动扩展和收缩。当一个数组需要扩展时,JavaScript 引擎会为其分配更多的内存空间,同时将原有数据拷贝到新的内存空间中。当一个数组需要收缩时,JavaScript 引擎会将其内存空间释放掉。

在 JavaScript 中,数组的每个元素是通过其索引来访问的。索引是一个整数,用于指定数组中的一个元素。JavaScript 中的数组索引是以 0 开始的,也就是说,第一个元素的索引为 0,第二个元素的索引为 1,以此类推。

由于 JavaScript 中的数组是动态数组,所以它的索引可以随时改变。因此,当你访问一个数组时,需要确保你正在访问的索引是有效的,否则会导致数组越界的错误。

鱼皮评论:这题比较基础,了解一下即可

鱼友的精彩回答

星光的回答

1、在哪里存储?

JS中的数组是在堆内存中进行存储的,并且在栈内存中有指针来指向数组在堆内存的起始地址。

2、以什么形式进行存储?

JS中的数组继承于对象,因此可以存储不同数据类型的元素,可以是字符串、对象、数字、布尔值等,各个元素占的内存大小不一,JS引擎很难直接分配一块连续内存来进行存储,容易造成空间浪费。为了解决这种问题,JS引擎设有快数组和慢数组。

快数组采用线性存储的方式,占用一块连续内存来进行存储。新创建的数组默认都是快数组。

快数组长度可变,可动态扩展和收缩。当push元素,需要扩容时,会申请一块原来的1.5倍+16的内存空间,并将原来的数据拷贝过去,返回++length;当pop元素后,会判断当前容量是否大于等于length*2+16,决定是否收缩容量,并根据length+1 == old_length 来决定收缩一半容量还是回收全部容量。

慢数组采用字典存储结构(key,value,description)来记录映射关系,内存不连续。

快数组=》慢数组:快数组扩容时,如果出现大量“孔”,如原数组为[1,2], 突然a[1999] = 123,全填充数组变得特别稀疏,容量变成原来的9倍以上,或者新增的索引值比原来最大索引值 大于1024,则快数组变成慢数组。慢数组=》快数组:慢数组收缩时,如果元素实际所占内存小于数组所占内存容量的一半,则可转化为快数组。如上述例子,delete a[1999],慢数组则有机会转为快数组。

3、数组元素占据多大内存?

首先,在V8引擎当中,数字区分为Smi和HeapNumber两种,前者代表小整数(可以用32位表示的数字),后者代表浮点数和无法用32位表示的数,比如NaN,Infinity,-0,1.1等。(JS数字默认为64位表示,为加快数字运算速率,将小整数优化为32位进行运算)

其次,根据不同元素类型,数组可分大致分为6种类型:

1、PACKED_SMI_ELEMENTS:元素全为Smi类型的全填充数组

2、HOLEY_SMI_ELEMENTS:元素全为Smi类型的带孔数组

3、PACKED_DOUBLE_ELEMENTS:元素全为HeapNumber类型的全填充数组

4、HOLEY_DOUBLE_ELEMENTS:元素全为HeapNumber类型的带孔数组

5、PACKED_ELEMENTS:存在元素无法用HeapNumberr(Double)或Smi类型表示的全填充数组

6、HOLEY_ELEMENTS:存在元素无法用HeapNumberr(Double)或Smi类型表示的带孔数组

Smi类型的数组各个元素只占4字节,HeapNumber(Double)类型元素的各个元素占8字节,而对于PACKED_ELEMENTS和HOLEY_ELEMENTS这类数组,取决于数组中是否有Double类型元素,有则全为8字节,无则4字节。

另外,数组类型的转化只有从上到下,如PACKED_SMI_ELEMENTS => PACKED_DOUBLE_ELEMENTS => PACKED_ELEMENT,如 【1】 =》 【1,0.1】 =》 【1,{}】 【1,{}】 =》 【1,0.1】依旧为PACKED_ELEMENT类型数组

注意,元素内存大小 只有数组类型变化时,才会改变。如【1,0.1】此时元素占8字节,变成【1,{}】数组类型变化,并且数组中没有Double类型元素,则此时元素占4字节,如果此时还有Double类型元素,则依旧占8字节。

题目二

JS 中 Map 和 WeakMap 有什么区别?

官方解析

在 JavaScript 中,Map 和 WeakMap 都是键值对的集合,但它们有几个重要的区别:

  1. 键类型:Map 对象的键可以是任何类型,包括基本数据类型和对象类型,而 WeakMap 对象的键必须是对象类型。
  2. 垃圾回收:Map 对象中的键和值都会被常规垃圾回收机制回收,而 WeakMap 中的键是弱引用,即在对象被垃圾回收时,WeakMap 中对应的键值对也会被自动删除。这使得 WeakMap 通常用于缓存或元数据,当对象不再被使用时,WeakMap 可以自动清除对应的数据,避免内存泄漏。
  3. 迭代器:Map 对象有内置的迭代器,可以通过 for...of 循环来遍历键值对,而 WeakMap 没有内置的迭代器,因此不能直接遍历键值对。

综上所述,Map 和 WeakMap 在功能上有所重叠,但应用场景不同。Map 可以存储任何类型的键值对,适用于大部分情况;WeakMap 适用于需要自动清除的缓存和元数据场景。

鱼友的精彩回答

Kristen 的回答

1.弱引用 与强引用不同,弱引用并不阻止被引用的对象被垃圾收集器回收或收集,即使它是内存中对对象的唯一引用。

// 创建 WeakMap 对象的实例
let human = new WeakMap();
// 创建一个对象,并将其赋值给名为 man 的变量
let man = { name: "xiaan" };
// 调用 human 的 set 方法,并传递两个参数(键和值)给它
human.set(man, "done")
console.log(human)

以上代码的输出如下:

WeakMap {{…} => 'done'}
man = null;
console.log(human)

当我们将 man 变量重新赋值为 null 时,内存中对原始对象的唯一引用是弱引用,它来自我们前面创建的 WeakMap。当 JavaScript 引擎运行垃圾收集过程时,man 对象将从内存和我们分配给它的 WeakMap 中删除。这是因为它是一个弱引用,并且它不阻止垃圾收集。

当我们将 man 变量重新赋值为 null 时,内存中对原始对象的唯一引用是弱引用,它来自我们前面创建的 WeakMap。当 JavaScript 引擎运行垃圾收集过程时,man 对象将从内存和我们分配给它的 WeakMap 中删除。这是因为它是一个弱引用,并且它不阻止垃圾收集。

2.强引用 JavaScript 中的强引用是防止对象被垃圾回收的引用。它将对象保存在内存中。

下面的代码片段说明了强引用的概念:

let human = [man];
man =  null;
console.log(human);


由于 human 数组和对象之间存在强引用。对象被保留在内存中,可以通过以下代码访问:console.log(human[0]) 这里要注意的重要一点是,弱引用不会阻止对象被垃圾回收,而强引用却会阻止对象被垃圾回收。


与 map 不同,WeakMap 保存弱引用。因此,如果这些值在其他地方没有被强引用,它不会阻止垃圾回收删除它引用的值。除此之外,WeakMap 与 map 是相同的。由于弱引用,WeakMap 不可枚举。


区别总结


Map 和 WeakMap 都是键值对的集合 Map 对象的键可以是任何类型,包括基本数据类型和对象类型,而 WeakMap 对象的键必须是对象类型。垃圾回收:Map 对象中的键和值都会被常规垃圾回收机制回收,而 WeakMap 中的键是弱引用,即在对象被垃圾回收时,WeakMap 中对应的键值对也会被自动删除。当对象不再被使用时,WeakMap 可以自动清除对应的数据,避免内存泄漏。Map 对象有内置的迭代器,而 WeakMap 没有内置的迭代器,因此不能直接遍历键值对。


你还费解吗的回答


Map 和 WeakMap 都是 ES6 新增的用于存储键/值对的集合(Hash 结构),类似于对象。它们之间的区别如下:

  • 键的类型:Map 的键可以是任意数据类型,如字符串、数组、DOM 节点对象等。而 WeakMap 只能接受引用类型作为键,如果将其它类型作为键,则会报错。


  • 引用强度:Map 的键是强引用的,强引用就是默认的引用关系,比如创建一个普通的对象 let obj = {},这个对象就是强引用的。

JavaScript 规定,只要一个对象被强引用,也就是可以被访问,垃圾回收机制就不会回收对象所占用的内存。所以,只有手动消除 obj 对对象的强引用关系(obj = null),才能使对象占用的内存被回收。同理,如果使用 Map,那么对象间是存在强引用关系的:


let obj = { name : 'x' }; // obj 强引用着对象 { name : 'x' } 
const map = new Map(); 
map.set(obj, 'y'); // map 强引用着对象 { name : 'x' } 
obj = null;

尽管最后消除了 obj 对对象的强引用关系,但由于 map 依然强引用着对象,所以这部分内存无法被释放。 WeakMap 与 Map 相对,它的键是弱引用的,如果一个对象只被弱引用所引用,则被认为是不可访问(或弱可访问)的,垃圾回收机制会在适当的时机回收该对象所占用的内存。将上面的代码改成 WeakMap:

let obj = { name : 'x' }; // obj 强引用着对象 { name : 'x' } 
const wm = new WeakMap(); 
wm.set(obj, 'y'); // wm 弱引用着对象 { name : 'x' } 
obj = null;

当执行 obj = null 后,就只剩下 wm 弱引用着对象,因此当下一次垃圾回收机制执行时,这块内存就会被释放掉。 上述代码只涉及到了一个简单的对象,因此两者使用起来对内存的影响差别不大,但如果对象非常大,使用 Map 就会对内存造成非常大的额外消耗,甚至可能引发内存泄漏,需要手动清除 Map 的属性才能释放这块内存,而 WeakMap 能有效解决该问题。


API:Map 提供了四个与遍历操作相关的方法 keys()、values()、entries()、forEach(),其实例具有一个属性 size 和五个操作方法 set()、get()、has()、delete()、clear() 。而对于 WeakMap,由于它的键是弱引用的,引用的对象随时都可能被垃圾回收机制回收,即某个键是否存在完全不可确定,所以没有遍历方法,也不能确定大小(实例无 size 属性),其实例只有四个方法 set()、get()、has()、delete()。


应用场景:与对象一样,Map 用于存储键/值对:需要使用除数值、字符串、Symbol 以外的数据类型作为键;需要存储非常多的键/值对;代码涉及大量插入、删除操作。


如果要对少量的键/值对进行查找操作,那么使用对象有时速度会更快。 当需要为某个 DOM 节点对象(或其它对象)添加一些数据,并确保对象被删除后数据也随之消失,就可以使用 WeakMap,比如:


上述代码中,wm 保存着对 DOM 节点对象的弱引用,也就是说,其它位置对该对象的引用一旦消除,该对象占用的内存就会自动被垃圾回收机制释放。WeakMap 保存的这个键/值对,也会自动消失,无需手动清除。


题目三

Vue 模板是如何编译的?经历了哪些过程?


官方解析


Vue 模板在运行时会被编译成渲染函数,最终生成 Virtual DOM,进行页面渲染。


Vue 模板编译的过程主要分为以下三个步骤:


  1. 解析模板:Vue 会使用正则表达式解析模板字符串,解析出其中的指令、标签、属性等内容。

  2. 生成 AST(抽象语法树):将解析后的模板字符串转化为抽象语法树,抽象语法树是一个以 JavaScript 对象表示的树形结构,它将模板中的各个节点和属性用 JavaScript 对象的形式表示出来,方便后续对模板进行分析和处理。

  3. 生成渲染函数:将 AST 转化为渲染函数,渲染函数是一个纯 JavaScript 函数,用于将模板转化为 Virtual DOM。


在模板的编译过程中,还会涉及到动态指令、插槽、组件等特殊情况的处理,不同的编译器实现可能存在一些差异,但大体的编译流程是相似的。


通过模板的编译,我们可以将模板转化为可被 JavaScript 运行的函数,从而更高效地渲染页面,提高应用的性能。

鱼皮补充:这题很关键的一个点就是 Vue 最后是转化为 js 来执行的,其他细节的话根据自己的时间来,能了解多少了解多少吧

鱼友的精彩回答

luckythus 的回答

Vue模板编译分为三个阶段:

  1. 解析阶段
  2. 优化阶段
  3. 生成代码阶段

在解析阶段:

编译器会将模板解析成一个抽象语法树(AST),对模板进行逐个字符的解析,识别模板中的各种语法,然后将其转化为AST节点,在这个阶段中,Vue会使用正则表达式匹配模板中的指令,插值表达式、文本等内容,然后将其转化为AST节点,同时再为每个AST节点添加对应的属性和指令。

在优化阶段:

对生成的AST节点进行静态优化,对AST节点进行遍历,识别其中不必要的节点,并将其删除。这些不必要的节点包括:静态文本节点、静态表达式节点等,删除这些节点主要是为了减少运行时DOM操作的开销。因为在页面渲染过程中,DOM操作是一个比较耗费性能的操作,每次进行DOM操作都会引起浏览器的重绘和回流,所以删除节点可以减少DOM操作的优化方式,提高页面的性能。

在生成代码阶段:

这个阶段主要将优化后的AST生成可执行的JavaScript代码,这个过程是通过对AST进行遍历,将AST中的每个节点都转化为相应的JavaScript代码,并将其添加到最终的渲染函数中。

生成的渲染函数,接收一个数据对象作为参数,渲染函数中的接收的数据对象一般来自于组件的props和data属性。当组件被渲染时,Vue会根据组件定义中的props和data属性来生成一个数据对象,然后将这个数据对象传入渲染函数中,在渲染函数中,会对这个数据对象进行解析和处理,得到虚拟的DOM树,接着就涉及到diff算法,将这份虚拟的DOM树与上一次渲染的虚拟的DOM树进行比较,找出需要更新部分进行更新。

渲染函数里面不执行diff算法,只是通过生成虚拟的DOM树的方式来描述页面结构,diff算法是在虚拟DOM树生成后,Vue对比新旧虚拟DOM树的过程中执行的。

useGieGie 的回答

Vue 模板编译是指将 Vue 模板字符串转换为可执行的渲染函数的过程。Vue 模板编译器会将模板字符串解析为抽象语法树 (AST),然后将 AST 转换为可执行的渲染函数。具体来说,Vue 模板编译经历了以下几个过程:

  1. 解析:将模板字符串解析成 AST。解析器会对模板字符串进行词法分析和语法分析,生成对应的 AST。
  2. 优化:对 AST 进行优化,提高渲染性能。优化过程包括静态节点提升、缓存节点、diff 算法优化等。
  3. 代码生成:将 AST 转换为可执行的渲染函数。生成的渲染函数可以直接用于渲染视图。

除了上述过程,Vue 模板编译还会经历以下一些细节过程:

  1. 模板预处理:将模板字符串进行预处理,去掉换行符、注释、空白等不必要的内容,从而减少编译时间和生成的代码大小。

  2. 模板插值处理:将模板中的插值表达式 (如 {{}}) 解析成对应的渲染函数调用,从而实现数据的动态渲染。

  3. 指令处理:将模板中的指令 (如 v-for、v-if 等) 解析成对应的渲染函数调用,并生成对应的响应式数据绑定逻辑,从而实现视图和数据的绑定。

  4. 事件处理:将模板中的事件 (如 @click、@keydown 等) 解析成对应的事件监听器,并生成对应的事件处理逻辑,从而实现视图和交互的绑定。

需要注意的是,由于 Vue 模板编译的过程比较复杂,会占用一定的性能和资源,因此在开发中,可以考虑使用 Vue 的单文件组件 (SFC) 格式,将模板、样式和逻辑组合在一起,并使用打包工具进行编译和打包,以便实现更高效的开发和部署。

星球活动

1.欢迎参与 30 天面试题挑战活动 ,搞定高频面试题,斩杀面试官!

2.欢迎已加入星球的同学 免费申请一年编程导航网站会员

3.欢迎学习 鱼皮最新原创项目教程,手把手教你做出项目、写出高分简历!

加入我们

欢迎加入鱼皮的编程导航知识星球,鱼皮会 1 对 1 回答您的问题、直播带你做出项目、为你定制学习计划和求职指导,还能获取海量编程学习资源,和上万名学编程的同学共享知识、交流进步。

💎 加入星球后,您可以:

1)添加鱼皮本人微信,向他 1 对 1 提问,帮您解决问题、告别迷茫!点击了解详情

2)获取海量编程知识和资源,包括:3000+ 鱼皮的编程答疑和求职指导、原创编程学习路线、几十万字的编程学习知识库、几十 T 编程学习资源、500+ 精华帖等!点击了解详情

3)找鱼皮咨询求职建议和优化简历,次数不限!点击了解详情

4)鱼皮直播从 0 到 1 带大家做出项目,已有 50+ 直播、完结 3 套项目、10+ 项目分享,帮您掌握独立开发项目的能力、丰富简历!点击了解详情

外面一套项目课就上千元了,而星球内所有项目都有指导答疑,轻松解决问题

星球提供的所有服务,都是为了帮您更好地学编程、找到理想的工作。诚挚地欢迎您的加入,这可能是最好的学习机会,也是最值得的一笔投资!

长按扫码领优惠券加入,也可以添加微信 yupi1085 咨询星球(备注“想加星球”):

往期推荐

编程导航,火了!

考研失败,找工作的我上岸了

如何用 Redis 实现一个排行榜?

人生第一次面试,有点紧张

被问到 Java 和 Go 的区别,懵了

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存