文章目录
- 1.自我介绍
- 2.介绍一下前端熟悉的技术做过的项目。
- 3.接触过webpack吗?没怎么接触过……
- 4.有自己把前端项目部署到服务器上面吗?试过
- 5.是怎么从0到1搭建前端项目的?
- 6.从输入URL到页面呈现经历了哪些环节?
- 7.介绍一下HTTP请求方式。get post put delete这些名词累积
- 8.了解过HTTP幂等性吗?没听过
- 9.HTTP无状态所以cookie和session的区别。
- 10.HTTP缓存策略。说了一下大概,细节记得不太清了。
- 11.想让HTML解析的时候让JS延迟加载怎么办?
- 12.async和defer在哪些场景下使用好?
- 13.怎么理解进程和线程。大概说了说
- 15.了类里面的this,
- 16.es6了解哪些特性?累计名词
- 17.手写一个react组件,
- 18.说一说hooks的一些特性。
- 19.一个JS隐式转换的题,
- 20.CSS盒子模型讲一讲。就讲了讲
- 21.CSS外边距重叠。
- 22.编程题:找到二叉树中是否存在一条路径有满足节点值加起来为目标值。
- 23.编程题:给n个异步操作,要求存入队列中然后按序执行。
- 24.有看前端相关的书籍吗,怎么看的。
- 25.你是怎么学习前端的。也是一面问过的。
- 26.通过什么方式去打基础。bilibili大学
- 27.平时有逛github吗,有去了解一些开源社区里面的一些前端最新方向吗?
1.自我介绍
当在前端面试中进行自我介绍时,你可以按照以下结构进行组织:
- 介绍自己:
开始时,简要介绍自己的姓名和背景。你可以提及自己的学历、专业背景以及工作经验。
- 技术专长:
提及你在前端领域的技术专长和经验。列举你熟悉的编程语言、前端框架、库和工具等。可以着重强调你在哪些方面特别擅长,例如前端开发、响应式设计、用户界面设计、性能优化等。
- 项目经验:
介绍你在过去的项目中所承担的角色和贡献。可以选择其中几个有代表性的项目来详细介绍,包括项目的规模、技术栈、解决的问题以及你在项目中的具体工作。
- 学习与成长:
提及你的学习态度和成长经历。你可以分享你如何持续学习新技术、参与开发社区、阅读技术文章和书籍等。强调你对于前端领域的持续关注和追求,以及你在学习过程中遇到的挑战和如何克服它们。
- 团队合作与沟通能力:
强调你在团队合作中的角色和能力。你可以提及你在过去的团队合作中的贡献,以及你如何与其他团队成员协作、解决问题和有效沟通的能力。
- 总结与展望:
总结你的自我介绍,并表达你对前端领域的热情和对未来的展望。你可以提及你对前端技术的热爱和对新技术的追求,以及你希望在未来的工作中继续学习和成长。
自我介绍需要简明扼要地表达你的关键信息,同时展现你的技术实力、学习能力和团队合作能力。在介绍过程中,注意保持清晰的表达、自信的姿态和积极的态度。记得准备并练习自我介绍,以确保你能在面试中自信地展现自己。
2.介绍一下前端熟悉的技术做过的项目。
-
项目背景:首先,简要介绍你所参与的项目的背景和目的。说明该项目是什么,它解决了什么问题,以及它在业务或技术上的重要性。
-
技术栈:列出你在项目中使用的主要技术栈和工具。这包括编程语言、框架、库和其他相关技术。说明你选择这些技术的原因以及它们在项目中的作用。
-
功能和模块:简要描述项目的主要功能和模块。说明你在项目中负责的具体部分,以及你如何与团队协作开发这些功能和模块。
-
技术亮点:强调项目中的一些技术亮点或你在项目中遇到的挑战。这可以是你解决的技术难题、性能优化、跨浏览器兼容性或响应式设计等方面的问题。
-
成果和影响:列举项目的成果和影响。这可以是用户数量增长、用户反馈的改善、网站性能提升等。如果有具体的数据或统计结果支持,更好地展示项目的价值。
-
学习和成长:分享你在项目中学到的经验和取得的成长。说明你在项目中遇到的挑战,以及你如何克服这些挑战并学到新的知识和技能。
-
自我评价:最后,对你在项目中的贡献和经验进行自我评价。突出你在项目中发挥的作用,以及你对前端开发的热情和承诺。
确保在介绍项目时,简明扼要地表达你的观点,并与面试官保持良好的沟通。提供实际的例子和具体的细节,以支持你的介绍。同时,准备好回答与项目相关的问题,展示你对项目的深入了解。
3.接触过webpack吗?没怎么接触过……
Webpack 是一个现代化的前端构建工具,用于将多个前端资源(如 JavaScript、CSS、图像等)打包成优化的静态资源。它提供了强大的模块化能力和丰富的插件系统,使开发者能够更高效地构建和管理前端项目。
1.作用:
1.模块打包:
Webpack 可以将项目中的各个模块按照依赖关系进行打包,将多个模块合并成一个或多个最终的静态资源文件,从而减少网络请求和提升加载速度。
2.资源转换:
Webpack 可以通过加载器(Loader)将非 JavaScript 资源(如 CSS、图片、字体等)转换为 JavaScript 可以处理的模块。
3.代码分割:
Webpack 支持将代码拆分成多个块(chunks),实现按需加载,减小初始加载文件的体积,提高页面加载性能。
4.优化和压缩:
Webpack 提供了丰富的优化和压缩选项,可以优化代码、压缩资源、拆分 CSS 等,以提升项目的性能和效率。
5.开发环境支持:
Webpack 可以在开发过程中提供热模块替换(Hot Module Replacement)等功能,支持实时预览和快速开发调试。
2.使用 Webpack:
1.安装 Webpack:
通过 npm 或 yarn 安装 webpack 的相关依赖包。
2.创建配置文件:
在项目根目录下创建一个名为 webpack.config.js 的配置文件,用于配置 webpack 的各种选项。
3.配置入口和出口:
在配置文件中指定项目的入口文件和打包后的输出路径。
4.配置加载器:
根据项目的需要,配置各种加载器来处理不同类型的资源,如 Babel 加载器处理 ES6+ 语法、CSS 加载器处理 CSS 文件等。
5.配置插件:
根据需求,选择合适的插件来进一步优化项目,如压缩代码、拆分 CSS 文件等。
6.运行构建命令:
在命令行中运行相应的构建命令,将项目的资源进行打包。
3.类似的工具:
Webpack 是目前前端构建领域最受欢迎和广泛使用的工具之一,它提供了强大的功能和生态系统。除了 Webpack,还有一些类似的工具,如:
1.Parcel:
一个零配置的前端打包工具,提供了类似于 Webpack 的功能,但无需手动配置,可以快速开始项目开发。
2.Rollup:
一款专注于 JavaScript 库打包的工具,它通过静态分析来生成高效的输出,适用于构建库或组件。
3.Browserify:
一个让你可以在浏览器中使用 require 方法的工具,它可以将 CommonJS 模块打包成适用于浏览器环境的代码。
这些工具都有各自的特点和适用场景,开发者可以根据项目需求选择合适的工具进行前端构建和打包。
4.自带webpack
像 Create React App 等脚手架工具创建的项目通常会自带 Webpack。这些脚手架工具会在项目初始化的过程中,自动配置并集成 Webpack,以提供便捷的开发环境和构建流程。
脚手架工具会隐藏 Webpack 的配置细节,提供预设的配置文件,让开发者可以专注于业务代码的开发,而不必手动配置 Webpack。这些预设的配置文件中会包含常用的 Webpack 配置,例如入口文件、输出路径、加载器、插件等,以满足一般的项目需求。
脚手架工具还可能提供其他功能,例如开发服务器、热模块替换、代码分割等,以便开发者可以更加高效地进行项目开发。
需要注意的是,虽然脚手架工具会提供默认的 Webpack 配置,但在一些情况下,开发者可能需要自定义配置来满足特定需求。对于这种情况,脚手架工具通常也会提供一些方式来覆盖默认配置或进行扩展。
4.有自己把前端项目部署到服务器上面吗?试过
写起来有点麻烦,后面有机会单独写篇日志介绍。
5.是怎么从0到1搭建前端项目的?
使用 Create React App 可以快速搭建一个基于 React 的前端项目。下面是使用 Create React App 的步骤:
1.确保开发环境:首先,
确保你的机器上已经安装了 Node.js。你可以在终端中输入 node -v 和 npm -v 来检查是否已经安装。如果没有安装,可以去 Node.js 官网下载并进行安装。
2.安装 Create React App:
在终端中运行以下命令来全局安装 Create React App:
npm install -g create-react-app
这将会在你的机器上安装 Create React App,使其可在任何位置使用。
3.创建项目:
选择一个合适的位置,在终端中运行以下命令来创建一个新的 React 项目:
create-react-app my-app
这将会创建一个名为 my-app 的新项目。你可以将 my-app 替换为你自己想要的项目名称。
4.进入项目目录:在终端中进入你的项目目录:
cd my-app
5.启动开发服务器:
在终端中运行以下命令来启动开发服务器并运行你的 React 应用:
npm run start
这将会在默认端口(通常是 3000)上启动一个开发服务器,并在浏览器中打开你的 React 应用。
至此,你已经成功使用 Create React App 快速搭建了一个基于 React 的前端项目。Create React App 已经为你自动配置了 Webpack 和其他必要的开发工具,以便你可以立即开始开发。你可以在项目目录中找到更多的说明和示例代码,根据需要进行修改和扩展你的应用。
6.从输入URL到页面呈现经历了哪些环节?
当在浏览器中输入URL并按下回车后,以下是详细的步骤:
1.URL解析:
- 提取协议:浏览器解析URL以确定使用的协议(如HTTP、HTTPS)。
- 解析域名:如果URL包含域名,浏览器将解析该域名以获取对应的IP地址。
2.DNS解析:
- 浏览器向本地DNS缓存查找域名对应的IP地址。如果有匹配,则跳至步骤 4。
- 如果本地缓存中没有找到对应的IP地址,则浏览器向操作系统发送DNS查询请求。
- 操作系统将查询发送到配置的DNS服务器,以获取域名对应的IP地址。
- DNS服务器返回IP地址给操作系统,然后操作系统将其返回给浏览器。
3.建立TCP连接:
- 使用获取到的IP地址,浏览器与服务器之间建立TCP连接。
- 这涉及通过三次握手来确保连接的可靠性和完整性。
4.发送HTTP请求:
- 浏览器向服务器发送HTTP请求。
- 请求包括请求方法(如GET、POST)、路径、请求头和可选的请求体。
5.服务器处理请求:
- 服务器接收到请求后,根据请求的路径和其他信息来处理请求。
- 这可能涉及执行服务器端的代码、访问数据库等操作。
6.服务器发送HTTP响应:
- 服务器根据请求的处理结果生成HTTP响应。
- 响应包括响应状态码、响应头和响应体等。
7.接收并解析响应:
- 浏览器接收到服务器发送的HTTP响应。
- 浏览器解析响应头,获取响应的元数据,如状态码、内容类型等。
- 浏览器开始逐步接收和解析响应体,获取实际的响应内容。
8.渲染页面:
- 如果响应是HTML文档,浏览器开始解析HTML,并构建DOM(文档对象模型)树。
- 浏览器解析CSS样式表,构建CSSOM(CSS对象模型)树,并将其与DOM树合并形成渲染树。
- 浏览器执行JavaScript代码,可能会修改DOM树和CSSOM树,以及处理事件等。
- 浏览器根据渲染树计算布局和绘制,将页面的可视化表示生成为位图。
9.显示页面:
- 浏览器将渲染好的页面显示给用户。
- 页面上的文本、图像、链接和其他元素可见并交互。
- 如果页面包含其他资源(如样式表、图像、脚本文件),浏览器将并行下载这些资源并进行相应的处理。
7.介绍一下HTTP请求方式。get post put delete这些名词累积
get与post介绍
get请求:从指定的资源请求数据,用于获取数据,一般用于搜索排序和筛选之类的操作。
post请求:向指定的资源提交要被处理的数据,用于将数据发送给服务器,一般用于修改和写入数据。
get请求和post请求本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
post请求和get请求的区别
(1)post请求更安全(不会作为url的一部分,不会被缓存、保存在服务器日志、以及浏览器浏览记录中,get请求的是静态资源,则会缓存,如果是数据,则不会缓存)
(2)post请求发送的数据更大(get请求有url长度限制,http协议本身不限制,请求长度限制是由浏览器和web服务器决定和设置)
(3)post请求能发送更多的数据类型(get请求只能发送ASCII字符)
(4)传参方式不同(get请求参数通过url传递,post请求放在request body中传递)
(5)get请求产生一个TCP数据包;post请求产生两个TCP数据包(get请求,浏览器会把http header和data一并发送出去,服务器响应200返回数据;post请求,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 返回数据)
注意:在发送 POST 的时候都没有带 Expect 头,server 也自然不会发 100 continue。
post请求的过程:
(1)浏览器请求tcp连接(第一次握手)
(2)服务器答应进行tcp连接(第二次握手)
(3)浏览器确认,并发送post请求头(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
(4)服务器返回100 Continue响应
(5)浏览器发送数据
(6)服务器返回200 OK响应
get请求的过程:
(1)浏览器请求tcp连接(第一次握手)
(2)服务器答应进行tcp连接(第二次握手)
(3)浏览器确认,并发送get请求头和数据(第三次握手,这个报文比较小,所以http会在此时进行第一次数据发送)
(4)服务器返回200 OK响应
网络环境好的情况下,发一次包和发两次包的时间差别基本可以忽略。而在网络环境差的情况下,两次包的TCP在验证数据包完整性上,有非常大的优点。
总结
除了 GET 和 POST 之外,HTTP 协议中还有许多其他请求方法,如HEAD、 PUT、DELETE、PATCH 等。这些请求方法都有不同的用途和特点,需要根据具体的情况选择合适的请求方法。
3.HEAD:和Get请求相一致,只不过不会返回响应体,这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。(用于获取报头)
-
OPTIONS: 询问支持的请求方法。请求成功后,http头中包含一个名为“Allow”的头,值是支持的方法,如get,post,put等
-
HEAD: 类似于get,通常用来测试某个资源是否存在。
-
PUT:向指定资源位置上传最新的内容。和post很类似,但是put通常指定来资源存放的位置,而post则没有。
-
DELETE:请求服务器删除Request-URI所标识的资源
-
TRACE:(极少使用)回显服务器收到的请求,主要用于测试或诊断
-
CONNECT:(极少使用)HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
8.了解过HTTP幂等性吗?没听过
HTTP 幂等性是指对同一个 HTTP 请求的多次执行,产生的效果与单次执行的结果是相同的。这意味着无论对同一请求执行多少次,服务器端的资源状态都只会发生一次变化,并且在多次请求中不会产生额外的副作用。
常见的 HTTP 方法中,**GET 和 HEAD 方法是幂等的,**因为它们只用于获取资源的状态,不会对服务器资源进行修改。对同一个 GET 或 HEAD 请求进行多次执行不会产生任何副作用。
而对于非幂等的 HTTP 方法,如 POST、PUT 和 DELETE 方法,它们可能对服务器资源进行修改或删除,因此需要特殊的处理来确保幂等性。具体实现幂等性的方式取决于应用程序的设计和业务需求。
对于 POST 方法,可以通过在请求中使用唯一的标识符或在服务端进行幂等性检查来实现幂等性。这样即使对同一个请求进行多次执行,也只会创建一次资源。
对于 PUT 和 DELETE 方法,它们在设计上就是幂等的,因为它们用于更新和删除资源,多次执行对同一资源的操作不会产生额外的影响。
总结来说,HTTP 幂等性确保对同一个请求的多次执行不会产生不一致的结果,并且可以在网络通信中处理重复请求和请求失败的情况。这对于构建可靠和稳定的分布式系统非常重要。
9.HTTP无状态所以cookie和session的区别。
1.http无状态
HTTP 是一种无状态协议,这意味着服务器在处理每个请求时都是相互独立的,它不会在请求之间保留任何关于客户端的状态信息。为了处理这种无状态性,引入了 Cookie 和 Session 这两种机制来维护用户的状态信息。
2.cookie介绍
Cookie 是在客户端保存数据的一种机制。当客户端首次访问服务器时,服务器可以通过响应头中的 Set-Cookie 字段将一个 Cookie 发送给客户端,客户端则会将该 Cookie 存储在本地。之后,每次客户端向服务器发送请求时,会自动在请求头中携带该 Cookie,从而允许服务器识别并跟踪客户端。Cookie 是基于文本的键值对,可以存储少量的用户信息。
3.cookie它具有以下特点:
- 存储在客户端,由客户端负责管理和发送给服务器。
- 可以设置过期时间,可以在指定时间后自动删除。
- 可以设置域名和路径限制,控制 Cookie 的作用范围。
- 有大小限制,通常每个域名下的 Cookie 总大小限制为 4KB 左右。
4.session介绍
Session 是在服务器端保存用户状态信息的一种机制。当客户端首次访问服务器时,服务器会为该客户端创建一个唯一的 Session ID,并将该 Session ID 发送给客户端。客户端在后续的请求中会将该 Session ID 携带在请求头中,服务器根据 Session ID 来识别和恢复客户端的状态。Session 可以存储较大量的用户信息,通常以键值对的形式保存在服务器内存或持久化存储中。
5.Session 的特点包括:
- 存储在服务器端,服务器负责管理和维护。
- 通常使用服务器内存或数据库等进行存储。
- 可以设置过期时间,可以在一段时间后自动删除。
- 没有大小限制,但存储在服务器内存中可能对服务器性能造成影响。
6.Cookie 和 Session 的区别可以总结如下:
(1)存储位置不同:Cookie 存储在客户端,Session 存储在服务器端。
(2)容量限制不同:Cookie 大小有限制,通常为 4KB 左右;Session 没有大小限制,但存储在服务器内存中可能会影响服务器性能。
(3)管理方式不同:Cookie 由客户端管理和发送给服务器;Session 由服务器管理和维护。
(4)安全性不同:Cookie 存储在客户端,容易受到安全攻击;Session 存储在服务器端,相对较安全。
在实际应用中,通常会结合使用 Cookie 和 Session。服务器通过使用 Cookie 来分配一个唯一的 Session ID 给客户端,客户端在后续的请求中通过 Cookie 携带该 Session ID,服务器根据 Session ID 来识别和恢复客户端的状态信息,从而实现用户的身份验证和状态管理。
10.HTTP缓存策略。说了一下大概,细节记得不太清了。
浏览器缓存策略可以分为强缓存和协商缓存。
-
强缓存是指客户端向服务器发送的请求中包含了强缓存控制头,如“Cache-Control”和“Expires”等,服务器根据这些头中的信息来决定是否使用缓存数据。如果请求中包含了强缓存控制头,服务器将直接使用缓存数据,而不需要进行协商。
-
协商缓存是指客户端向服务器发送的请求中不包含强缓存控制头,而是通过缓存协商的方式,由客户端和服务器之间进行协商,来确定是否使用缓存数据和缓存的有效期等信息。在协商缓存中,客户端会向服务器发送一个缓存请求,服务器根据请求中的信息来决定是否使用缓存数据,如果使用缓存数据,服务器也会返回一个缓存版本号给客户端,以便客户端判断是否需要更新缓存数据。
相比强缓存,协商缓存需要更多的网络交互和服务器资源,但是可以更好地利用缓存,避免缓存数据被重复使用,从而提高网站的性能和响应速度。
在实际应用中,强缓存和协商缓存可以结合使用,以实现更好的缓存控制效果。同时,需要注意缓存数据的安全和完整性,以避免缓存数据被篡改或失效。
强缓存:
服务器通知浏览器一个缓存时间,在缓存时间内,下次请求,直接用缓存,不在时间内,执行比较缓存策略。
-
强缓存命中直接读取浏览器本地资源,在network中显示的是from memory 或者 from disk,如在微信小程序开发中,命中图片缓存,显示from disk。
-
控制强缓存的字段有:Cache-Control(http1.1)和Expires(http1.0)
-
协商缓存,让客户端与服务器之间能实现缓存文件是否更新的验证、提升缓存的复用率,将缓存信息中的Etag和Last-Modified
通过请求发送给服务器,由服务器校验,返回304状态码时,浏览器直接使用缓存。 -
Cache-control是一个相对时间,用以表达自上次请求正确的资源之后的多少秒的时间段内缓存有效。
-
Expires是一个绝对时间。用以表达在这个时间点之前发起请求可以直接从浏览器中读取数据,而无需发起请求
-
Cache-Control的优先级比Expires的优先级高。前者的出现是为了解决Expires在浏览器时间被手动更改导致缓存判断错误的问题。
如果同时存在则使用Cache-control。
强缓存-expires:
- 优势特点
1、HTTP 1.0 产物,可以在HTTP 1.0和1.1中使用,简单易用。
2、以时刻标识失效时间。 - 劣势问题
1、时间是由服务器发送的(UTC),如果服务器时间和客户端时间存在不一致,可能会出现问题。
2、存在版本问题,到期之前的修改客户端是不可知的。
HTTP缓存都是从第二次请求开始的:
强缓存-cache-conche:
- 优势特点
1、HTTP 1.1 产物,以时间间隔标识失效时间,解决了Expires服务器和客户端相对时间的问题。
2、比Expires多了很多选项设置。 - 劣势问题
1、存在版本问题,到期之前的修改客户端是不可知的。
协商缓存:
- 协商缓存的状态码由服务器决策返回200或者304
- 当浏览器的强缓存失效的时候或者请求头中设置了不走强缓存,并且在请求头中设置了If-Modified-Since 或者 If-None-Match 的时候,会将这两个属性值到服务端去验证是否命中协商缓存,如果命中了协商缓存,会返回 304 状态,加载浏览器缓存,并且响应头会设置 Last-Modified 或者 ETag 属性。
- 对比缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 在响应体体积上的节省是它的优化点。
- 协商缓存有 2 组字段(不是两个),控制协商缓存的字段有:Last-Modified/If-Modified-since(http1.0)和Etag/If-None-match(http1.1)
- Last-Modified/If-Modified-since表示的是服务器的资源最后一次修改的时间;Etag/If-None-match表示的是服务器资源的唯一标
识,只要资源变化,Etag就会重新生成。 - Etag/If-None-match的优先级比Last-Modified/If-Modified-since高。
协商缓存-协商缓存-Last-Modified/If-Modified-since
- 1.服务器通过 Last-Modified 字段告知客户端,资源最后一次被修改的时间,例如 Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- 2.浏览器将这个值和内容一起记录在缓存数据库中。
- 3.下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的 Last-Modified 的值写入到请求头的 If-Modified-Since 字段
- 4.服务器会将 If-Modified-Since 的值与 Last-Modified 字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
优势特点
- 1、不存在版本问题,每次请求都会去服务器进行校验。服务器对比最后修改时间如果相同则返回304,不同返回200以及资源内容。
劣势问题
1、只要资源修改,无论内容是否发生实质性的变化,都会将该资源返回客户端。例如周期性重写,这种情况下该资源包含的数据实际上一样的。 - 2、以时刻作为标识,无法识别一秒内进行多次修改的情况。 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
- 3、某些服务器不能精确的得到文件的最后修改时间。
- 4.、如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
协商缓存-Etag/If-None-match
为了解决上述问题,出现了一组新的字段 Etag 和 If-None-Match
Etag 存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag 字段。之后的流程和 Last-Modified 一致,只是 Last-Modified 字段和它所表示的更新时间改变成了 Etag 字段和它所表示的文件 hash,把 If-Modified-Since 变成了 If-None-Match。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
浏览器在发起请求时,服务器返回在Response header中返回请求资源的唯一标识。在下一次请求时,会将上一次返回的Etag值赋值给If-No-Matched并添加在Request Header中。服务器将浏览器传来的if-no-matched跟自己的本地的资源的ETag做对比,如果匹配,则返回304通知浏览器读取本地缓存,否则返回200和更新后的资源。
Etag 的优先级高于 Last-Modified。
优势特点
- 1、可以更加精确的判断资源是否被修改,可以识别一秒内多次修改的情况。
- 2、不存在版本问题,每次请求都回去服务器进行校验。
劣势问题 - 1、计算ETag值需要性能损耗。
- 2、分布式服务器存储的情况下,计算ETag的算法如果不一样,会导致浏览器从一台服务器上获得页面内容后到另外一台服务器上进行验证时现ETag不匹配的情况。
第一次请求资源时,服务器返回资源,并在response header中回传资源的缓存策略;
第二次请求时,浏览器判断这些请求参数,击中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否击中协商缓存,击中则返回304,否则服务器会返回新的资源。
11.想让HTML解析的时候让JS延迟加载怎么办?
1.实现方式
要让 HTML 解析时延迟加载 JavaScript,可以使用以下方法:
1.异步加载:
将 <script>
标签的 async 属性设置为 true。这样,在 HTML 解析过程中遇到该<script>
标签时,浏览器会开始异步下载并执行 JavaScript 文件,而不会阻塞 HTML 的解析和渲染过程。例如:
<script async src="your-script.js"></script>
2.延迟加载:
将 <script>
标签的 defer 属性设置为 true。这样,在 HTML 解析过程中遇到该 <script>
标签时,浏览器会异步下载 JavaScript 文件,但会延迟执行,直到 HTML 解析完成。这允许脚本在文档完全加载后执行,避免阻塞 HTML 的解析和渲染过程。例如:
<script defer src="your-script.js"></script>
使用异步加载或延迟加载的好处是,它们可以加快页面的加载速度和交互响应性,因为 JavaScript 文件的下载和执行不会阻塞页面的渲染和用户交互。
需要注意的是,使用异步加载或延迟加载时,确保 JavaScript 代码不依赖于 HTML 解析过程中的特定元素或 DOM 结构。如果需要确保 JavaScript 在特定元素之后执行,可以在代码中使用 DOMContentLoaded 事件或其他方式进行延迟执行的控制。
请注意,虽然异步加载和延迟加载可以改善页面性能,但也需要根据具体情况权衡利弊。某些情况下,例如需要在页面加载期间执行特定操作或在特定位置注入脚本时,可能需要其他加载策略。
2.区别介绍
设置 async 和 defer 属性的区别在于脚本的加载和执行时机。
1.async 属性:
当使用 async 属性时,浏览器会异步加载脚本,并继续解析 HTML 文档。一旦脚本下载完成,它会立即执行,独立于 HTML 文档的解析过程。多个带有 async 属性的脚本在下载完成后,会按照下载完成的顺序执行,但无法保证它们的执行顺序与它们在 HTML 文档中的顺序一致。
示例:
<script async src="script1.js"></script>
<script async src="script2.js"></script>
上述示例中的两个脚本将会异步加载,脚本的执行时机取决于其下载完成的顺序。
2.defer 属性:
当使用 defer 属性时,浏览器会异步加载脚本,但会等到整个 HTML 文档解析完成后再执行脚本。这意味着脚本的执行会延迟到 DOMContentLoaded 事件触发之前,而不会阻塞 HTML 文档的解析。
示例:
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
上述示例中的两个脚本会被延迟执行,直到整个 HTML 文档解析完成。
3.总结:
async 属性用于异步加载脚本,下载完成后立即执行,不阻塞 HTML 文档的解析。
defer 属性也用于异步加载脚本,但会等到整个 HTML 文档解析完成后再执行,不阻塞 HTML 文档的解析。
在选择使用 async 还是 defer 时,需要根据具体情况考虑脚本的加载和执行时机,以及脚本之间的依赖关系。
12.async和defer在哪些场景下使用好?
async 和 defer 属性在不同的场景下有不同的应用。
1.使用 async 属性的场景:
当脚本不依赖于其他脚本或 DOM 的特定元素,并且可以独立执行时,可以考虑使用 async。
当脚本的加载和执行顺序不重要,而且希望尽早下载并开始执行脚本时,可以使用 async。
当脚本较小且对页面性能影响不大时,可以使用 async 加快页面的加载速度。
示例:
<!DOCTYPE html>
<html>
<head>
<title>Async Example</title>
<script async src="script1.js"></script>
<script async src="script2.js"></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>'
在上述示例中,script1.js 和 script2.js 脚本将被异步加载,并在下载完成后立即执行,不会阻塞页面的解析和渲染过程。由于这两个脚本之间没有依赖关系,它们的执行顺序可能不确定。
2.使用 defer 属性的场景:
当脚本依赖于其他脚本或 DOM 的特定元素,并且需要在整个 HTML 文档解析完成后执行时,可以使用 defer。
当希望将脚本的执行推迟到 DOMContentLoaded 事件触发之前,以确保脚本能够访问页面的 DOM 结构时,可以使用 defer。
当脚本较大或对页面性能有一定影响时,可以使用 defer 避免阻塞页面的解析和渲染过程。
示例:
<!DOCTYPE html>
<html>
<head>
<title>Defer Example</title>
<script defer src="script1.js"></script>
<script defer src="script2.js"></script>
</head>
<body>
<!-- 页面内容 -->
</body>
</html>
在上述示例中,script1.js 和 script2.js 脚本会被异步加载,但会延迟执行,直到整个 HTML 文档解析完成。这样可以确保脚本能够访问页面的 DOM 结构。
需要根据具体的需求和脚本的依赖关系选择合适的加载方式,以优化页面加载性能和脚本的执行顺序。
13.怎么理解进程和线程。大概说了说
1.进程与线程介绍
在计算机科学中,进程(Process)和线程(Thread)是操作系统中用于执行任务的概念。
1.进程介绍
进程是指计算机中运行的一个程序的实例。它拥有独立的内存空间和系统资源,并且可以由操作系统进行调度和管理。每个进程都是一个独立的执行单元,具有自己的代码、数据和运行状态。
2.线程介绍
线程是进程中的一个执行路径。一个进程可以包含多个线程,它们共享相同的内存空间和系统资源。线程是执行计算机指令的最小单位,可以独立运行,并且可以并发执行。线程之间可以共享进程的资源,如变量、文件等。
3.进程与线程联系:
进程和线程都是计算机执行任务的基本单位。
进程可以包含多个线程,线程是进程的一个执行路径。
进程和线程都能够并发执行,提高计算机的利用率和响应性。
4.进程与线程区别:
进程是独立的执行实体,拥有自己的内存空间和系统资源,而线程是进程内的执行路径,共享进程的资源。
进程之间相互独立,互不影响,而线程之间共享同一个进程的上下文和资源。
创建和销毁进程比创建和销毁线程的开销更大,进程间的切换开销也比线程大。线程的创建和切换开销相对较小。
进程之间通信需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等。线程之间可以通过共享内存等方式直接进行通信。
2.JavaScript中的进程与线程
在 JavaScript 中,JavaScript 是一种单线程语言,意味着它只有一个执行线程(通常称为主线程)。这是因为 JavaScript 是一种设计用于浏览器中的脚本语言,主要用于操作 DOM、响应用户交互等任务。JavaScript 运行在浏览器的单个线程上,以确保事件的顺序执行和避免竞态条件。
尽管 JavaScript 本身是单线程的,但通过使用 Web Workers,可以在 JavaScript 中创建多个子线程,以实现并行处理任务。Web Workers 是运行在后台的 JavaScript 线程,可以执行耗时的计算和操作,以避免阻塞主线程,提高页面的响应性。但需要注意,Web Workers 之间是相互独立的,无法直接共享内存或访问 DOM,需要通过消息传递进行通信。
总结:
进程是计算机中运行的程序的实例,线程是进程中的执行路径。
进程具有独立的内存空间和系统资源,线程共享进程的资源。
JavaScript 是一种单线程语言,但可以使用 Web Workers 创建多个子线程实现并行处理。
3.问:一个8核CPU只能创建8个进程吗
不,一个 8 核 CPU 并不限制同时创建的进程数量为 8 个。实际上,可以创建更多的进程,不仅仅取决于 CPU 的核心数,还受操作系统和系统资源的限制。
在一个拥有 8 个核心的 CPU 上,可以同时运行多个进程,每个进程都可以在一个独立的核心上执行。这意味着你可以创建和管理超过 8 个的进程。操作系统负责调度和分配进程到可用的核心上,并根据需要进行进程切换。
实际上,操作系统能够同时管理和运行多个进程,数量取决于操作系统的设计和配置,以及系统资源的可用性。操作系统使用调度算法来确定哪些进程应该运行,并在核心之间进行动态分配。因此,在一个 8 核 CPU 上,可以创建和运行多个进程,数量通常远远超过 CPU 的核心数。
需要注意的是,创建过多的进程可能会对系统资源造成负担,并影响系统性能。因此,在实际应用中,应该根据实际需求和系统资源进行合理的进程管理和调度。
4.系统中,线程数可以大于等于进程数对吗
在一个系统中,CPU 上运行的线程数可以大于或等于进程数
每个进程都是一个独立的执行环境,包含自己的代码、数据和资源。而线程是在进程内部创建的执行单元,共享进程的资源,但拥有独立的执行上下文。
在多线程的情况下,多个线程可以在同一个进程中并行执行,利用多核 CPU 的并行处理能力。操作系统会将线程分配给可用的核心进行执行。
因此,一个进程可以包含多个线程,这些线程可以在多个核心上同时执行。所以在一个系统中,通常会有多个线程在 CPU 上并发执行,而进程的数量可以大于或等于线程的数量。
5.一个进程可以在不同cpu核心上执行吗
一个进程在不同 CPU 核心上执行是可能的。在多核处理器的系统中,操作系统可以将一个进程的不同线程分配到不同的 CPU 核心上并行执行。
当一个进程有多个线程时,操作系统可以根据调度算法将这些线程分配给可用的 CPU 核心,以实现并发执行。这样可以提高系统的并发性和性能。
进程是操作系统分配资源和管理任务的基本单位,而线程是在进程内部创建的执行单元。一个进程可以包含多个线程,这些线程共享进程的地址空间和资源,但拥有独立的执行上下文。
通过将不同线程分配到不同的 CPU 核心上,系统可以利用多核处理器的并行处理能力,提高计算能力和响应性能。每个 CPU 核心独立地执行一个线程,从而实现并发执行。
需要注意的是,进程和线程的调度是由操作系统负责管理的,具体的调度策略和算法会根据系统配置和需求而有所不同。操作系统会根据系统负载、线程优先级、资源需求等因素来决定将进程的哪些线程分配给哪个 CPU 核心执行。
因此,一个进程可以在不同的 CPU 核心上执行,以实现并发执行和提高系统的性能。这种多核并行处理的能力是现代计算机系统的重要特性之一。
6.一个进程的不同线程,可以在不同核心上执行吗
是的,一个进程的不同线程可以在不同的 CPU 核心上执行。现代操作系统具有多核处理器的支持,可以同时运行多个线程,并将它们分配到不同的 CPU 核心上执行。
操作系统通过调度算法来决定哪些线程应该在哪个 CPU 核心上执行。这样可以实现线程级别的并行处理,提高系统的并发性和性能。
具体的线程调度和分配是由操作系统负责管理的,它根据系统负载、线程的优先级和其他因素来做出决策。操作系统会尽可能地将线程分配给可用的 CPU 核心,以实现并行执行和最大化系统资源的利用。
因此,一个进程的不同线程可以在不同的 CPU 核心上并发执行,从而提高系统的性能和效率。这种并发执行可以充分利用多核处理器的优势,实现更高的计算能力和响应速度。
7.一个进程的多个线程在不同核心上执行,他们如何共享这一个进程的资源呢
当一个进程的多个线程在不同核心上执行时,它们可以共享同一个进程的资源。
进程内的线程共享进程的地址空间,包括代码、数据和堆栈。这意味着它们可以访问和修改相同的变量、对象和数据结构。线程可以通过共享内存进行通信,从而实现数据共享和交互。
此外,进程的文件描述符(file descriptor)和打开的文件也是进程级别的资源,线程可以共享这些资源,从而可以访问和操作相同的文件。
在多线程的环境中,需要注意对共享资源的访问控制和同步。由于多个线程可以并发地访问和修改共享资源,可能会出现竞态条件(race condition)等问题。因此,需要使用同步机制如互斥锁(mutex)、信号量(semaphore)等来确保线程之间的安全访问。
操作系统会提供这些同步机制,以及其他的线程间通信方式如条件变量(condition variable)等,来协调线程之间的共享资源和交互。
因此,虽然多个线程可能在不同的 CPU 核心上执行,但它们可以共享同一个进程的资源,通过共享内存和同步机制来实现数据共享和交互。这种共享资源的方式使得多线程编程可以更高效地利用系统资源,并实现并发执行和协作任务的目的。
14.前端的类。讲了讲class
在前端开发中,“class” 是一个关键词,用于定义和创建对象。它是面向对象编程(OOP)的核心概念之一。
在 JavaScript 中,class 是一种创建对象的模板,它包含了对象的属性和方法的定义。使用 class 可以更方便地创建和管理对象,并实现代码的模块化和复用。
以下是 class 的一些特点和用法:
1.类声明:
使用 class 关键词可以声明一个类,类名通常采用大写字母开头的驼峰命名法。
class MyClass {
// 类的定义
}
2.构造函数:
类可以定义一个特殊的方法叫做构造函数(constructor),它会在创建对象时被调用。
class MyClass {
constructor(name) {
this.name = name;
}
}
2.属性和方法:
类可以定义实例属性和方法,通过 this 关键词可以访问实例的属性和方法。
class MyClass {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, ${this.name}!`);
}
}
3.继承:
类可以通过 extends 关键词实现继承,子类可以继承父类的属性和方法,并可以添加自己的属性和方法。
class ChildClass extends ParentClass {
constructor(name, age) {
super(name);
this.age = age;
}
sayAge() {
console.log(`I am ${this.age} years old.`);
}
}
4.实例化对象:
通过使用 new 关键词和类名,可以实例化一个类创建一个对象。
const obj = new MyClass('John');
obj.sayHello(); // 输出:Hello, John!
Class 提供了一种更清晰、更结构化的方式来组织代码,并且使得代码更易于维护和扩展。它提供了面向对象编程的特性,如封装、继承和多态,让开发者可以更好地设计和构建复杂的应用程序。在现代的前端开发中,class 已经成为了编写可复用组件的基础。
15.了类里面的this,
然后让我讲讲。自己给自己挖坑,好像记错了555。
- 普通函数中this指向的是调用它的对象,如果作为构造函数,它指向创建的对象实例;
- 箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
1.全局上下文:
全局上下文:当在全局上下文中使用 this 时,它指向全局对象(在浏览器中是 window 对象,在 Node.js 中是 global 对象)。
console.log(this); // 在全局作用域中输出全局对象(window 或 global)
2.函数调用:
当函数作为独立函数调用时,this 指向全局对象。这是因为函数没有被绑定到任何对象上。
function myFunction() {
console.log(this);
}
myFunction(); // 在浏览器中输出全局对象(window),在 Node.js 中输出全局对象(global)
3.方法调用:
当函数作为对象的方法调用时,this 指向调用该方法的对象。
const obj = {
name: 'John',
sayHello: function() {
console.log(`Hello, ${this.name}!`);
}
};
obj.sayHello(); // 输出 "Hello, John!"
4.构造函数调用:
当函数作为构造函数使用 new 关键字调用时,this 指向新创建的对象实例。
function Person(name) {
this.name = name;
}
const john = new Person('John');
console.log(john.name); // 输出 "John"
5.箭头函数:
箭头函数的 this 是在定义时继承自外部作用域,并且在整个箭头函数生命周期内保持不变。
const obj = {
name: 'John',
sayHello: () => {
console.log(`Hello, ${this.name}!`);
}
};
obj.sayHello(); // 输出 "Hello, undefined!"
在箭头函数中,this 的值是继承自外部作用域,因此在上述例子中,箭头函数没有自己的 this,导致 this.name 为 undefined。
首先,这里有个问题是js分严格模式和非严格模式,严格模式和非严格模式下this指向稍微有点区别,主要就是全局作用域中普通函数中的this指向问题,严格模式下是指向undefined的,非严格模式下是指向window。
16.es6了解哪些特性?累计名词
ECMAScript 6(ES6)是JavaScript的第六个版本,也被称为ES2015。它引入了许多新特性和语法改进,为开发者提供了更强大和更便捷的编程工具。下面是ES6中一些重要的新特性:
1.块级作用域和变量声明:
使用let和const可以在块级作用域中声明变量。块级作用域是由花括号({})定义的代码块。
// 使用let声明变量
function example() {
let x = 10;
if (true) {
let x = 20;
console.log(x); // 输出 20
}
console.log(x); // 输出 10
}
// 使用const声明常量
const PI = 3.14;
PI = 3.14159; // 报错,常量不可被重新赋值
2.箭头函数:
箭头函数使用=>符号定义,可以更简洁地声明函数。它们自动绑定上下文,并且在某些情况下省略了function关键字和return语句。
// 常规函数
function add(a, b) {
return a + b;
}
// 箭头函数
const add = (a, b) => a + b;
3.默认参数值:
ES6允许在函数参数中指定默认值。如果参数没有被传递或传递的值是undefined,则会使用默认值。
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // 输出 "Hello, Guest!"
greet('John'); // 输出 "Hello, John!"
4.模板字符串:
模板字符串使用反引号()包裹字符串,并支持在字符串中插入变量或表达式,使用${}`语法。
const name = 'Alice';
const age = 25;
const greeting = `My name is ${name} and I'm ${age} years old.`;
console.log(greeting); // 输出 "My name is Alice and I'm 25 years old."
5.解构赋值:
解构赋值语法允许从数组或对象中提取值,并将它们赋给变量。这样可以更方便地获取和操作复杂数据结构中的数据。
// 解构数组
const numbers = [1, 2, 3];
const [a, b, c] = numbers;
console.log(a, b, c); // 输出 1 2 3
// 解构对象
const person = { name: 'Alice', age: 25 };
const { name, age } = person;
console.log(name, age); // 输出 "Alice" 25
6.类和模块:
ES6引入了类和模块的概念,使得面向对象编程更加直观和易于使用。类使用class关键字定义,支持构造函数、继承和方法定义。模块允许将代码分割为多个文件,并使用export和import关键字导出和导入模块。
// 类
class Person {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, ${this.name}!`);
}
}
const person = new Person('Alice');
person.greet(); // 输出 "Hello, Alice!"
// 模块
// utils.js
export function add(a, b) {
return a + b;
}
// main.js
import { add } from './utils.js';
console.log(add(2, 3)); // 输出 5
7.迭代器和生成器:
迭代器提供了一种统一的遍历数据结构的方式。ES6中的可迭代对象可以通过实现Symbol.iterator方法来定义自己的迭代行为。生成器是一种特殊类型的函数,可以暂停和恢复执行,并且可以用于简化迭代器的创建。
// 迭代器
const numbers = [1, 2, 3];
const iterator = numbers[Symbol.iterator]();
console.log(iterator.next()); // 输出 { value: 1, done: false }
console.log(iterator.next()); // 输出 { value: 2, done: false }
console.log(iterator.next()); // 输出 { value: 3, done: false }
console.log(iterator.next()); // 输出 { value: undefined, done: true }
// 生成器
function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
const iterator = generateNumbers();
console.log(iterator.next()); // 输出 { value: 1, done: false }
console.log(iterator.next()); // 输出 { value: 2, done: false }
console.log(iterator.next()); // 输出 { value: 3, done: false }
console.log(iterator.next()); // 输出 { value: undefined, done: true }
8.Promise:
Promise是一种处理异步操作的机制。它代表一个异步操作的最终完成(或失败)及其结果的值。ES6引入了原生的Promise对象,使得异步编程更加简洁和可读。
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data fetched successfully!');
}, 2000);
});
}
fetchData()
.then((data) => {
console.log(data); // 输出 "Data fetched successfully!"
})
.catch((error) => {
console.log(error);
});
这只是ES6引入的一些重要特性的概览,还有其他一些语法改进和功能增强,如新的数组方法、Map和Set数据结构、Symbol、Proxy等。ES6的引入对于JavaScript的发展起到了重要的推动作用,并为开发者提供了更加现代和强大的编程工具。
9.Map(映射):
Map是ES6引入的一种新的数据结构,它类似于对象(Object),但有一些不同之处。Map允许将键值对(Key-Value)存储,并且键可以是任意类型的值,而不仅限于字符串类型。Map提供了丰富的方法用于增删改查操作。
// 创建一个Map
const map = new Map();
// 添加键值对
map.set('key1', 'value1');
map.set('key2', 'value2');
// 获取值
console.log(map.get('key1')); // 输出 "value1"
// 判断键是否存在
console.log(map.has('key2')); // 输出 true
// 删除键值对
map.delete('key1');
// 获取Map的大小
console.log(map.size); // 输出 1
// 遍历Map
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
10.Set(集合):
Set是另一种ES6引入的新的数据结构,它类似于数组(Array),但元素是唯一且无序的。Set提供了一组方法用于添加、删除和检查元素。
// 创建一个Set
const set = new Set();
// 添加元素
set.add('a');
set.add('b');
set.add('c');
// 检查元素是否存在
console.log(set.has('b')); // 输出 true
// 删除元素
set.delete('a');
// 获取Set的大小
console.log(set.size); // 输出 2
// 遍历Set
set.forEach((value) => {
console.log(value);
});
11.Symbol(符号):
Symbol是ES6引入的一种新的基本数据类型,用于表示唯一的标识符。Symbol的值是不可变且唯一的,可以用作对象的属性名,以防止属性名冲突。
// 创建一个Symbol
const key1 = Symbol();
const key2 = Symbol('description');
const obj = {};
// 使用Symbol作为属性名
obj[key1] = 'value1';
obj[key2] = 'value2';
// 获取Symbol作为属性名的值
console.log(obj[key1]); // 输出 "value1"
// 获取Symbol的描述
console.log(key2.description); // 输出 "description"
12.Proxy(代理):
Proxy是ES6提供的一种用于创建代理对象的机制。通过使用Proxy,可以拦截并自定义对象上的操作,例如访问、赋值、调用等。它提供了一个中间层,允许对对象的行为进行修改和增强。
// 创建一个被代理的对象
const target = {
name: 'Alice',
age: 25,
};
// 创建一个Proxy对象
const proxy = new Proxy(target, {
get(target, property) {
console.log(`Getting ${property}`);
return target[property];
},
set(target, property, value) {
console.log(`Setting ${property} to ${value}`);
target[property] = value;
},
});
console.log(proxy.name); // 输出 "Getting name"
proxy.age = 30; // 输出 "Setting age to 30"
17.手写一个react组件,
很简单就是点击button把input alert到页面。翻车了,有点久没写react了都在背八股。
18.说一说hooks的一些特性。
1.React Hooks介绍:
React Hooks 是在 React 16.8 版本中引入的一项重要特性,它改变了组件编写的方式。它允许你在函数组件中使用状态(state)、副作用和其他 React 特性,而无需编写类组件。
使用react hooks 的好处包括:
1.简化组件:
使用 Hooks 可以将逻辑相关的代码组织在一起,使组件更加简洁和易于理解。
2.复用逻辑:
通过自定义 Hooks,可以将组件之间共享的逻辑提取出来,实现逻辑的复用。
3.状态管理:
使用 useState Hook 可以在函数组件中定义和管理状态。
4.副作用管理:
使用 useEffect Hook 可以处理副作用,如数据获取、订阅和事件处理等。
5.上下文管理:
使用 useContext Hook 可以方便地在组件之间共享数据。
2.常用的 Hooks 包括:
1.useState:
用于定义和管理组件的状态。
2.useEffect:
用于处理副作用,如数据获取、订阅和事件处理等。
3.useContext:
用于在组件之间共享数据。
4.useReducer:
用于复杂的状态管理,类似于 Redux 的 reducer。
5.useCallback:
用于缓存回调函数,以便在依赖不变时避免不必要的重新创建。
6.useMemo:
用于缓存计算结果,以便在依赖不变时避免不必要的重新计算。
React Hooks 的引入使得函数组件具备了更多的能力和灵活性,使组件的编写更加简洁、可维护和可测试。使用 8.Hooks 可以摆脱类组件中繁琐的生命周期方法,提高代码的可读性和可维护性。
需要注意的是,使用 Hooks 需要在 React 版本 16.8 或更高版本中。如果使用较旧的 React 版本,需要升级到 React 16.8 或更高版本才能使用 Hooks。
3.部分Hooks使用
下面是除useState和useEffect外,四个常用的React Hooks的介绍和使用示例:
1.useContext:
useContext钩子用于在React组件中访问上下文(Context)。它接收一个上下文对象,并返回该上下文的当前值。使用useContext可以避免通过组件树手动传递上下文值。
import React, { useContext } from 'react';
// 创建一个上下文
const MyContext = React.createContext();
function MyComponent() {
// 使用useContext获取上下文的当前值
const contextValue = useContext(MyContext);
return <div>{contextValue}</div>;
}
function App() {
return (
<MyContext.Provider value="Hello, Context!">
<MyComponent />
</MyContext.Provider>
);
}
2.useReducer:
useReducer钩子用于管理具有复杂状态和行为的组件。它接收一个reducer函数和初始状态,并返回当前状态和dispatch函数,用于触发状态更新。
import React, { useReducer } from 'react';
// 定义reducer函数
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
// 使用useReducer管理状态
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'INCREMENT' })}>Increment</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>Decrement</button>
</div>
);
}
3.useCallback:
useCallback钩子用于在依赖项发生变化时,返回一个稳定的回调函数。它用于避免在每次渲染时创建新的回调函数,以提高性能。
import React, { useState, useCallback } from 'react';
function Button({ onClick }) {
return <button onClick={onClick}>Click</button>;
}
function App() {
const [count, setCount] = useState(0);
// 使用useCallback返回一个稳定的回调函数
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<Button onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
}
4.useMemo:
useMemo钩子用于在依赖项发生变化时,返回一个计算结果。它用于避免在每次渲染时重复计算成本较高的值,以提高性能。
import React, { useState, useMemo } from 'react';
function ExpensiveComponent({ value }) {
// 使用useMemo返回一个计算结果
const expensiveValue = useMemo(() => {
// 假设这里有一个复杂的计算
console.log('Calculating...');
return value * 2;
}, [value]);
return <div>Expensive Value: {expensiveValue}</div>;
}
function App() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<ExpensiveComponent value={count} />
</div>
);
}
以上是useContext、useReducer、useCallback和useMemo这四个常用React Hooks的介绍和使用示例。它们提供了一种在函数组件中管理状态、优化性能以及访问上下文的方式,使得React开发更加灵活和高效。
19.一个JS隐式转换的题,
1.隐式转换介绍
在JavaScript中,隐式转换(Implicit Conversion)是指在表达式中自动发生的类型转换。当两个不同类型的值进行操作时,JavaScript会根据一组规则自动转换其中一个值的类型,以便进行操作。以下是几个常见的隐式转换和示例:
1.字符串与数字的隐式转换:
当字符串和数字进行操作时,JavaScript会尝试将其中一个值转换为另一个值的类型。
const str = '10';
const num = 5;
console.log(str + num); // 输出 "105",数字转换为字符串进行字符串拼接
console.log(str - num); // 输出 5,字符串转换为数字进行减法运算
2.布尔值的隐式转换:
在需要布尔值的上下文中,JavaScript会自动将其他类型的值转换为布尔值。对于大多数值,除了以下情况,它们都被视为真值:
- 空字符串 ‘’"
- 数字 0 和 NaN
- null 和 undefined
- 布尔值 false
if ('hello') {
console.log('Truthy'); // 输出 "Truthy",非空字符串为真值
}
if (0) {
console.log('Truthy');
} else {
console.log('Falsy'); // 输出 "Falsy",数字0为假值
}
3.对象与原始值的隐式转换:
当对象与原始值进行操作时,JavaScript会尝试将对象转换为原始值的类型。
const obj = {
valueOf() {
return 10;
},
};
console.log(obj + 5); // 输出 15,对象通过valueOf方法转换为数字进行加法运算
4.强制转换:
在某些情况下,可以通过显式调用特定的转换函数来进行强制转换,例如String()、Number()和Boolean()。
const num = 5;
const str = String(num); // 数字转换为字符串
console.log(str); // 输出 "5"
const bool = Boolean(str); // 字符串转换为布尔值
console.log(bool); // 输出 true
隐式转换在JavaScript中经常发生,因此了解这些规则对于编写正确的代码很重要。然而,隐式转换可能会导致意外的行为,因此在需要时最好显式进行类型转换,以提高代码的可读性和可维护性。
2.举例说明
1. if ( { } ), if ( [ ] )
当在条件中使用一个空对象, 或空数组时,对象和数组会被隐式转换为布尔值。根据JavaScript的规则,非空的对象、数组等被视为真值。
if ({}) {
console.log('Condition is true.'); // 代码块会执行
} else {
console.log('Condition is false.');
}
if ([]) {
console.log('Condition is true.'); // 代码块会执行
} else {
console.log('Condition is false.');
}
2.if( [ ] == true) ,if ( [ ] === true),
在这个例子中,我们尝试将一个空数组 [] 与布尔值 true 进行相等性比较。
if([]==true) { //触发类型转换
console.log('Condition is true.');
} else {
console.log('Condition is false.'); // 代码块会执行
}
在相等性比较中,JavaScript 会根据规则进行类型转换。空数组的类型是对象,而 true 的类型是布尔值。在相等性比较时,布尔值 true 会被转换为数字 1。而空数组被转换为0因此,空数组和布尔值 true 是不相等的,条件被认为是 false。
if ([] === true) { // === 不会触发类型转换
console.log('Condition is true.');
} else {
console.log('Condition is false.'); // 代码块会执行
}
=== 会对等号双方的类型进行比较,因此不会触发类型转换,数组和true的类型都不一样,所以被认为是false。
3.if( { } == true) ,if ( { } === true),
结果同数组类似。
4.==会进行类型转换,===不会
在JavaScript中,==
运算符进行相等性比较时会进行类型转换,而===
运算符进行严格相等性比较时不会进行类型转换。
具体来说:
==
运算符在比较之前会尝试将操作数转换为相同类型,然后再进行比较。这个过程称为隐式类型转换。它会根据一组规则将不同类型的值转换为相同类型,然后再进行比较。
===
运算符在比较时不会进行类型转换。它要求操作数的值和类型都要相等才会返回true,否则返回false。
5.== 和 ===的类型转换说明
在 JavaScript 中,使用 == 运算符进行相等性比较时,会根据一组规则进行类型转换以使操作数具有相同的类型。以下是一些常见的类型转换规则:
(1)如果一个操作数是布尔值(true 或 false),则将其转换为数字:
-
true 转换为 1
-
false 转换为 0
(2)如果一个操作数是字符串,另一个操作数是数字,将字符串转换为数字。
(3)如果一个操作数是对象,另一个操作数是数字或字符串,将对象转换为原始值:
-
对象会首先尝试通过调用 valueOf 方法来转换为原始值。
-
如果 valueOf 方法的返回值不是原始值,则会尝试调用 toString 方法来转换为原始值。
-
如果对象既没有 valueOf 方法,也没有 toString 方法,或者它们的返回值不是原始值,则对象不会进行转换。
(4)如果一个操作数是 null,另一个操作数是 undefined,它们被认为是相等的。
(5)如果一个操作数是 NaN(非数字),比较结果通常为 false,除非另一个操作数也是 NaN。
需要注意的是,使用 ==
运算符进行类型转换的过程是复杂的,而且容易引起意外的结果。由于类型转换的规则比较灵活,可能会导致一些奇怪的比较结果。因此,建议在比较值时使用严格相等性运算符 ===
,它不进行类型转换,要求操作数的值和类型都要相等才返回 true。
下面是一些示例来说明类型转换规则:
console.log(5 == '5'); // 输出 true,数字5和字符串'5'会进行类型转换,转换为相同类型后进行比较
console.log(5 === '5'); // 输出 false,因为数字5和字符串'5'的类型不同
console.log(false == 0); // 输出 true,布尔值false会被转换为数字0进行比较
console.log(false === 0); // 输出 false,因为布尔值false和数字0的类型不同
console.log(null == undefined); // 输出 true,null和undefined被视为相等
console.log(null === undefined); // 输出 false,null和undefined的类型不同
console.log(true == 1); // 输出 true,布尔值 true 被转换为数字 1
console.log(false == 0); // 输出 true,布尔值 false 被转换为数字 0
console.log("5" == 5); // 输出 true,字符串 "5" 被转换为数字 5
console.log("10" == 10); // 输出 true,字符串 "10" 被转换为数字 10
console.log({} == 0); // 输出 false,对象无法直接转换为数字
console.log({} == "[object Object]"); // 输出 true,对象被转换为字符串 "[object Object]"
console.log(null == undefined); // 输出 true,null 和 undefined 被认为是相等的
console.log(NaN == NaN); // 输出 false,NaN 和 NaN 通常被认为是不相等的
请注意,在实际开发中,为了避免混淆和意外的结果,推荐使用严格相等性运算符 === 并明确比较操作数的值和类型。
6.{ } == “abc”的处理过程
(1)类型转换:
首先,根据 JavaScript 的规则,将对象 {} 和字符串 “abc” 转换为相同的类型以便进行比较。
(2)对象转换为原始值:
对象 {} 会被转换为原始值。这个过程是通过对象的 toString 和 valueOf 方法来尝试转换的。
如果对象有 toString 方法,那么 JavaScript 会调用该方法并将其结果作为转换后的值。
如果对象没有 toString 方法,但有 valueOf 方法,那么 JavaScript 会调用 valueOf 方法并将其结果作为转换后的值。
如果对象既没有 toString 方法,也没有 valueOf 方法,或者它们的返回值不是原始值,那么对象将保持不变。
(3)字符串类型转换:
字符串 “abc” 保持不变,因为它已经是字符串类型。
综上所述,对于 {} 和 “abc” 的比较,会将对象 {} 转换为原始值,具体的转换顺序是先尝试 toString 方法,然后再尝试 valueOf 方法。如果对象 {} 没有这些方法,或者它们的返回值不是原始值,那么对象 {} 会保持不变。
最后进行比较的是两个转换后的值,它们的类型可能是字符串、数字或布尔值,根据转换的结果进行比较。在本例中,具体的转换结果取决于对象 {} 是否有定义 toString 或 valueOf 方法以及它们的返回值。
需要注意的是,具体的比较结果取决于对象 {} 的实现方式和属性的值。如果你有一个具体的对象 {},可以通过在对象上定义 toString 或 valueOf 方法来改变转换的结果。
20.CSS盒子模型讲一讲。就讲了讲
CSS盒子模型是CSS布局中的核心概念之一,用于描述元素在页面中占据的空间和相互之间的关系。它将一个元素视为一个矩形的盒子,由内容区域、内边距、边框和外边距组成。
盒子模型的组成部分包括:
1.内容区域(Content):
内容区域是盒子中用于显示实际内容的区域,例如文本、图像等。它的大小由元素的width和height属性决定。
2.内边距(Padding):
内边距是内容区域与边框之间的空间,用于控制内容与边框之间的距离。可以使用padding属性来设置内边距的大小。
3.边框(Border):
边框是包围内容区域和内边距的线或者边界。可以使用border属性来设置边框的样式、宽度和颜色。
4.外边距(Margin):
外边距是盒子与其他元素之间的空间,用于控制盒子与周围元素之间的距离。可以使用margin属性来设置外边距的大小。
这些部分形成了一个嵌套的层次结构,其中内层的盒子被包含在外层的盒子中。每个盒子都有自己的尺寸和定位属性,可以通过CSS来控制它们的外观和布局。
在标准的盒子模型中,宽度(width)和高度(height)属性指定的是内容区域的尺寸,不包括内边距、边框和外边距。然而,通过CSS的box-sizing属性可以控制盒子模型的计算方式,
有两个可选值:
- content-box(默认值):宽度和高度只包括内容区域的尺寸。
- border-box:宽度和高度包括内容区域、内边距和边框的尺寸,而外边距仍然不包括在内。
以下是一个示例,展示了盒子模型的组成部分和CSS属性的应用:
.box {
width: 200px;
height: 150px;
padding: 20px;
border: 1px solid black;
margin: 10px;
}
html
Copy code
<div class="box">
This is the content area.
</div>
在上述示例中,.box 类定义了一个盒子,并设置了宽度、高度、内边距、边框和外边距。盒子将包含一个内容区域,其宽度为200px、高度为150px,周围有20px的内边距和1px的边框,外边距为10px。
盒子模型在网页布局中扮演重要角色,通过控制盒子的尺寸、内外边距和边框样式,可以实现各种布局效果和页面结构。
21.CSS外边距重叠。
1.外边距重叠情况介绍
- 只在块元素发生
- 只在垂直方向上发生
CSS外边距重叠是指当两个或多个相邻的 块级元素
(inline-block等是不会出现这种情况的) 的外边距相遇时,它们的外边距会合并(或称为折叠)成一个外边距。这种合并现象可能会导致元素之间的间距变得更大或更小,具体取决于合并外边距的规则。
外边距重叠的主要规则如下:
1.相邻的兄弟元素的外边距会发生重叠。
当一个块级元素的上边距和下边距都与相邻的兄弟元素的上边距和下边距相遇时,它们的外边距会合并成一个外边距,大小取两者中的较大值。
2.父元素和第一个/最后一个子元素的外边距会发生重叠。
当一个块级元素的上边距或下边距与其父元素的上边距或下边距相遇
时,它们的外边距会合并成一个外边距,大小取两者中的较大值。
- 没有内容将父元素和后代隔开
( 如果没有行内内容、边框(border)、内边距(padding)、也没有创建 BFC 或者清除浮动来分开块级父元素的上边距(margin-top)与其子元素的上边距(margin-top),此时就会发生外边距塌陷。下边距同理)
3.空的块级元素的上边距和下边距会发生重叠。
如果一个没有内容的块级元素(无实际高度)的上边距和下边距相遇
时,它们的外边距会合并成一个外边距,大小取两者中的较大值。
没有内容,空的:=》也就是没有设置边框 border、没有内边距 padding、没有高度 height、没有最小高度,或者内容被设定为 inline (inline没有height)或者清除浮动 clear-fix
外边距重叠可以在垂直方向上发生,水平方向上的外边距不会重叠。且只发生在块元素上,行内元素和行内块元素不会发生外边距重叠。
外边距重叠在页面布局中可能会产生一些意外的效果,特别是在垂直方向上。以下是一些常见的外边距重叠的示例:
/* 示例1:相邻的兄弟元素外边距重叠 */
.box1 {
margin-bottom: 20px;
}
.box2 {
margin-top: 30px;
}
/* box1和box2的外边距会重叠,最终外边距为30px */
<div class="box1"></div>
<div class="box2"></div>
/* 示例2:父元素和第一个/最后一个子元素外边距重叠 */
.parent {
margin-top: 40px;
}
.child {
margin-top: 50px;
}
/* parent和child的外边距会重叠,最终外边距为50px */
<div class="parent">
<div class="child"></div>
</div>
/* 示例3:空的块级元素外边距重叠 */
.empty-box {
margin: 60px 0;
}
/* 空的块级元素的上下外边距会重叠,最终外边距为60px */
<div class="empty-box"></div>
-
如果参与重叠的外边距中包含负值,重叠后外边距的值是最大的正边距与绝对值最大的负边距的和;
-
如果所有参与重叠的外边距都为负值,则重叠后外边距的值为最小负边距的值。
2.相邻元素如何消除外边距重叠影响
<div className='m-c2'></div>
<div className='m-c3'></div>
.m-c2 {
height: 100px;
width: 200px;
background-color: green;
margin-bottom: 50px;
display: inline-block;
}
.m-c3 {
height: 100px;
width: 200px;
background-color: yellow;
margin-top: 100px;
/* display: inline-block; */
/* float: left; */
/* position: absolute; */
}
1.给上方或者下方元素设置display:inline-block;
2.给下方元素设置float
3.给下方元素设置绝对定位属性position:absolute;
注意,float和absolute只能加给下方的元素,如果加给上面的元素,排版直接就出问题了。上方元素脱离文档流了。
3.父子元素如何消除外边距重叠影响
<div className='m-f1'>
<div className='m-c1'></div>
</div>
.m-f1 {
height: 600px;
width: 500px;
background-color: aqua;
margin-top: 100px;
/* display: inline-block; */.
/* padding: 1px; */
/* overflow: hidden; */
/* float: left; */
border: 1px solid red;
/* position: absolute; */
}
.m-c1{
height: 100px;
width: 200px;
background-color: rebeccapurple;
margin-top: 100px;
/* float: left; */
/* display: inline-block; */
/* position: absolute; */
}
上述代码中,所注释的每一行,为一种解决方法
1.给父元素加overflow:hidden;
小心子元素内容被隐藏。
2.父元素加border
这样父元素和子元素的外边距就有了border的距离间距,但是会增加额外的边框。
3.父元素或者子元素设置display:inline-block;
因为只有块元素才会出现外边距重叠,所以改成行内块。
4.父或子元素设置浮动
5.父或子元素设置绝对定位position:absolute
6.父元素增加padding
注意:overflow:hidden,border, padding,只有给父元素设置才有用。因为如果给子元素设置padding或者border,子元素的margin和父元素的margin依然接触在一起,没有被隔开。
4. 无内容元素如何消除外边距重叠影响
<div className='m-c3'></div>
<div className='m-c4'></div> //无内容子元素
<div className='m-c5'></div>
.m-c4 {
margin: 50px auto;
/* display: inline-block; */
/* padding: 1px; */
/* border: 1px solid red; */
/* height: 1px; */
/* min-height: 1px; */
}
/* .m-c4::after{
content: '占位内容';
visibility: hidden;
} */
1.给该元素设置display:inline-block;
2.给该元素设置padding,只有top、bottom起作用
3.给该元素设置border,只top、bottom起作用
4.给该元素设置height
5.给该元素设置min-height
6.给该元素设置个伪元素
22.编程题:找到二叉树中是否存在一条路径有满足节点值加起来为目标值。
大概说了说是回溯法,虽然面试官疯狂提示了但还是不会写5555
23.编程题:给n个异步操作,要求存入队列中然后按序执行。
有点懵,本来应该会的,但就是没想到,然后面试官告诉我是递归。
24.有看前端相关的书籍吗,怎么看的。
看了红宝书和阮一峰es6,说看到不会的再查,然后面试官说还是得全部看一遍。
25.你是怎么学习前端的。也是一面问过的。
26.通过什么方式去打基础。bilibili大学
27.平时有逛github吗,有去了解一些开源社区里面的一些前端最新方向吗?
没有,直接反问前端有什么方向。