前端面试笔记
1. CSS 题目
1.1 画等腰直角三角形
这题是美团前端面试一面题: 参考链接
第一种方法: 使用
boarder
- 第二种方法: 使用
linear-gradient
- 第三种方法: 使用
clip-path
1.2 不定宽高水平垂直居中
- 第一种方法: 使用
Flexbox
(也可以父元素是flex
然后子元素margin:auto
)
- 第二种方法: 使用
Grid
- 第三种方法: 绝对定位 +
transform
1.3 正方形子元素
- 这题是字节跳动一面题: 父元素的宽高不固定,并且可以随窗口变形,而子元素必须保持正方形 (只能使用
CSS
)- 解决方案: 子元素使用百分比宽度并使用
aspect-ratio
属性来保持子元素的正方形比例
- 解决方案: 子元素使用百分比宽度并使用
1.4 position 定位
position: static
(默认定位): 所有元素的默认定位方式。元素按照正常的文档流排列,不会受top
、right
、bottom
或left
等属性的影响position: relative
(相对定位): 相对于元素自身的原始位置进行定位,但可以通过top
、right
、bottom
、left
等属性调整元素的相对位置
position: absolute
(绝对定位): 相对于最近的非static
定位的父元素进行定位。如果没有非static
的父元素,则相对于文档的根元素(通常是<html>
或<body>
)进行定位
position: fixed
(固定定位): 相对于浏览器窗口进行定位,无论页面如何滚动,元素始终保持在浏览器窗口中的固定位置position: sticky
(粘性定位): 结合了relative
和fixed
的特性。元素一开始是相对于文档流定位的(relative
),当页面滚动到某个阈值时,元素会变为固定定位(fixed
)- 父元素不能
overflow:hidden
或者overflow:auto
属性 - 必须指定
top
、bottom
、left
、right
四个值之一,否则只会处于相对定位
- 父元素不能
1.5 盒模型
盒模型分为IE
盒模型和W3C
标准盒模型
- 在标准盒模型下,一个块的总宽度 =
width + margin(左右) + padding(左右) + border(左右)
- 在
IE
盒模型下,一个块的总宽度 =width + margin(左右)
(即width
已经包含了padding
和border
值)
当设置
box-sizing:content-box
时,采用标准盒模型计算,也是默认模式
当设置box-sizing:border-box
时,采用IE
盒模型计算
JS
如何获取盒模型对应的宽和高window.getComputedStyle(dom).width/height
: 取到的是最终渲染后的宽和高dom.getBoundingClientRect().width/height
: 得到渲染后的宽和高,还可以取到相对于视窗的上下左右的距离dom.offsetWidth/offsetHeight
: 包括高度(宽度)、内边距和边框,不包括外边距 (兼容性最好)
1.6 CSS 实现扇形
1.7 BFC
BFC
是CSS
布局的一个概念,是一块独立的渲染区域,是一个环境,里面的元素不会影响到外部的元素
2. JS 题目
2.1 var 与循环
- 字节跳动一面的题目
- 下面循环的最终结果是什么,
i
的最终值是什么,i
在循环结束后的值是什么
for (var i = 0; i < 5; i++) { |
这个循环将会输出
0 1 2 3 4
,i
的最终值为5
由于
var
声明的变量在整个函数(或全局范围内)都是共享的,它不受块作用域的限制。因此,i
在整个函数或脚本范围内是可见的。换句话说,i
仍然在循环结束后存在并且可以访问
for (var i = 0; i < 5; i++) { |
- 这个循环将会输出
5 5 5 5 5
,i
的最终值为5
- 由于
var
是函数级作用域,所有的setTimeout
回调函数在执行时访问的都是同一个i
,即循环结束时的i
值5
。因为setTimeout
是异步的,它会在1
秒后执行,此时循环早已结束,i
已经被更新为5
- 解决方案: 使用
let
或闭包可以解决这个问题,确保每次迭代时i
的值是独立的
2.2 手写倒计时
美团一面的题目
手写一个倒计时函数,要求输出
5 4 3 2 1
,每隔一分钟输出一个值方法一: 使用
setInterval
setInterval
会在指定的时间间隔内反复执行一个函数,直到手动清除这个定时器
function countdown() { |
- 方法二: 使用
Generator
函数- 使用
Generator
函数可以通过yield
暂停和恢复函数的执行,结合setTimeout
来实现倒计时
- 使用
function* countdownGenerator() { |
2.3 防抖和节流
可以使用类似
loadash
这种库来实现防抖
Debounce
- 防抖是指在事件被触发
n
秒后在执行回调,如果在这n
秒内时间又被触发,则重新计时 - 可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求
- 防抖是指在事件被触发
function debounce(fn, delay = 500) { |
防抖函数分为非立即执行版和立即执行版,需要根据不同的场景来决定需要使用哪一个版本的防抖函数
- 节流
Throttle
- 节流就是一定时间内执行一次事件,即使重复触发,也只有一次生效
- 可以使用在监听滚动
scroll
事件上,通过事件节流来降低事件调用的频率
const throttle = (fn, delay = 500) => { |
const throttle = (fn, delay = 500) => { |
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。时间戳版的函数触发是在时间段内开始的时候,而定时器版的函数触发是在时间段内结束的时候
2.4 undefined 与 null 的区别
undefined
表示”缺少值”,就是此处应该有一个值,但是还没有定义null
表示”没有对象”,即该处不应该有值
2.5 浅拷贝和深拷贝
- 浅拷贝
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝
- 如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
function shallowCopy(obj) { |
- 深拷贝
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
function deepCopy(obj, map = new WeakMap()) { |
2.6 函数柯里化
- 在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术
柯里化实际是把简单的问题复杂化,但是复杂化的同时,在使用函数时拥有了更加多的自由度。而对于函数参数的自由处理,正是柯里化的核心所在。柯里化本质上是降低通用性,提高适用性
- 参数定长的柯里化
- 假设存在一个原函数
fn
,fn
接受三个参数a, b, c
,那么函数fn
最多被柯里化三次
- 假设存在一个原函数
function curry(fn) { |
- 参数不定长的柯里化
- 如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数。同时要让
JS
引擎在解析得到的这个结果时,能求出预期的值
- 如果要支持参数不定长的场景,已经柯里化的函数在执行完毕时不能返回一个值,只能返回一个函数。同时要让
function curry(fn) { |
2.7 数组扁平化
实现扁平化的方法,封装
flatten
已有多级嵌套数组[1, [2, [3, [4, 5]]], 6]
将其扁平化处理,输出[1,2,3,4,5,6]
ES6 flat
flat(depth)
方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。使用Infinity
,可展开任意深度的嵌套数组
const arr = [1, [2, [3, [4, 5]]], 6] |
⭐ 直接使用自带的方法可以很快的实现, 但是面试官当然不希望就看到这些呀
- 循环递归
- 循环判断数组的每一项是否是数组
Array.isArray(arr[i])
- 是数组就递归调用扁平化代码
result = result.concat(flatten(arr[i]));
- 不是数组,直接通过
push
添加到返回值数组
- 循环判断数组的每一项是否是数组
function flatten(arr) { |
⭐⭐⭐ 使用递归写出数组扁平化, 但是缺少控制层级关系
- 增加参数控制扁平化深度
- 可以理解为手写
flat()
方法
- 可以理解为手写
// forEach 遍历数组会自动跳过空元素 |
⭐⭐⭐⭐ 使用递归写出数组扁平化, 可以通过参数控制层级关系
while
循环+some
方法- 通过
some
来判断数组中是否用数组, 通过while
不断循环执行判断, 如果是数组的话可以使用拓展运算符...
,...
每次只能展开最外层的数组, 加上contact
来减少嵌套层数
- 通过
function flatten(arr) { |
⭐⭐⭐⭐ 使用
while
循环取消递归操作, 巧用some
操作进行判断
2.8 typeof 判断
typeof null
是“object”
: 历史遗留问题typeof NaN
是“number”
:NaN
实际存储是一种特殊的数值类型typeof Function.prototype
是function
类型 | 结果 |
---|---|
Undefined | "undefined" |
Null | "object" |
Boolean | "boolean" |
Number | "number" |
BigInt | "bigint" |
String | "string" |
Symbol | "symbol" |
Function (class) | "function" |
其他任何对象 | "object" |
2.9 事件委托优化
- 字节一面题
举个例子,比如我们需要去做
Event Tracking System
,需要去记录用户在网站内做了什么,点击了什么按钮。如果有很多个按钮,每个都绑一个点击事件性能很差,如何优化?
- 绑定事件到父元素
- 而不是为每个子节点单独绑定事件处理程序,事件委托可以将事件处理程序绑定到公共的父元素上,然后通过事件的
target
来确定实际触发事件的子节点。
- 而不是为每个子节点单独绑定事件处理程序,事件委托可以将事件处理程序绑定到公共的父元素上,然后通过事件的
- 检查目标元素
- 使用事件对象的
event.target
属性来判断哪个子节点触发了事件,并根据需要处理相应的逻辑
- 使用事件对象的
<ul id="parent"> |
事件委托特别适用于存在大量类似元素的场景,如列表、表格中的行、动态生成的元素等
3. 网络安全
3.1 跨站脚本攻击 XSS
XSS
(跨站脚本攻击,Cross-Site Scripting
)是一种安全漏洞,攻击者通过向网页注入恶意脚本,使得当用户访问受感染的页面时,恶意脚本会在用户的浏览器中执行- 防止
XSS
攻击的关键是严格处理用户输入和输出 - 对用户输入进行严格验证和过滤,使用安全的框架和库
- 防止
3.2 跨站请求伪造 CSRF
- 跨站点请求伪造 (
CSRF
) 是一种前端安全攻击,通过伪造的形式来执行你原本不希望执行的操作- 防止
CSRF
攻击需要确保请求是合法的,并且是用户有意发起的 - 防止
CSRF
攻击的最简单方法之一是使用从服务器生成的CSRF
令牌。如果客户端无法提供准确的令牌,服务器可以拒绝请求的操作
- 防止
4. 网络协议
4.1 网络七层模型与四层模型区别
- 参考: 链接
- 网络七层模型
OSI
(Open Systems Interconnection Model
)是一个标准,而非实现
OSI
模型是从上往下的,越底层越接近硬件,越往上越接近软件,这七层模型分别是物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
> 物理层:底层数据传输,如网线;网卡标准。 |
- 网络四层模型是一个实现的应用模型,由七层模型简化合并而来
TCP/IP
模型将OSI
模型由七层简化为四层,传输层和网络层被完整保留,因此网络中最核心的技术就是传输层和网络层技术
4.2 http 和 https 基本概念
HTTP
: 是互联网上应用最为广泛的一种网络协议,是一个客户端和服务器端请求和应答的标准TCP
,用于从WWW
服务器传输超文本到本地浏览器的传输协议,它可以使浏览器更加高效,使网络传输减少HTTPS
: 是以安全为目标的HTTP
通道,简单讲是HTTP
的安全版,即HTTP
下加入SSL
层,HTTPS
的安全基础是SSL
,因此加密的详细内容就需要SSL
HTTPS
协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全。另一种就是确认网站的真实性
4.3 http 和 https 区别
HTTP
协议传输的数据都是未加密的,也就是明文的,因此使用HTTP
协议传输隐私信息非常不安全,为了保证 这些隐私数据能加密传输,于是网景公司设计了SSL
(Secure Sockets Layer
)协议用于对HTTP
协议传输的数据进行加密,从而就诞生了HTTPS
。简单来说,HTTPS
协议是由SSL+HTTP
协议构建的可进行加密传输、身份认证的网络协议,要比HTTP
协议安全
https
协议需要到ca
申请证书,一般免费证书较少,因而需要一定费用http
是超文本传输协议,信息是明文传输,https
则是具有安全性的ssl
加密传输协议http
和https
使用的是完全不同的连接方式,用的端口也不一样,前者是80
,后者是443
http
的连接很简单,是无状态的。https
协议是由SSL+HTTP
协议构建的可进行加密传输、身份认证的网络协议,比http
协议安全
注意:
HTTPS = HTTP + SSL/TLS
,如今SSL
已废弃,所以现在只关注HTTP + TLS
4.4 http1.x 和 http2.x 区别
http1.x
和http2.x
主要有以下4
个区别
HTTP2
使用的是二进制传送,HTTP1.X
是文本(字符串)传送- 二进制传送的单位是帧和流。帧组成了流,同时流还有流
ID
标示 - 优势: 传输速度更快 (二进制数据体积较小), 处理更高效 (不需要进行文本解析), 适用于复杂数据类型 (图像、音频、视频), 安全性更高 (二进制数据不易被直接阅读)
- 二进制传送的单位是帧和流。帧组成了流,同时流还有流
HTTP2
支持多路复用- 因为有流
ID
,所以通过同一个http
请求实现多个http
请求传输变成了可能,可以通过流ID
来标示究竟是哪个流从而定位到是哪个http
请求
- 因为有流
HTTP2
头部压缩HTTP2
通过gzip
和compress
压缩头部然后再发送,同时客户端和服务器端同时维护一张头信息表,所有字段都记录在这张表中,这样后面每次传输只需要传输表里面的索引ID
就行,通过索引ID
查询表头的值
HTTP2
支持服务器推送HTTP2
支持在未经客户端许可的情况下,主动向客户端推送内容
4.5 http 请求方式
http
请求方式有以下8
种,其中GET
和POST
是最常用的
GET
: 向特定的资源发出请求。GET
方法不应当被用于产生“副作用”的操作中POST
: 向指定资源提交数据进行处理请求, 例如提交表单或者上传文件。数据被包含在请求体中。POST
请求可能会导致新的资源的建立和/或已有资源的修改PUT
: 向指定资源位置上传其最新内容DELETE
: 请求服务器删除Request-URL
所标识的资源HEAD
: 向服务器索与GET
请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息OPTIONS
: 返回服务器针对特定资源所支持的HTTP
请求方法,也可以利用向web
服务器发送‘*’
的请求来测试服务器的功能性TRACE
: 回显服务器收到的请求,主要用于测试或诊断CONNECT
:HTTP/1.1
协议中预留给能够将连接改为管道方式的代理服务器
4.6 TCP 三次握手
5. React 题目
6. 缓存
6.1 强缓存与协商缓存
参考: 链接
为了减少资源请求次数,加快资源访问速度,浏览器会对资源文件如图片、
css
文件、js
文件等进行缓存,而浏览器缓存策略又分为强缓存和协商缓存强缓存
Strong Cache
- 所谓强缓存,可以理解为强制缓存的意思,即浏览器在访问某个资源时会判断是否使用本地缓存里已经存在的资源文件,使用本地缓存的话则不会发送请求到服务器,从而达到减轻服务器访问压力的作用,且由于直接从本地缓存读取资源文件,大大提高了加载速度
- 浏览器第一次请求远程服务器的某个资源时,如果服务器希望浏览器得到该资源后一段时间内不要再发送请求过来,直接从浏览器里的缓存里取,则服务器可以通过在响应头里设置
Cache-Control: max-age=31536000
,max-age
代表缓存时间,单位为秒,这里的数据换算过来就是一年,意味着在一年内浏览器不会再向服务器发送请求。
- 使用缓存的话,状态码
200
后面会标明情况。浏览器缓存资源的地方有两个: 磁盘缓存(disk cache
)和内存缓存(memory cache
)
- 当缓存时间到期后再次访问时,状态码
200
后面便没有括号内的内容了
一般来说,浏览器会将较大的资源缓存到
disk cache
,而较小的资源则被缓存到memory cache
里。内存缓存与磁盘缓存相比,访问速度要更快一些
- 强缓存除了使用
Cache-Control
实现之外,还可以使用Expires
字段,Expires
是Http1.0
规范,Cache-Control
是Http1.1
规范,Expires
返回一个具体的时间值,代表缓存的有效期,在该日期内浏览器不会向服务器发起请求,而是直接从缓存里获取资源
- 因为
Expires
参照的是本地客户端的时间,而客户端的时间是可以被修改的,所以会有误差产生的情况,这也是Expires
的一个缺点,所以有了后来Http1.1
规范的Cache-control
Cache-control
的优先级要高于Expires
,如果两者同时设置,会优先使用Cache-control
而忽略掉Expires
- 协商缓存
Negotiation Cache
- 在强缓存里,是否使用缓存是由浏览器来确定的,而协商缓存则是由服务器来告诉浏览器是否使用缓存资源,也就是浏览器每一次都要发送请求到服务器询问是否使用缓存
- 浏览器初次请求资源,服务器返回资源,同时生成一个
Etag
值携带在响应头里返回给浏览器,当浏览器再次请求资源时会在请求头里携带If-None-Match
,值是之前服务器返回的Etag
的值,服务器收到之后拿该值与资源文件最新的Etag
值做对比
如果没有变化则返回
304
,告诉浏览器继续使用缓存(不返回资源文件)
如果发生变化,则返回200
和最新的资源文件给浏览器使用
- 除了
Etag
外,还有一个Last-Modified
的属性,它是Http1.0
规范的,服务器返回Last-Modified
,浏览器请求头对应携带的是If-Modified-since
,与Etag
不同的是,Last-Modified
的值是一个时间值,代表文件的修改时间,服务器通过对比文件的修改时间是否发生改变来判断是否使用缓存
- 相比
Last-Modified
,Etag
优先级更高,使用上也更精确一些,因为有时候会存在文件内容并没有改变,但文件的修改时间变更了,Last-Modified
不一致所以服务器会重新返回资源文件,实际上还是可以继续使用缓存的
强缓存优先级大于协商缓存,即两者同时存在时,如果强缓存开启且在有效期内,则不会走协商缓存
7. 算法
7.1 LRU Cache
- 字节一面算法题
- 使用
JS
实现一个魔改版的LRU Cache
,并且满足以下要求
维护一个容量为
n
的缓存
每个缓存项如果在X
秒后没有被使用,则自动删除
- 可以使用
Map
来维护缓存的顺序和容量,利用setTimeout
来实现自动删除的功能
class LRUCache { |
7.2 格式化数字
字节一面算法题
给一个数字比如
1000000
,把它转化成1,000,000
。或者是1000000.12
,把它转化成1,000,000.12
。只能使用JS
实现解决方案一: 使用
toLocaleString()
function formatNumberWithCommas(number) { |
- 解决方案二: 正则表达式
function formateNumberWithCommas(number) { |
- 解决方案三: 手动实现格式化
function formatNumberWithCommas(number) { |
7.3 手写数组转树
- 做到类似下面的转换
let input = [ |
function arrayToTree(array) { |
7.4 数组去重
将
[1,1,2,2,3,3,4,4,5,5]
去重, 结果应该是[1,2,3,4,5]
方法一:
ES6
的Set
去重
const arr = [1,1,2,2,3,3,4,4,5,5]; |
Set
去重有一个弊端,无法去重引用类型的数据。比如对象数组[{a:1}, {a:1}]
- 方法二: 双重
for
循环去重
const handleRemoveRepeat = (arr) => { |
使用
len = arr.length
的原因: 假设这个循环需要循环10000
次,length
就会被执行10000
次
附录
文件还未上传 Github