HTML + CSS + JS
HTML、CSS、JavaScript 面试题
HTML
1. 回流和重绘
- 回流/重排(Reflow): 当页面中因为元素的规模尺寸、布局、隐藏等改变而需要重新构建布局时,就会触发回流。每个页面至少需要一次回流,就是在页面第一次加载的时候。
以下情况浏览器会触发回流:
- 页面初次渲染。
- 添加或删除可见的DOM元素。
- 元素位置改变。
- 元素尺寸改变(例如,边界大小、填充区域、外边距等)。
- 浏览器窗口尺寸变化。
- 获取元素的位置信息(clientWitdh、offsetTop...)
-
重绘(Repaint): 是指当页面中元素样式的改变并不影响它在文档流中的位置和布局时,浏览器会重绘这些元素(例如,color、background-color、visibility)。重绘不涉及元素位置的改变,因此成本低于回流。
-
回流与重绘的关系
- 回流必定会引发重绘,但重绘不一定会引发回流。
- 由于回流涉及到更多的计算,通常比重绘的成本更高。
- 减少回流方案
- 在一个操作中尽量合并多次对DOM的修改。
- 使用修改class操作代替多个样式的修改。
- 减少获取DOM元素的尺寸和位置信息次数。
- 使用CSS的transform属性实现动画效果。
- 使用文档片段(DocumentFragment):在一个离线容器中构建新的DOM结构,然后一次性将其添加到文档中。
- 为什么transform不触发回流
当应用transform变换时,浏览器将元素看作一个独立的图层。这意味着元素的几何属性(如宽度和高度)以及其在文档流中的位置保持不变,只是其外观被视觉上变化了。因此,这类变换不需要重新计算页面的布局,避免了回流的开销。
2. html5有哪些新特性
- 新的语义化标签:
<article>、<section>、<nav>、<header>、<footer>等 - 音频、视频:
<video>和<audio> - 画布API:
<canvas> - 本地存储:localStorage 和 sessionStorage
- 拖拽事件:drag 和 drop
3. 块级元素和行内元素
块级元素:
- 独占一行
- 可以设置宽高,margin 和 padding
- 例如:div、p、h1、h2、ul、li、header、footer
- display: block 设置成块元素
行内元素:
- 多个行内元素可以在同一行显示
- 无法设置宽高,垂直方向上的 margin 和 padding
- 例如:a、span、i、b
- display: inline 设置成行内元素
行内块元素:
- 可以和行内元素在同一行显示,但是可以设置宽高、margin和padding
- 例如:img、input、button
- display: inline-block 设置成行内元素
CSS
1. position
- relative: 相对定位,相对于自身定位
- absolute: 绝对定位,元素脱离文档流,相对于有定位的父级元素定位
- fixed: 固定定位,元素脱离文档流,相对于浏览器窗口定位
- static: 默认值
- sticky: 粘性定位,相对它的最近滚动祖先,基于 top、right、bottom 和 left 的值进行偏移
2. css让一个元素隐藏的几种方式
- display: none 隐藏元素,从文档流移除,不占用空间,会触发回流。
- visibility:hidden 隐藏元素,但是在文档布局中仍保留原来的空间。
- opacity: 0 元素还在页面,只是透明度为0,仍然可以触发事件。
3. 如何理解CSS的盒子模型?
标准盒模型:元素的总宽度 = width + border + padding
IE盒模型:元素的总宽度 = width (width包含内容宽度 + border + padding)
设置标准盒模型:box-sizing: content-box;
设置IE盒模型:box-sizing: border-box;
4. 如何让一个元素水平垂直居中
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.parent {
display: flex; /* 给父级元素添加 flex */
justify-content: center; /* 主轴居中 */
align-items: center; /* 交叉轴居中 */
}
5. CSS优先级
- 总结排序:!important > 行内样式 > ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性
- 多个选择器的话权重值相加,权重值大的优先级高
- a 标签字体颜色不会继承父级
6. CSS3的新特性
-
transtion 过渡
-
transform 变形,该属性不会改变页面布局,不会触发回流
-
属性值: rotate 旋转、translate 移动、scale 缩放、skew 倾斜
-
animation 动画
-
@keyframes 定义动画
-
@font-face 自定义字体
-
背景渐变色 线性渐变(linear-gradient)和径向渐变(radial-gradient)
-
flex布局
7. Flex布局
**作用:****给子元素提供空间分配和对齐能力,**方便实现子元素在垂直和水平方向的各种布局
- 容器的属性
- flex-direction 决定主轴的方向(即项目的排列方向)
- row(默认值):主轴为水平方向,起点在左端。
- row-reverse:主轴为水平方向,起点在右端。
- column:主轴为垂直方向,起点在上沿。
- column-reverse:主轴为垂直方向,起点在下沿。
flex-wrap 是否换行
- nowrap(默认):不换行。
- wrap:换行,第一行在上方
- wrap-reverse:换行,第一行在下方
justify-content 主轴上的对齐方式
- flex-start(默认值):左对齐
- flex-end:右对齐
- center:居中
- space-between:两端对齐,项目之间的间隔都相等。
- space-around:每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
align-items 交叉轴对齐方式
- flex-start:交叉轴的起点对齐。
- flex-end:交叉轴的终点对齐。
- center:交叉轴的中点对齐。
- baseline: 项目的第一行文字的基线对齐。
- stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
align-content 多根轴线的对齐方式
- flex-start:与交叉轴的起点对齐。
- flex-end:与交叉轴的终点对齐。
- center:与交叉轴的中点对齐。
- space-between:与交叉轴两端对齐,轴线之间的间隔平均分布。
- space-around:每根轴线两侧的间隔都相等。所以,轴线之间的间隔比轴线与边框的间隔大一倍。
- stretch(默认值):轴线占满整个交叉轴。
- 子元素 flex 属性
flex-grow:分配剩余空间的比例
flex-shrink:元素是否收缩,默认为 1: 收缩, 0: 不收缩
flex-basis:元素在主轴方向的初始大小,类似 width 和 height 的作用 clientWidth
flex:1; 复合属性:包含 flex-grow: 1; flex-shrink: 1; flex-basis: 0%;
8. CSS中的BFC
BFC定义:BFC页面中的一种CSS渲染模式,指一个独立的渲染区域。在这个区域内的元素布局不会影响到区域外的元素。
BFC的特性:
-
bfc就是页面上的一个独立容器,容器里面的子元素不会影响外面元素。
-
bfc的区域不会与float的元素区域重叠。
-
计算bfc的高度时,浮动元素也参与计算,可以清除浮动。
-
属于同一个BFC的两个相邻 Box 的 margin 会发生重叠。
-
解决 margin 塌陷问题可以把元素放在不同的 BFC 元素中
形成BFC的条件:
- 浮动元素,float 除 none 以外的值。
- 定位元素,position(absolute,fixed)。
- display: inline-block/flex/grid。
- overflow:(hidden,auto,scroll)。
9. 伪类和伪元素
伪类:用于选择处于特定状态或者位置的元素。
- **:**hover — 当用户鼠标悬停在元素上时应用样式。
- :focus — 当元素获得焦点时应用样式
- :nth-child() — 匹配属于其父元素的第n个子元素的元素。
- :first-child, :last-child — 分别匹配父元素的第一个和最后一个子元素。
伪元素:用于在元素内容中添加一些内容或者样式,例如添加图标。
- ::before — 在元素内容的前面插入内容。
- ::after — 在元素内容的后面插入内容。
10. 实现左侧固定,右侧自适应排版
11. 左右固定,中间自适应布局
移动端
1. css 单位
- px: 固定单位;
- vw:100vw === 屏幕宽度;
- vh:100vh === 屏幕高度;
- em:em 是指相对于父元素的字体大小的单位;
- rem:rem 是指相对于根元素的字体大小的单位, 如果 html的font-size: 100px,页面中1rem ==100px
2. 移动端适配
- flex 布局;
- 使用 @media 媒体查询,根据不同的屏幕大小展示不同的样式;
- css 中使用 rem 单位布局,根据屏幕宽度动态修改 html 的 font-size,实现等比例缩放。
// 1. 设定标准屏幕下 1rem 的基准值
// 2. 假定设计图宽度是 750px
// 3. 定义基准值:750屏幕下 1rem === 100px
// 1rem 等于多少都可以,定100px是为了好计算
// 比如设计图一个元素宽度为 50px 时,代码可以写 width: 0.5rem
function setRem() {
const baseSize = 100; // 基准大小为 100px
const designWidth = 750; // 设计稿宽度
const width = window.innerWidth; // 获取浏览器窗口的宽度
// 计算当前页面宽度相对于设计稿宽的缩放比例,并乘以基准值
// 计算出当前屏幕应该设置多少font-size
const currentSize = width / designWidth * baseSize;
document.documentElement.style.fontSize = currentSize + 'px';
}
setRem()
html {
/* 750屏幕下 1rem === 10px */
/* 因为750屏幕下 100vw === 750px,所以 font-size: 750px / 75 = 10 */
font-size: calc(100vw / 75);
}
.box {
/* 750 屏幕下显示 100px */
width: 10rem;
height: 10rem;
}
- 使用 scss + vw 单位布局
/* 设计稿宽度 */
$designWidth: 750;
/* px转vw的函数 */
@function px2vw($px) {
/* 标准屏幕:750px */
/* 750px === 100vw */
/* 1px === 100vw / 750 计算 1px 等于多少 vw */
@return 100vw / designWidth * $px
}
.box {
/* 元素占满屏幕宽度 */
width: px2vw(750);
/* 屏幕 750 时高度显示 100px */
height: px2vw(100);
}
3. 移动端1像素问题
CSS中的1px并不等于设备的1像素
屏幕 375x667
分辨率 375x667:宽1px === 1设备物理像素
分辨率 750x1334:宽1px === 2设备物理像素
在 750x1334 的设计图下 1px 代码中应该改为 0.5px才能显示1物理像素
需要根据屏幕不同的分辨率来进行对应的缩放才可以实现设计图中的1px,可以使用伪类实现
4. 如何用CSS实现0.5px的边框
- 步骤一:创建伪元素去实现;
- 步骤二:给伪元素设置为宽高为元素本身的2倍;
- 步骤三:用 CSS3 的 transform 缩小一半;
.half-border {
position: relative;
border: none;
}
.half-border:before {
content: '';
position: absolute;
width: 200%;
height: 200%;
top: 0;
left: 0;
border: 1px solid black;
transform: scale(0.5);
transform-origin: 0 0;
box-sizing: border-box;
}
JavaScript
1. JavaScript 数据类型
- 基础类型: string、number、boolean、null、undefined、Symbol
- 引用数据类型:object、array、function
区别:基础类型存在栈内存,引用类型存在堆内存
null,undefined 的区别
- undefined 表示不存在这个值,例如变量被声明了,但没有赋值时,就等于 undefined
- null 表示一个对象被定义了,值为“空值”
2. 判断变量的类型
- typeof
- 返回一个表示数据类型的字符串,返回结果包括7种:
- "number"、"boolean"、"string"、"symbol"、"object"、"undefined"、"function"。
- 缺陷:typeof 无法判断数组和null,都返回 object
- instanceof
- instanceof 运算符是用来判断变量是否为类的实例,
- 表达式为:变量 instanceof 类 返回布尔值
- 缺陷:只要变量的原型链上有此类,返回结果都为true
- 根据原型判断
- Object.prototype.toString.call([]) === '[object Array]'
- Object.prototype.toString.call({}) === '[object Object]'
- Object.prototype.toString.call(null) === '[object Null]'
- Object.prototype.toString.call('') === '[object String]'
- Object.prototype.toString.call(1) === '[object Number]'
- Object.prototype.toString.call(true) === '[object Boolean]'
3. 如何判断一个变量是对象还是数组
- Array.isArray(变量)
- 变量 instanceof Array
- Object.prototype.toString.call(变量) === '[object Array]'
3.1. 如何判断一个对象是否为空对象
- 转成数组判断数组长度
**Object.keys(对象).length === 0** - 转成 json 字符串判断
**JSON.stringify(对象) === '{}'**
3.2. 如何判断一个对象中是否存在某一个属性
**对象.hasOwnProperty('属性名')**此方法返回一个布尔值
4. 闭包
定义:闭包是指有权访问另一个函数作用域中的变量的函数
- 闭包用途:
- 能够访问函数定义时所在的作用域中的变量
- 私有化变量
- 模拟块级作用域
- 应用场景:
- react中的hook
- 函数中使用定时器
- 回调函数处理异步
- 防抖、节流
- 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏
- 解决方案:变量没用时赋值为null
5. 哪些操作会造成内存泄漏?
- 不合理的闭包函数(闭包中存的变量在执行完后没有被释放)
- 循环引用(两个对象相互引用)
- 定时器未清除
- 绑定原生事件未清除
6. 垃圾回收机制
在浏览器中,垃圾回收器会定期找出那些不再继续使用的变量,然后释放其占用的内存。浏览器的垃圾回收机制通常通过两种方式实现:
- 标记清除
- 垃圾回收器遍历所有正在使用的对象添加标记
- 完成标记之后,垃圾回收器再次遍历堆中的所有对象,并清除那些没有被标记的对象。
- 引用计数
- 跟踪每个对象被引用的次数。
- 当对象的引用计数变为零时,表示没有任何变量指向这个对象,因此它可以被垃圾回收。
7. 原型、原型链
原型: 对象有个隐藏属性 proto 属性,该属性指向构造函数的 prototype 原型属性,原型的主要用途是允许对象共享方法和属性。
原型链: 当访问一个对象的属性或方法时,JavaScript首先在对象本身查找。如果没有找到,它会沿着该对象的原型链向上查找,直到找到该属性或方法为止。如果在原型链的顶端(Object.prototype)仍然没有找到,那么结果就是 undefined。
注意:原型连的顶端 Object.prototype.proto 返回 null
8. new操作符具体干了什么呢?
- 首先创建了一个新的空对象
- 设置原型,将对象的原型设置为函数的prototype对象
- 让函数的this指向这个对象,执行构造函数的代码
- 判断函数的返回值类型,如果是基础类型,返回新创建的对象,如果是引用类型,就返回这个引用类型的对象
9. ==和===区别是什么?
两个等号
- 允许不同数据类型之间的比较;
- 如果是不同类型的数据进行,会默认进行数据类型之间的转换;(大部分转换成数字)
- 如果是对象数据类型的比较,比较的是空间地址
三个等号:只要数据类型不一样,就返回false
10. 事件模型
触发事件会经历三个阶段:捕获阶段、目标阶段、冒泡阶段
- 冒泡事件:当你使用事件冒泡时,子级元素先触发,父级元素后触发
- 捕获事件:当你使用事件捕获时,父级元素先触发,子级元素后触发
- 阻止冒泡:stopPropagation()
11. JavaScript 事件代理(事件委托)
事件代理又称之为事件委托。“事件代理”即是把原本需要绑定在子元素的响应事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡。
事件委托的优点:
【1】可以大量节省内存占用,减少事件注册,比如在ul上代理所有li的click事件
【2】可以实现当新增子对象时无需再次对其绑定(动态绑定事件)
12. this指向
- 全局作用域下的this指向window
- 如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素
- 函数中的this,要看哪个对象调用的函数,哪个对象调用该函数this就指向该对象,如果没有对象调用就指向window (自执行函数和定时器中的函数 this 指向 window)
- 构造函数中的this指向当前的实例
- call、apply、bind可以改变函数的this指向
- 箭头函数中没有自己的this,如果使用this,就会输出箭头函数定义时所在的作用域中的this
13. bind,apply,call的区别
-
apply**:**
**fn.apply(对象, [参数1,参数2,...])** -
立即执行函数,函数中的 this 指向第一个参数,函数原本的参数通过数组传入
-
call**:**
**fn.call(对象, 参数1,参数2,...)** -
立即执行函数,函数中的 this 指向第一个参数,函数原本的参数通过逗号分隔依次传入
-
bind:
**fn.bind(对象)( 参数1,参数2,...)** -
返回改变 this 指向后的新函数
14. Ajax的四个步骤
- 创建ajax实例
- 执行open 确定要访问的链接 以及同步异步
- 监听请求状态
- 发送请求
const ajax = new XMLHttpRequest()
ajax.open('请求方式','接口地址',布尔值(true表示异步))
ajax.send() // post请求的话,可发送字符串或json格式的数据
ajax.onreadystatechange = function(){
if(ajax.readyState === 4){ // 4表示请求成功,数据彻底返回
console.log(ajax.responseText) // 返回的数据,是字符串类型,需自己转换为json对象
}
}
ajax.readyState属性:表示当前连接的状态(指浏览器与服务器的连接状态)
0:(未初始化)表示没有建立连接,此时还没调send方法
1:(载入)表示建立连接成功,此时已调用send方法正在发送请求
2:(载入完成)表示浏览器开始接收服务器的数据,此时send方法执行完毕,已接收全部响应内容
3:(交互)表示浏览器正在接收数据,此时正在解析响应内容
4:(完成)表示浏览器的数据接收完成,也就表示本次请求结束,响应内容解析完成
15. 深拷贝和浅拷贝
浅拷贝:浅拷贝只复制对象的第一层属性。如果对象的属性值是基本类型,拷贝的是值本身;如果属性值是复杂类型(如Array、Object),则拷贝的是引用地址。改变引用类型的值会互相影响。
- 方式1: const obj2 = {...obj1}
- 方式2: const obj2 = Object.assign({}, obj1)
深拷贝:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。
-
方式1: 转成 json 字符串再转成对象
**JSON.parse(JSON.stringify(对象))** -
缺陷:对象必须符合json格式,不可以拷贝函数、正则对象、日期对象和循环引用的对象
-
**方式2:递归拷贝(社区方案如:lodash.**cloneDeep(value)等)
16. 跨域
定义:跨域是指在浏览器中,当前网页的协议、域名、端口与请求的资源的协议、域名、端口不一致时,就会发生跨域问题。
原因:跨域问题是由于同源策略造成的,浏览器为了保障用户的安全,采取了同源策略,发送 ajax 请求时只能访问和当前页面的协议、域名、端口一致的接口。
常见的解决跨域问题的方式包括:
1、服务器设置CORS
原理:服务器设置 Access-Control-Allow-Origin(允许跨域访问的地址) 响应头之后,浏览器将会允许跨域请求
2、请求代理:本地开发代理配置 webpack 中的 proxy 属性,线上代理使用 nginx 代理
原理:跨域是浏览器产生的问题,服务端之间通讯不存在跨域问题,前端页面与本地服务端都属于同一域名,前端页面将请求发送给本地服务端,服务端代为请求第三方接口并将响应返回给前端。
3、JSONP跨域(只能解决get请求,不常用)
原理:利用 <script> 标签可以跨域加载资源的特性,动态创建一个 <script> 标签,其 src 属性指向服务端的资源,服务端返回一段 JavaScript 代码,这段代码会执行一个回调函数,并将数据作为参数传递给回调函数。通过回调函数来实现数据的传递。
17. 异步加载JS的方式有哪些?
- 动态创建 script:
document.createElement('script') - XmlHttpRequest 脚本注入
- script 标签添加 async 属性
<script async src="./index.js"> - script 标签添加 defer 属性
<script defer src="./index.js">
| 行为 | 没有 async 或者 defer | async | defer |
|---|---|---|---|
| 加载 | 立即加载 | 和后续文档并行加载 | 和后续文档并行加载 |
| 执行 | 立即执行 | 加载完立即执行 | 所有元素解析完之后 DOMContentLoaded 之前执行 |
| 顺序 | 顺序执行 | 哪个先加载完执行哪个 | 顺序执行 |
18. js继承方式有哪些?
18.1. 原型链继承
原理:通过使子类的原型对象等于父类的一个实例来实现继承。
缺点
- 所有子类型实例共享父类型的属性。
- 创建子类型实例时,不能向父类型的构造函数传递参数。
// 父类
function Parent() {
this.parentProperty = true;
}
Parent.prototype.getParentProperty = function() {
return this.parentProperty;
};
// 子类
function Child() {
this.childProperty = false;
}
// 设置 Child 的原型为 Parent 的一个实例
Child.prototype = new Parent();
var child = new Child();
console.log(child.getParentProperty()); // true
18.2. 构造函数继承(借用构造函数)
原理:通过在子类型构造函数的内部调用父类型构造函数实现继承
优点
- 避免了子类共享父类实例属性的问题。
- 可以在子类型构造函数中向父类型构造函数传递参数。
缺点
- 继承父类的方法都在构造函数中定义,每次创建实例都会创建一遍方法。
- 只能继承父类的实例属性和方法,无法继承父类原型上的属性和方法。
function Parent(name) {
this.name = name;
}
function Child(name) {
// 子类中调用父类的构造函数
Parent.call(this, name);
}
var child = new Child("Alice");
console.log(child.name); // Alice
18.3. 组合继承
原理:组合继承结合了原型链和借用构造函数的技术,将两者的优点结合起来。
优点
- 可以向父类型构造函数传递参数。
- 避免了原型链继承中子类型实例共享父类型引用属性的问题。
- 父类型的方法可以被复用。
缺点
- 调用了两次父类型构造函数(一次在设置子类型原型时,一次在子类型构造函数内部)。
// 父类
function Parent(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.sayName = function() {
console.log(this.name);
};
// 子类
function Child(name, age) {
Parent.call(this, name); // 第二次调用 Parent 继承父类的实例属性
this.age = age;
}
Child.prototype = new Parent(); // 第一次调用 Parent 继承父类的原型属性
Child.prototype.constructor = Child; // 需要修复构造函数的指向
Child.prototype.sayAge = function() {
console.log(this.age);
};
var child1 = new Child("Alice", 10);
19. Cookie、WebStorage的区别
cookie:
- 大小限制:通常每个cookie限制为4KB。
- 生命周期:可以设置失效时间;如果不设置,默认为浏览器会话结束时删除。
- 发送到服务器:每次HTTP请求时,所有的cookie都会自动添加到请求头中发送到服务器。
- cookie 数据还有路径的概念,可以限制cookie只属于某个路径下
- 可以通过设置 Cookie 的 HttpOnly 属性来防止前端通过 JavaScript 获取 Cookie。当 HttpOnly 属性设置为 true 时,该Cookie将只能通过HTTP请求发送到服务器,而无法通过JavaScript访问。
localStorage:
- 大小限制:一般为5MB。
- 生命周期:数据在本地持久保存,除非被手动删除。
sessionStorage:
- 大小限制:一般为5MB。
- 生命周期:数据在页面会话期间可用,关闭页面数据清除。
相同点:
- 都是存在浏览器端并且都是同源的,不能跨域访问。
- 只能存储字符串。
20. Cookie的属性
- Expires/Max-Age:定义了Cookie的生命周期。Expires设置一个过期日期和时间,之后Cookie会被自动删除。Max-Age设置Cookie存在的秒数,从Cookie被创建时开始计算。同时指定Expires和Max-Age时 Max-Age 优先级更高。
- Domain:定义了哪些域名可以接收Cookie。如果设置为**.baidu.com**,则所有以baidu.com结尾的域名都可以访问这个Cookie。
- Path:定义了哪些路径下的页面可以请求Cookie。如果设置为**/abc**,只有**/abc** 目录及其子目录下的页面可以访问这个Cookie。
- HttpOnly:设置该属性后,此条 Cookie 将只能通过 HTTP 请求发送到服务器,前端无法通过 js 获取此条 Cookie。
21. 作用域
- 全局作用域:在最外层定义的变量和函数所在的作用域。
- 函数作用域
- 块级作用域(var没有块级作用域)
作用域链当在代码中引用一个变量时,JavaScript首先在当前作用域中查找该变量,如果找不到,则会沿着作用域链向上查找,直到在某个作用域中找到该变量或达到全局作用域。这个过程被称为作用域链的遍历。(全局还没有找到就报错)
22. 事件循环机制(宏任务微任务 | event loop)
- 执行主代码块同步任务(主代码块是宏任务)
- 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
- 执行过程中如果遇到宏任务,就将它添加到宏任务的任务队列中
- 同步代码执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行,先进先出)
- 当前微任务队列清空后,开始下一个宏任务(开始下一个循环) ![[事件循环.png]] 宏任务:包括整体代码 script,setTimeout,setInterval,ajax请求,UI事件。
微任务:promise中的then、catch、await 下边的代码
23. 从输入url到获取页面的完整过程
- 查询DNS(域名解析),获取域名对应的IP地址 ,查询浏览器缓存;
- 浏览器与服务器建立tcp链接(三次握手);
- 浏览器向服务器发送http请求(请求和传输数据);
- 服务器接受到这个请求后,给浏览器返回 html 代码;
- 浏览器拿到完整的html页面代码开始解析和渲染;
- 解析过程中如果遇到的 css 或者 js 还是相同的流程从服务端获取代码;
- 浏览器根据拿到的资源对页面进行渲染。
渲染过程:
- 将 html 代码按照深度优先遍历来生成DOM树;
- css 文件下载完后也会进行渲染,生成相应的CSS树;
- 当所有的 css 文件下载完且所有的 CSS树 构建结束后,就会和DOM一起生成渲染树;
- 浏览器根据渲染树的内容和样式,计算出每个元素在页面中的位置和大小;
- 最后,通过绘制环节将所有的节点内容呈现到屏幕上。
24. 为什么外链css要放在头部,js要放在尾部
为什么外链css要放在头部?
- 首先整个页面展示给用户会经过html 的解析与渲染过程。
- 而外链css无论放在html的任何位置都不影响html的解析,但是影响html的渲染。
- 如果将css放在尾部,html的内容可以第一时间显示出来,但是会阻塞html行内css的渲染。
- 如果将css放在头部,css的下载解析是可以和html的解析同步进行的,放到尾部,要花费额外时间来解析CSS,并且浏览器会先渲染出一个没有样式的页面,等CSS加载完后会再渲染成一个有样式的页面,页面会出现明显的闪动的现象。
js要放在尾部?
- 因为当浏览器解析到script的时候,就会立即下载执行,中断html的解析过程,如果外部脚本加载时间很长(比如一直无法完成下载),就会造成网页长时间失去响应。
25. 数组常见方法
- 会改变原数组:push、pop、shift、unshift、splice、sort、reverse
- join、concat、slice、indexOf
- map、forEach、filter、some、every、reduce、find、findIndex
map 和 forEach 区别:
- map返回一个新数组,新数组的值由传给map的函数的返回值组成
- forEach没有返回值
26. 遍历数组的方式
- for 循环
- map、forEach、filter、find、findIndex、every、some
27. 遍历对象的方法
- Object.keys(对象) ===> 返回由对象的 key 组成的数数组
- Object.values(对象) ===> 返回由对象的 value 组成的数组
- Object.entries(对象) ===> 返回由对象的 key 和 value 组成的二维数组
- for ... in
28. for of 和 for in
- for...in 循环语句是用于遍历对象的属性名称,遍历数组时是遍历的下标,原型上的属性和方法也会遍历。
- for...of 循环语句是用于遍历可迭代对象的值的。它可以遍历数组、字符串、Map、Set 等对象类型,但不能遍历普通对象。遍历的是对象的值本身,而不是属性名或索引。
29. 0.1 + 0.2 === 0.3?
结果:在 JavaScript 中,0.1 + 0.2 === 0.3 的结果是 false。
原因:这是由于 JavaScript 使用 IEEE 754 双精度浮点数(64位)表示数字,0.1 和 0.2 在二进制中无法精确表示。当它们相加时,结果并不是精确的0.3,而是一个非常接近0.3的值。
解决办法:转换为整数计算然后在转成小数
function add(num1, num2) {
//num1 小数位的长度
const num1Digits = (num1.toString().split('.')[1] || '').length;
//num2 小数位的长度
const num2Digits = (num2.toString().split('.')[1] || '').length;
// 取最大的小数位作为10的指数
const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
// 把小数都转为整数然后再计算
return (num1 * baseNum + num2 * baseNum) / baseNum;
}
Es6语法
1. es6的新特性
- let、const
- 解构赋值
- 扩展运算符
- 箭头函数
- Promise
- async和await
- class
- for of
- Set对象:Set对象的值是唯一的,不会有重复值,利用这个特性可以用来数组去重
- Map对象:键值对集合,以 key 和 val 来的形式存数据
1.1. Map对象和普通对象的区别:
- 键的类型:普通对象的 key 只能是字符串,map 对象的 key 可以是任何类型;
- 属性顺序:普通对象的属性没有固定的顺序,而Map的键值对是按照插入顺序保持的。
1.2. WeakMap和Map对象的区别:
- WeakMap 对象的 key 必须是引用类型
- WeakMap 对象的 key 是弱引用,当键对象在代码中没有其他引用时,自动被垃圾回收,键值对自动销毁
- WeakMap 对象不可遍历,没有任何获取所有成员的方法,只能通过已知的键获取对应值。
2. var、let、const区别
-
let 和 const 没有变量的提升,必须先声明后使用,同一作用域不可以重复声明
-
let 和 const 有块级作用域
-
const 定义常量
-
const 声明变量的同时必须赋值,const 声明的变量必须初始化,一旦初始化完毕就不允许修改
-
const 定义的对象\数组内的属性值可以修改,但是变量的引用地址不能修改
3. 箭头函数的特点
- 箭头函数没有自己的 this,他的 this 指向定义时的父级作用域的this
- 不能作为构造函数的,不能被new
- 箭头函数没有 arguments 实参集合,使用 ...剩余运算符获取实参集合
- 箭头函数没有函数原型
- 不能使用call、apply、bind改变箭头函数中this指向
4. async await函数
作用:async/await 使异步代码更像同步代码
- async/await 是基于 promise 实现的
- await 只能 在 async 函数中使用,不能再普通函数中使用
- async函数返回一个 promise 实例
- 使用 try {} catch (e) {} 捕获 async 函数中的失败情况
5. Promise
5.1. Promise 作用
-
Promise 是异步编程的一种解决方案,支持链式调用:
-
Promise 对象有三种状态:pending(进行中)、fulfilled(成功)和 rejected(失败)。
-
状态一旦改变就不会再发生改变。Promise 对象的状态改变,只有两种可能:
-
调用 resolve(): pending => fulfilled
-
调用 reject(): pending => rejected
-
状态改变后执行 .then 方法,then 接受两个函数作为参数,
-
当 promise 对象的状态变成 fulfilled 会执行第一个参数
-
变成 rejected 会执行第二个参数,如果 then 没有传第二个参数就会报错然后执行到 .catch
-
.then 会返回一个新的 promise 对象,可以链式调用
-
.catch 处理兜底逻辑,捕获报错
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value) // 状态变成 fulfilled 执行 then
} else {
reject(error) // 状态变成 rejected 执行 catch
}
})
promise
.then(value => {
// 成功
})
.catch(e => {
// 失败
})
5.2. Promise 的静态方法
-
Promise.all:
-
Promise.all 方法用于将多个 Promise 实例,包装成一个新的 Promise 实例,接收一个数组作为参数
-
当这个数组中的所有 Promise 对象全部变为 fulfilled 时,Promise.all 状态返回 fulfilled,执行then 函数,then 中获取到的结果是一个数组
-
数组中只要有一个 promise 对象变成 rejected ,Promise.all 状态返回 rejected
Promise.all([p1, p2, p3])
.then(function(res) {
// p1, p2, p3 都成功执行then,res === [p1结果,p2结果,p3结果]
})
.catch(e => {
// p1, p2, p3只要有一个失败就执行 catch
})
-
Promise.race
-
接收一个数组作为参数,只要其中一个 Promise 先完成,无论成功或失败,Promise.race 的状态就立即跟着改变。
// p1, p2, p3 只要有一个完成就改变状态,成功的话执行then,失败执行catch
Promise.race([p1, p2, p3])
.then(function(res) {
// 最先执行完成并且成功执行then
})
.catch(e => {
// 最先执行完成并且失败被执行catch
})
-
Promise.allSettled
-
接收一个数组作为参数,等待数组中所有的 Promise 对象状态改变后执行 then,无论成功或失败;
-
then 中获取到的结果是一个对象数组。
Promise.allSettled([
Promise.resolve(33),
new Promise(resolve => setTimeout(() => resolve(66), 0)),
Promise.reject(new Error('an error'))
]).then(results => {
console.log(results)
// results 的值
// [
// { status: "fulfilled", value: 33 },
// { status: "fulfilled", value: 66 },
// { status: "rejected", reason: Error: an error }
// ]
});
- Promise.resolve 把参数包装成 Promise 对象,如果参数是 Promise 就直接返回,不是就返回一个成功状态的 Promise 对象
- Promise.reject 返回一个失败状态的 Promise 实例
服务器端
1. GET和POST请求的区别
-
传参方式:
-
GET 发送数据是存放在 url 的 ? 之后,发送多个数据时参数之间以 & 相隔
-
POST 方法是把提交的数据放在 Http 的 Body 中
-
传参大小限制:
-
GET 提交的数据大小有限制,因为浏览器对url的长度有限制
-
POST 的方法通过 body 提交的数据没有大小限制,适用于大量数据的提交
-
参数可见性
- GET 请求参数在 URL 中完全可见。
- POST 请求参数在 HTTP 请求的Body中,对用户来说不可见。
-
缓存:
-
GET 请求的结果通常被浏览器缓存
-
POST 通常不被缓存(除非在响应头明确指示需要缓存)
2. options 预检请求
-
浏览器发送跨域请求时,如果是复杂请求就会先发送一个 options 预检请求
-
简单请求:请求方式为 get、post 并且没有设置额外的请求头
-
复杂请求:
-
put、delete请求
-
请求头中包含自定义请求头,例如:authorization
3. http 和 https的区别
- 安全性
- HTTP:数据在传输过程中以明文形式发送。
- HTTPS:HTTPS在数据传输过程中增加了加密处理,通常使用SSL/TLS协议对数据进行加密。防止数据被窃听和篡改。
- 端口
- HTTP:默认使用80端口。
- HTTPS:默认使用443端口。
- 成本
- HTTPS:需要购买和维护SSL证书
4. 常见状态码
- 200 请求成功
- 301 永久重定向
- 302 临时重定向
- 304 协商缓存
- 401 用户信息失效、登录失效
- 403 服务器拒绝请求、用户没有权限访问
- 404 服务器找不到请求的资源
- 500 服务器遇到错误,无法完成请求
- 502 网关错误
- 400请求参数错误、格式不符。检查请求头、体格式、参数类型、JSON语法是否正确
- 405请求方法错误。
- 408请求超时。优化网络环境,增加超时时间(如Axios设置timeout)
- 429频繁请求被限流
- 503服务不可用(如维护中)
- 504网关超时。检查后端服务响应速度,或优化接口性能
5. 常见的请求头
- Content-Type:请求的内容类型,告诉服务器发送的数据的格式,以便服务器能够正确解析。
- application/x-www-form-urlencoded
- multipart/form-data
- application/json
- Cookie:向服务器发送存储在客户端的cookie。
- User-Agent:包含了关于客户端的信息,让服务器能识别客户端使用的操作系统、浏览器和版本
- Referer:指示请求来源的页面的地址。这可以用于分析、日志记录等。
- Cache-Control:指定请求和响应遵循的缓存机制
- no-store:完全禁止缓存,每次请求都会发送到服务器。
- max-age=多少秒:指定一个时间长度,在这个时间段内,直接读取缓存的数据。
6. 常见的响应头
- Access-Control-Allow-Origin:指明哪些网站可以跨域访问。
- Content-Type:响应体的数据类型,如text/html、application/json等。
- Content-Length:响应体的长度。
- Set-Cookie:设置Cookie
- Cache-Control:控制数据缓存。
- no-store:完全禁止缓存,每次请求都会发送到服务器。
- max-age=多少秒:指定一个时间长度,在这个时间段内,直接读取缓存的数据。
- Expires:缓存的过期时间。
- Last-Modified:资源的最后修改日期和时间。
7. http 缓存
7.1. 强缓存
-
强缓存如果命中不会向服务器发送请求,直接读取本地数据;
-
状态码返回 200 (from memory cache) 或者 200 (from disk cache)
-
from memory cache 内存缓存,小文件或者频繁访问的文件
-
from disk cache 磁盘缓存,一般存的大文件
-
强缓存可以通过两种响应头来配置:Cache-Control 和 Expires;
-
**Cache-Control:**max-age=缓存多少秒
-
Expires:过期时间
注意:如果同时设置了Cache-Control 和 Expires 时 Cache-Control 优先级更高,因为 Expires使用的是具体的日期和时间,客户端和服务器之间可能存在时间不同步的情况,Cache-Control 设置的是缓存的秒数,不存在这种问题。
7.2. 协商缓存
描述:当客户端请求资源时,如果该资源已经被缓存在本地,客户端会向服务器发送一个带有本地缓存的相关信息的请求(如Last-Modified和ETag),询问服务器资源是否有更新,如果有更新的话就返回最新的资源,没有更新时返回 304 状态码,通知浏览器可以使用本地缓存的资源。
具体的工作原理如下:
- 当客户端首次请求某个资源时,服务器会在响应头中携带文件最后修改时间(Last-Modified)或唯一内容标识(ETag)。
- 当客户端再次请求相同的资源时,会在请求头中把之前服务器返回的 Last-Modified 或 ETag 通过 If-Modified-Since 和 If-None-Match 字段传给服务器。
- If-Modified-Since 传 Last-Modified
- If-None-Match 传 ETag
- 服务器接收到请求后,会比较客户端传入的 Last-Modified 或 ETag 与服务器上的是否一致。
- 如果两者一致,服务器会返回 304 状态码,浏览器可以直接从本地缓存中读取。
- 如果不一致,服务器会返回 200,和最新的资源内容。
7.3. 协商缓存为什么有 Last-Modified 还要有 ETag
协商缓存中同时使用 Last-Modified 和 ETag 是为了解决单一机制存在的局限性,两者互为补充。以下是主要原因:
Last-Modified 的局限性
- 精度问题:Last-Modified 只能精确到秒级,如果文件在1秒内多次修改无法识别
- 内容不变时间变:文件内容没变但修改时间变了(如touch操作),会导致不必要的重新下载
- 无法识别重命名:文件被重命名但内容相同,Last-Modified 会认为资源已改变
- 分布式系统时间同步问题:不同服务器时间不一致可能导致缓存失效判断错误
ETag 的优势
- 更精确的变更检测:ETag 是基于文件内容生成的哈希值或版本号,能精确识别内容变化
- 解决时间精度问题:不受时间精度限制,能检测1秒内的多次修改
- 处理特殊情况:能正确处理仅修改时间但内容不变的情况
- 分布式系统友好:不依赖服务器时间,适合分布式环境
实际应用中的配合
- 服务器通常同时发送 Last-Modified 和 ETag
- 客户端在后续请求中可能同时发送 If-Modified-Since 和 If-None-Match
- 服务器优先验证 ETag,若匹配则直接返回304,不匹配再检查 Last-Modified
- 这种双重验证机制提高了缓存判断的准确性
7.4. 浏览器加载缓存资源的流程
- 浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用本地资源;
- 如果不命中则根据头信息向服务器发起请求,服务器判断是否命中协商缓存;
- 如果协商缓存命中的话,则服务器状态码返回304,不返回资源,浏览器直接使用本地资源;
- 如果协商缓存不命中,则服务器返回最新的资源给浏览器。
8. Web安全问题
8.1. 跨站脚本攻击(XSS)
描述:攻击者在网页中注入恶意脚本,当其他用户访问该网页时,这些脚本会在用户浏览器中执行,可能窃取cookies、或者其他敏感信息。
原理:在目标网站的页面中注入可执行的脚本代码。当其他用户加载该网页时,嵌入其中的恶意脚本就会在他们的浏览器上执行。
- 用户提交的数据,未经处理直接显示在网页上;
- URL参数,未经过校验或转译就被嵌入到HTML代码中;
- 第三方内容,如广告或用户生成的内容,未经审查就包含在网页中。
防御策略:
- 对所有输入数据进行适当的过滤和转义,尤其是那些将会直接输出到HTML页面的数据。
- 在将用户输入的信息展示到页面前,对HTML特殊字符进行编码,防止恶意脚本执行。
- url参数使用encodeURIComponent方法转义。
9. TCP的三次握手和四次挥手
三次握手
- 第一次握手:客户端发送一个SYN码给服务器,要求建立数据连接;
- 第二次握手: 服务器SYN和自己处理一个SYN(标志);叫SYN+ACK(确认包);发送给客户端,可以建立连接
- 第三次握手: 客户端再次发送ACK向服务器,服务器验证ACK没有问题,则建立起连接;
四次挥手
- 第一次挥手: 客户端发送FIN(结束)报文,通知服务器数据已经传输完毕;
- 第二次挥手: 服务器接收到之后,通知客户端我收到了FIN,发送ACK(确认)给客户端,数据还没有传输完成
- 第三次挥手: 服务器已经传输完毕,再次发送FIN通知客户端,数据已经传输完毕
- 第四次挥手: 客户端再次发送ACK,进入TIME_WAIT状态;服务器和客户端关闭
性能优化
1. 前端性能的优化方案
-
网络层面
-
静态资源使用 CDN
-
使用 http 缓存(强缓存和协商缓存)
-
开启 gzip 压缩
-
图片
-
压缩图片
-
小图使用 base64 格式
-
使用字体图标代替图片
-
图片懒加载
-
通过 webpack 优化
-
压缩资源,压缩 html、js、css
-
分割代码,动态导入组件,组件懒加载
-
Tree Shaking 删除死代码
-
提取公共代码
-
渲染层面优化
-
减少Dom操作
-
减少重绘和回流
-
异步加载js, 对
<script>设置defer或async- 长列表可以是用虚拟列表