前端知识点大汇总
Html 相关
什么是文档流?
文档流(Normal Flow):也叫作普通流/正常流,相对于盒子模型来说的,就是 html 元素默认在页面中的排版布局,比如 div 块级元素是从上到下,span 等行内元素从左到右排列。
脱离文档流:就是将元素从排版布局中脱离出来,脱离文档流的方式:position 中的 absolute 和 fixed,还有 float 属性。
文本流:相对于文字段落来说的,是指 html 中文本的排版显示
什么是 dom 什么是 bom?
DOM:Document Object Model 是文档对象模型,DOM 遵循 W3c 的标准,定义了如何访问 html 文档元素,并允许脚本动态对文档进行操作,根比如 document.getElementById()
,document.getElementByName()
等
BOM:Browser Object Model 是浏览器对象模型,暂时还没有相关的标准,但是现代的浏览器都已经实现了获取和设置浏览器的一系列的操作,BOM 包含 windows(窗口)、navigator(浏览器)、screen(浏览器屏幕)、history(访问历史)、location(地址)等
什么是 web worker 和 websocket
web worker:
是 html5 的一个新特性,让 web 应用程序可以具备多线程的处理能力,可以运行在后台的 javascript,而不会影响页面其他正常处理和显示,可以将复杂需要时间的处理用 web worker 来处理。它的应用场景是,可以把一些阻塞页面渲染的一些 js 计算放到 web worker 中去做。
- 通过 worker = new Worker( url ) 加载一个 JS 文件来创建一个 worker,同时返回一个 worker 实例。
- 通过 worker.postMessage( data ) 方法来向 worker 发送数据。
- 绑定 worker.onmessage 方法来接收 worker 发送过来的数据,可以在里面操作 dom 元素。
- 可以使用 worker.terminate() 来终止一个 worker 的执行,比如在 worker.onerror()方法中终结。
web worker 能做:
-
可以加载一个 JS 进行大量的复杂计算而不挂起主进程,并通过 postMessage,onmessage 进行通信
-
可以在 worker 中通过 importScripts(url)加载另外的脚本文件
-
可以使用 setTimeout(), clearTimeout(), setInterval(), and clearInterval()
-
可以使用 XMLHttpRequest 来发送请求
-
可以访问 navigator 的部分属性
缺点:
-
不能跨域加载 JS
-
无法直接操作 DOM,需要依赖线程通信实现
-
各个浏览器对 Worker 的实现不大一致,例如 FF 里允许 worker 中创建新的 worker,而 Chrome 中就不行
-
不是每个浏览器都支持这个新特性,IE 10 及以下完全不支持,部分老旧移动浏览也存在兼容问题
-
线程数量与资源开销限制,Web Worker 并非"越多越好",每个都有独立的环境,存在明显的资源占用问题,浏览器县城数量限制通常为 4~8 个,无法共享内存(默认)
websocket 是什么:
websocket
是 html5 的一种新的网络协议,也是基于 tcp 的协议,它是持久的,可以持续在客户端和服务器端保持双向通信链接,服务器的更新可以被及时推送给客户端,不需要客户端再设置定时查询。
缺点:
- 受浏览器跨域策略限制,服务端需配置 Access-Control-Allow-Origin 等头信息,或通过 "代理""JSONP 握手" 等方式绕过;
- 服务端压力大,由于是长连接,若同时在线用户量大(如百万级),服务端需优化连接管理(如用 Redis 做分布式连接存储、用 Netty 等高性能框架处理并发),避免资源耗尽。
img 标签中 title 和 alt 有什么区别?
title 是鼠标滑过图片弹出框提示的信息,alt 是在图片未成功加载情况下显示的信息
Css 相关
什么是 css 盒子模型?
在网页中,一个元素的构成主要包括内容(content),边框(border),内边距(padding),外边距(margin)这些构成,这些就像实际中的盒子一样,所以叫做 css 的盒子模型。
盒子模型 box-sizing 中 content-box 和 border-box 的区别?
比如 div 的 width 和 height 都是 100px,padding 是 10px,如果用 标准盒子模型的 content-box
是不包含 padding 和 border 的元素,实际的高和宽是 120px;而 IE 盒子模型border-box
是包含 padding 和 border 的,实际的高和宽是设置的 100px,总的尺寸更可控,常用于布局。
什么是 flex-box 布局?
弹性盒子布局,设置元素的 display=flex
,会影响其子元素的排列方式,会使块级元素不再独占一行。使用布局之后,其子元素无论多少都会在一行显示。并且通过设置一些属性能达到垂直居中,水平居中,对齐方式和排序等等,可以自由操作布局,当前所有的浏览器都支持,并且手机安卓和 ios 上也都支持。
bfc 是什么?
BFC(Block Formatting Context)是块格式化上下文,简单来说就是一个独立的渲染区域,在 bfc 内部的元素不会影响到外面的元素,因为他们隔离互不影响的。
示例场景:比如两个 div,同时设置 margin:100px;这时候上下两个 div 的距离不是 200px,而是 100px,两个的外边距会重叠,这时候就需要用 bfc 来解决,分别用一个有 bfc 的 div 包裹着,这个 bfc 的 div 可以设置成 overflow:hidden;
来触发 bfc 即可。
触发条件:
- 根元素
<html>
标签本身就是一个 bfc - float 属性不为 none
- position 为 绝对定位 absolute 或 固定定位 fixed。
- display 为 inline-block, table,table-cell, table-caption,flex, inline-flex,grid,inline-grid,还有 css3 新增的 flow-root。
- overflow 为 hidden,auto,scroll。
css 的优先级?
!import > style内联样式 > ID 选择器 > class 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器 > 通配选择器(*{})
什么叫优雅降级和渐进增强?
其实就是向上兼容和向下兼容浏览器
优雅降级:就是向下兼容,先针对现代浏览器构建完整的高级功能体验,再为不支持高级特性的旧浏览器添加"降级方案",确保功能不失效(可能体验稍差)。
渐进增强:就是向上兼容,先确保所有浏览器(包括旧版)都能正常使用核心功能,再针对支持高级特性的浏览器逐步添加增强体验。
sass 和 less 的区别?
- 定义变量的符号不同,less 是用@,sass 使用$
- 语法有些不同,sass 支持 if else 条件判断,以及 for 循环,还支持 function 函数
- 编译环境不同,less 是基于 javascript,在客户端环境处理,sass 是基于 ruby 的,在服务器环境端处理。
- 作用域不同,sass 中的变量修改,会按照修改前后顺序赋值。 less 是只要变量被修改了,全局再引用都是被修改过的了。
- sass 新版中使用@use 来导入了,弃用了@import,因为@import 会有命名冲突,重复导入文件编译,依赖混乱的问题。@use 则可以模块化隔离,同一文件多次使用只加载一次,可以自定义命名空间,精确控制依赖关系。
px,em,rem 的区别?
px:实际就是像素,px 是是相对于显示器分辨率的
em:是相对父元素计算字体大小,如果父元素没有设置大小,会以 body 的字体大小或者浏览器默认大小来计算。
rem:称为 root em
,也就是说是以根节点来计算,它是以<html>
根的字体大小来计算,不会依赖父元素,整个页面的字体大小设置好后都不会乱了。想多个端兼容,最好是使用 rem。一般方便 rem 和 px 换算的话,html 根中定义 font-size:62.5%,这样 1rem 相当于 10px,1.2rem 相当于 12px,1.4rem 相当于 14px。
移动端怎么实现响应性布局?
- 在 head 标签中加一根 meta 标签,
html
<meta
name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0"
/>
-
使用媒体查询,即 @media 查询,媒体查询可以针对不同的屏幕尺寸设置不同的样式,通过控制 screen 的 min-width 和 max-width。
-
使用容器查询,即@container,可以把父元素样式中
container:size
属性设置为查询容器,根据父元素大小来调整子元素。 -
使用 rem 来代替 px,rem 会根据 html 根节点的字体大小来计算。而 em 呢是根据父元素节点大小,所以相对于还是 rem 更适合方便一些。
link 和@import 的区别
两者都是外部引用 css 样式的方式,其他区别如下:
-
link 是 HTML 提供的标签,不仅可以加载 CSS 文件,还可以定义 rel 其他连接的属性,比如 stylesheet(外部 css 样式表),icon(定义文档图标),prefetch(浏览器空闲预加载可用的资源)preload(强制提前加载关键资源,优先级高于 prefetch,如加载字体/核心脚本等),license(版权许可链接),author(文档作者的信息链接)等等
-
@import 是 CSS 提供的语法规则,只有导入样式表的作用;
-
link 引用 CSS 时,在页面载入时可以同时加载;@import 需要页面网页完全载入以后加载。
-
link 支持使用 js 动态操作 dom 去添加删除标签;而@import 不支持。
css 中可以让文字在垂直和水平方向上重叠的两个属性是什么?
- 垂直方向:line-height
- 水平方向:letter-spacing
css 中怎么解决 inline-block 的空白间隙?
-
最常用的是设置父元素的 font-size 为 0,然后设置子元素的 font-size。
-
父元素设置 letter-spacing 为负值,子元素设置 letter-spacing:0px;即可。
-
子元素用 margin-left 等于负数 来调整。
-
改用 flex 或 grid 来布局,默认子元素不会产生空白。
css 中垂直居中的方法有哪些?
-
设置固定的高度,然后 line-height 等于这个高度。
-
设置父元素 position:relative;子元素设置为
position:absolute;top:50%;transform:translateY(-50%);
意思就是自身高度向上偏移 50%。 -
父元素设置 position:relative;子元素设置
position:absolute; top:0; bottom:0; margin:auto;
若想水平也居中,改为用position:absolute;left:0;right:0; margin:auto;
即可。 -
使用 flex 布局,父元素设置
display:flex;align-items:center;
如果想水平居中,父元素设置justify-content: center;
-
使用 grid 布局,父元素的
place-items:center;
可以同时实现水平和垂直居中。 -
父元素使用 display:table;子元素使用
display:table-cell;vertical-align:middle;
-
使用伪元素,父元素设置固定高度,然后父元素::before 设置为
display:inline-block;height:100%;vertical-align:middle;
,子元素设置为display:inline-block;vertical-align:middle;
可以实现垂直居中。
用 css 隐藏元素的几种方法
-
display:none;
-
visibility:hidden;
-
opacity:0;
-
position:absolute;top:-999px;left:-999px;
-
transform: translateX(-500%);
css 英文首字母大写
比如:hello it's ok,使用 text-transform:capitalize; 输出:Hello It's Ok
CSS 优化、提高性能的方法有哪些?
-
避免过度约束
-
避免后代选择符
-
避免链式选择符
-
使用紧凑的语法
-
避免不必要的命名空间
-
避免不必要的重复
-
最好使用表示语义的名字。一个好的类名应该是描述他是什么而不是像什么
-
避免!important,可以选择其他选择器
-
尽可能的精简规则,你可以抽取不同类里的重复规则来复用
transition 与 animation 的区别
他们虽然都可以做出动画效果,但是 transition 主要做简单的过渡效果,而 animation 可以做复杂的动画效果,在语法和用法上有一些区别。
-
transition 是过渡属性,强调过渡,他的实现需要触发一个事件(比如鼠标移动上去,聚焦,点击等)才执行动画,过渡只有一组(两个:开始-结束)关键帧。
-
animation 是动画属性,他的实现不需要触发事件,设定好时间之后可以自己执行,且可以循环一个动画(设置多个关键帧)。
JavaScript 相关
js 编译原理?以及什么是 ast
js 是一种动态解释型语言,包含有:编译器(负责语法分析以及代码生成),引擎(负责 js 程序的编译和执行过程),作用域(负责收集并维护所有生成的标识符(变量),并实施一套非常严格的规则,确定当前执行的代码对这些变量的访问权限。
一、编译阶段:
- 词法解析:词法分析器(Lexer)将代码字符串分解为词法单元,比如定义一个变量,var num = 10; 词法分析器会逐个拆分
var
、num
、=
、10
、;
。 - 语法分析:语法分析器(Parser)将词法单元转换成一棵类似 JSON 树一样的程序语法结构树,也就是抽象语法树(AST),
- 生成代码:js 引擎(现代 V8 引擎)将抽象语法树 AST 转换为可执行的机器码。
二、执行阶
- 创建执行上下文:包含存储变量,函数声明,变量提升,作用域链,this 绑定。
- 执行代码:解释器按照顺序开始执行机器码。
js 数据类型都有哪些?
基本数据类型:字符串(String)、数字(Number)、布尔(Boolean)、空(Null)、未定义(Undefined)、Symbol。
引用数据类型:对象(Object)、数组(Array)、函数(Function)。
typeof 的返回值都有哪些呢?
有:string,number,boolean,object,undefined,function,ES6 新增了 symbol
js 变量命名规则?
-
常用骆驼命名法,首字母小写。
-
以字母,下划线,$符号开头,不能以中文,空格,特殊标点符号,数字开头。
-
变量命名尽量简洁和意思明了,不能过长。
-
不能使用 js 里面的关键字命名。
es6 去重方法?
Array.from(new Set([1,1,2,3]))
...new Set(\[1,1,2,3\])
typeof 和 instanceof 的区别?
-
typeof 可以判断一个变量是否为空,可以判断变量是属于哪个数据类型,基础数据类型的返回 number,string 这些。注意对于 Array,Object,Null 等特殊对象使用 typeof 一律返回 object,typeof undefined 是返回 undefined。
-
instanceof 是判断一个变量是否属于某个对象的实例,比如:[] instanceof Array,输出 true
什么是闭包?
闭包是指一个能够读取所在外部作用域变量的一个内部函数。
优点:
- 变量在内存中,可以提高性能。
- 避免全局变量的污染。
- 可以保护私有成员变量。
缺点:
- 因为变量可以一直在内存中,没有被销毁,会内存消耗大,所以在使用完之后手动把变量赋值为 null。
什么是立即执行函数?
是一种在定义时立即执行的函数表达式,函数内部的变量和函数不会影响到全局作用域,避免了变量名冲突。
js
// 在非严格模式下,this 默认指向全局对象 window。在严格模式下,this 的值为 undefined。
(function () {
"use strict";
console.log(this); // 输出: undefined
})();
js 中作用域有哪些?
-
全局作用域:声明在函数外部的变量,比如公共变量,在代码中任何地方都能访问的到。
-
函数作用域:在函数内部声明的变量 / 函数,仅在当前函数内部可访问,外部无法直接访问。
-
块级作用域:是 es6 有的一个概念,通过一对{}花括号定义的代码块(如 if,for,while 或独立的{})在这里定义的变量在代码块外面都是不可见的,这个就叫块级作用域。使用 let 和 const 定义的变量,只会在这一范围内起作用。但是用 var 声明的,会变量提升,在其他作用域里面可以访问到。
-
模块作用域:在 es6 模块中的脚本里,声明的顶级变量只能在当前模块用,在其他模块中 import 不进来,必须显示的暴露出来才可以。
什么是变量提升?
js 引擎在编译代码的时候,所有使用 var 声明的变量,都会被提升到当前作用域的顶部,只是默认值为 undefined,赋值留在原地,同样的函数也一样也是会存在提升的,也就是在调用的时候不存在按上下顺序调用。
什么是暂时性死区?
let 和 const 是块级作用域,声明的变量不会存在变量提升,在未声明之前就使用变量就会报错,所以在代码块内,使用 let 和 const 声明变量,先使用后声明是不行的,这在语法上称为'暂时性死区',简称 TDZ。
Array 对象的属性有哪些?
bash
constructor:返回对创建此对象的数组函数的引用。
length:设置或返回数组中元素的数目。
prototype :使您有能力向对象添加属性和方法。
Array 对象方法有哪些?
bash
concat() 连接两个或更多的数组,并返回拼加后的新数组。
pop() 删除数组最后一个元素,并返回该删除的元素。
shift() 删除数组第一个元素,并返回该删除的元素。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
reverse() 颠倒数组中元素的顺序。
slice() 通过下标截取数组
splice() 删除或者替换数组元素
fill()将一个搞定的值填充一个数组中从开始位置到起始位置的所有元素
keys() 返回一个可以迭代的对象
sort() 对数组的元素进行排序
from()将一个类似数组或可迭代对象转换成一个数组
some()遍历时候,只要有一项满足,就返回 true
every()遍历时候,全部都满足,才返回 true
forEach()遍历数组
map()对数组中的每个元素进行处理,得到一个新的数组
filter()返回满足条件的数组元素
includes()判断数组中是否包含某一个值,会返回 true 和 false
indexOf()判断数组中包含某一个值 ,存在则返回下标,不存在就返回-1
join()将数组中内容按照某个字符串分割,并返回分割后的字符串。
isArray()判断传递的对象是否是一个数组类型
toString()把数组转化为字符串
对象和类的区别?
类是对象的抽象,对象是类的实例,对象是实际存在的,有对应的行为和属性。
["1","2","3"].map(parseInt)的结果是多少?
答:[1,NaN,NaN],
bash
['1','2','3'].map(parseInt) 会传递parseInt(currentValue,index,array)这3个参数,
parseInt('1',0);radix 为 0,parseInt() 会根据十进制来解析,第一个可以任意数字都可以,所以这里最终结果为 1;
parseInt('2',1);radix 为 1,超出区间范围,所以结果为 NaN;
parseInt('3',2);radix 为 2,用2进制来解析,应以 0 和 1 开头,其他数字不可以,所以结果为 NaN。
介绍一些 es6 的特性
-
块级作用域:let,const。
-
变量解构赋值:如 let [a,b]=[1,2],输出是 a=1, b=2
-
字符串的扩展:比如新增 includes(),startsWith(),endsWith(),padStart(),padEnd(),matchAll(),字符串模版等。
-
函数的扩展:箭头语法,双冒号运算符(可以像 bind 一样将冒号前面的对象绑定到冒号右边,比如 foo::bar 相当于 bar.bind(foo))
-
数组和对象的扩展:新增了扩展运算符(...),用来合并数组和对象,不过这种是浅拷贝。数组的方法也新增了 from(),fill(),includes()等方法,对象也新增了 Object.is(),Object.assign()(浅拷贝),Object.keys(),Object.values()等。
-
新增了 Symbol 原始数据类型。
-
新增了 Set 和 Map,WeakSet,WeakMap。Set 里面的值是不会重复的;WeakSet 只能存对象,由于是弱引用,只能当做临时存储,没有被引用的对象可能会被内存回收。WeakMap 只能接受对象和 null 作为 key。
-
新增了 Promise 对象,用来处理异步操作,可以通过 then 链式调用,来控制异步代码执行顺序。
-
新增了 Generator 和 Async 和 await 来处理异步,和 Promise 一样的作用,async 返回的其实就是一个 Promise 对象。
-
新增了 Class 的语法,可以用 extends 来实现继承。
-
支持静态 static 方法,支持 get 和 set 方法避免直接访问类内部属性,支持#符号定义类里面的私有变量。
-
es6 的 import,export 语法来实现模块化,和 commonJS 的 module 是一样作用的。
es6 中 set 和 map 区别?
set 是没有元素可以重复的,map 是键值对可以允许重复
this 指向问题?
总体来说,this 的指向并不是在定义时决定的,而是在调用时决定的。
普通函数:取决于调用方式,非严格模式 this 默认指向 window,严格模式 this 是 undefined。
构造函数:对象实例化时,this 指向的是新创建的实例。
字面量对象:这种是该对象调用方法,那么方法里这个 this 就指向这个对象,本质上 this 永远指向调用该函数的对象。
箭头函数:箭头函数没有自己的 this,它根据所在的外层作用域环境决定,在哪个环境就指向谁。
什么是函数柯里化?
柯里化(Currying)是把接受多个参数的函数转变为嵌套结构接受单一参数的函数,并返回一个函数接受剩下的参数。这中间可嵌套多层这样的接受部分参数的函数,直至返回最后结果。
例如:
javascript
function add(a) {
return function (b) {
return function (c) {
return a + b + c;
};
};
}
console.log(add(1)(2)(3)); // 输出结果:6
call 和 apply 和 bind 的区别?
-
这 3 个都是函数原型链的方法,用于改变函数执行时 this 的指向。
-
执行时机上,call 和 apply 会立即执行函数,而 bind 是返回一个新函数,需要手动调用,还有 bind 是可以链式调用的。
-
参数传递上有区别,call 是可以接受多个参数,例如:B.call(A, arg1,arg2); apply 是只能接受一个数组参数,例如 B.apply(A, [arg1,arg2]); bind 是可以支持分次传参,例如 B.bind(A,arg1).bind(A,arg2),需要注意 bind 绑定的实例是首次时候就确认了,后面再改就被忽略了。
-
返回值有区别,call 和 apply 因为是立即执行函数,返回的是函数执行的结果,而 bind 返回的是绑定后的新函数。
怎么让一个构造函数的实例 instanceof 它为 false?
主要是直接修改对象的 prototype 就可以了,修改后就不是同一个实例了,比如下面:
javascript
function Cat() {
this.name = "cat";
}
var cat = new Cat();
Cat.prototype = {
say: function () {
console.log("a cat");
},
};
console.log(cat instanceof Cat); // 这里输出false
什么是构造函数?
构造函数的写法就是普通的函数,只不过可以通过 new 创建一个新的实例,这个函数就叫做构造函数,通常构造函数的命名以大写字母开头。
构造函数 new 一个对象后发生了什么?
- 在堆内存中创建一个空对象
- 给新对象设置原型链,将新对象的原型
__proto__
指向构造函数的 prototype 属性。 - 绑定 this 并执行构造函数中的代码,给这个新对象添加属性和方法。
- 返回新对象,如果构造函数没有显示返回对象则返回 new 创建的对象,如果构造函数显式返回另外一个对象,则 new 之后返回的是新返回的对象。
什么是原型和原型链?
原型:所有的函数都有一个 prototype
属性,这个就叫原型,也称为显示原型。我们用构造函数创建的对象会有 prototype
原型上面所有绑定的方法。
原型链:当我们用 obj.xxx
访问一个对象的属性时,会先在该对象自身属性中查找,如果没有找到,再沿着__proto__
这条链向上层去找,这样一层一层的查找(即__proto__.__proto__
)就会形成一个链式的结构,就叫做原型链。如果找到原型链的顶层 Object.prototype
还没有,就返回 null,也就是 Object.prototype.__proto__ === null
。
prototype 和 __proto__
的区别?
-
prototype
(原型)是构造函数才有的属性,可以叫做显示原型
,它实例化的对象包含 prototype 上所有共享的方法。(当构造函数 new 实例化一个对象时候,它实例化对象的__proto__
指向了它构造函数的 prototype); -
__proto__
是对象(不论是普通对象还是构造函数实例化的对象)才有的属性,可以叫做隐式原型
,用于指向创建它的构造函数的原型对象。(对象的__proto__
会指向它构造函数的 prototype);
js
function foo() {}
var f = new foo();
// 该实例化的对象的__proto__指向了其的构造函数prototype
console.log(f.__proto__ === foo.prototype); // 输出:true
var obj = {};
// __proto__指向构造函数的prototype
console.log(obj.__proto__ === obj.constructor.prototype); // 输出:true
怎么用 js 给元素添加 class?
bash
# 为 <div> 元素添加 class:
document.getElementById("myDIV").classList.add("mystyle");
# 为 <div> 元素添加多个类:
document.getElementById("myDIV").classList.add("mystyle", "anotherClass", "thirdClass");
# 为 <div> 元素移除一个类:
document.getElementById("myDIV").classList.remove("mystyle");
# 为 <div> 元素移除多个类:
document.getElementById("myDIV").classList.remove("mystyle", "anotherClass", "thirdClass");
# 为 <div> 设置属性
document.getElementById("myDIV").setAttribute("title","this is a title");
js 哪些情况会造成内存泄漏?
- 大量声明使用全局变量,又未清理。
- 闭包引起的内存泄漏。
- dom 元素引用未清理。
- 定时器,绑定的事件监听器未被清除。
- 子元素存在引用。
- 框架或组件使用不当,导致内存泄漏。
js 中垃圾回收的策略?
js 的垃圾回收机制是一种自动内存管理的方式,用于检测和清理不再使用的对象,从而释放内存空间,垃圾回收器会定期执行,确保程序的内存使用保持高效。
存在的缺点是需要遍历所有对象结构,会带来性能的影响。
标记清除:
这是目前 js 引擎最常用的策略,分为标记和清除两个阶段,垃圾回收器会从根对象(如浏览器环境全局的 window
或 Nodejs 环境全局的 global
)开始,遍历所有可访问的对象,这些被标记为"存活"。未被标记的对象被视为非"存活"的对象,会被释放,并将占用的内存加入空闲内存供后续使用。
引用计数:
这种是较老的垃圾回收算法,现在比较少用,甚至已经被主流引擎弃用了。原理就是跟踪一个对象的引用次数,每当有引用指向该对象时,引用次数加 1;当该对象引用解除时,引用次数减 1;当引用次数为 0 时就会被回收。
存在的缺点是如果被相互引用,则引用次数永远不会是 0,可能会导致内存泄漏。
分代回收和增量标记和惰性清理:
为提高垃圾回收效率,现代的引擎会在标记清除的基础上进行优化,分为分代回收、增量标记、惰性清理
-
分代回收
分代回收基于对象的生命周期管理内存,将内存划分为新生代和老生代,新生代是存放新创建的对象,比如声明的局部变量,垃圾回收频繁,速度快;老生代是存放存活比较久的对象,如全局变量、长期缓存,还有新生代对象多次仍未被回收也会进入到老生代中,垃圾回收频率低,速度慢。这种不同代采用不同的回收算法,平衡效率和性能。
-
增量标记
将原本一次性完成的标记阶段,拆分为多个步骤,穿插在 js 执行过程中,避免一次性遍历耗时间而堵塞主线程,解决传统标记清除卡顿的问题。
-
惰性清理
标记完成后,在内存充足时延迟清理,不会立即清理所有死亡对象,而是在需要分配新内存时,按需清理部分内存,减少单词回收的时间开销
js 中 undefined 和 null 区别?
这两个的值在强等于情况下是不相等的,即 undefined === null 是 false。undefined 表示变量定义了未赋值,null 表示变量的引用是空的。
注意(null == undefined)为 true,是成立的,因为在 es 规范中,null 和 undefined 的行为很相似,都表示一个无效的值,所以它们是相等的。
CommonJs,AMD,CMD,UMD,ESM 的区别?
以上这些 都是 JS 模块化的规范。
-
CommonJs 是服务器端模块的规范,Node.js 采用了这个规范。 根据 CommonJS 规范,一个单独的文件就是一个模块,使用
module.exports={}
导出模块内容,加载模块使用require('./index.js')
方法,CommonJS 加载模块是同步的,所以只有加载完成才能执行后面的操作。 -
AMD(Asynchronous Module Definition) 是 RequireJS (这里可以简记 AR)定义的规范,是异步方式加载模块,使用 define(['myModule'],function(arg){})定义模块,
require(['myModule'],function(arg){});
加载模块,AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块,但其回掉地狱的问题对代码的可读性造成影响,现基本已被淘汰。 -
CMD(Common Module Definition)是 SeaJS (这里可以简记 CS)定义的规范,旨在改进 AMD 的缺陷,CMD 推崇就近依赖,可以代码中在用到某个模块的时候再去 require 模块 。
-
UMD(Universal Module Definition - 通用模块定义)是 AMD 和 CommonJs 的一个融合,就是让同一个模块的代码可以在多种环境中运行,无论是 CommonJs,AMD 又或者是浏览器全局变量,通过判断来适配,一般工具库开发可以打包成这个格式。
-
ESM 是 ECMAScript 2015 (ES6) 官方推出的模块标准,旨在成为浏览器和服务器通用的模块解决方案,这个也是未来主流的规范。可以异步加载,在编译的时候确定导入内容,支持 tree shaking,其模块功能主要由两个命令构成:export 和 import。export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能,现代项目建议都使用 ems。
require 和 import 的区别?
-
require 是 commonJS 的规范
-
import 是 es6 中的语法
-
require 是运行时动态加载,import 是编译的时候调用
-
require 输出的是一个值的拷贝,import 输出的值是引用
js 中深拷贝和浅拷贝区别?
-
浅拷贝的对象和原对象的引用是同一个,当源对象发生改变,浅拷贝的对象也会改变。
object.asign()
和...扩展运算符
属于浅拷贝 -
深拷贝是完全创建一个新的引用,可以用
JSON.parse(JSON.stringify())
实现,缺点是不能对Function,Data,RegExp,Map,Set,Symbol,BigInt
之类的进行转换,或者第三方工具库来实现,又或者手动写函数,递归遍历对象,重新赋值个新的拷贝后的对象
什么是事件流?
也就是事件处理的流程,是有先后执行顺序的,现代浏览器通常遵循 W3C 标准的事件流它分为 3 个阶段:事件捕获 》事件目标 》事件冒泡
什么是事件模型?
是描述浏览器中事件如何被触发、传播和处理机制的一套规则,它定义了事件从产生到被处理的完整流程,以及开发者如何绑定、触发和管理事件。
- 原始事件模型(DOM 0 级事件模型)
在原始事件模型中,事件发生后没有传播的概念,也没有事件流的产生,事件发生后就马上处理然后结束,比如:
js
<button id="btn">点击</button>;
document.getElementById("btn").onClick = function () {
alert("按钮被点击");
};
缺点:
一个事件只能绑定一个处理函数,后绑定的会覆盖前一个。
功能简单,不支持事件流控制。
- 标准事件模型(DOM 2 级事件模型)
这个模型是 W3C 指定的标准模型,是现代浏览器的主流事件模型,引入了事件流(捕获 → 目标 → 冒泡)的概念。在标准中,一次事件的发生包含 3 个过程:
-
事件捕获(事件被从 document 一直向下传播到目标元素,在这过程中依次检查经过的节
点是否注册了该事件的监听函数,若有则执行。)
-
事件处理阶段(事件到达目标元素,执行目标元素的事件处理函数)
-
事件冒泡阶段(事件从目标元素上升一直到达 document,同样依次检查经过的节点是否注册
了该事件的监听函数,有则执行。)
添加事件:
document.getElementById('div').addEventListener('click',function(){},false)
addEventListener 中第三个参数是 false 就是事件冒泡,是 true 就是事件捕获,默认是 false 事件冒泡。
删除事件:
removeEventListener('click')
- IE 事件模型(非标准,已淘汰)
这个是在 ie 系列浏览器(IE8 及以下)里独有的,ie 中的事件流不是标准的,它只有事件目标和事件冒泡,没有事件捕获,与标准模型差异较大,现在基本已经弃用。
事件绑定/移除: attachEvent('onclick',function(){});
和 detachEvent('onclick',function(){})
。
事件对象:事件对象通过 window.event 获取,而非函数参数。
什么是事件委托?
说到事件委托,其实也是事件代理,都是表示会有一个第三方统一去做某件事情。
委托就是通过事件冒泡机制,把事件监听函数绑定到父元素上,在父辈元素的监听函数中,可通过 event.target 属性拿到触发事件的原始元素,然后再对其判断后进行相关处理。好处是可以减少事件处理函数的数量,降低内存消耗,适合列表、表格这种包含大量相似子元素的结构。
举个例子,比如我们去饭店吃饭,小明说想吃蛋炒饭,小花说想吃牛肉面,小明和小花不可能直接告诉厨师吃什么,人多的话厨师记不清,这时候服务员出来了,服务员记录下来小明和小花想吃的饭,然后报给厨师,做好后,服务员根据做好的饭和订单上对应(event.target 判断是哪个元素),依次给小明和小花, 这个服务员就是个委托,也是代理。
同源策略是什么,还有跨域是什么?
-
同源策略是浏览器一项重要安全机制,用于限制不同源之间的交互,保护用户数据安全。
-
同源指的是协议、域名和端口完全相同。如果任意一项不同,就会被视为跨域。
-
跨域就是指不同源的两个资源相互访问,就会受同源策略限制访问不到,也就是跨域了,需要通过 CORS,NGINX 代理,JSONP 等方式来解决。
不受同源策略限制的有?
-
页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
-
跨域资源的引入是可以的,如嵌入到页面中的
<script/>,<img>,<link>,<iframe>,<video/>,<audio/>,@font-face
等。 -
跨域就是前面说的同源策略影响的解决办法,就是跨域。
跨域的方式有?
-
jquery ajax 的 jsonp 跨域,通过设置回调。
-
通过 document 创建 script 标签加载跨域 js,在回调函数获取返回数据
-
通过修改 document.domain 来跨域。
-
CORS(跨域资源共享) 实现跨域,主要是后端配置 Access-control-allow-origin 来实现。
-
nginx 做代理也可以实现,配置 proxy_pass 地址。
-
在开发环境下也可以实现,比如 webpack 或者 vite 配置文件中配置 proxy 代理。
-
使用 websocket 建立双全工通信,由于本身不受同源策略影响,所以可以实现跨域通信。
-
通过 iframe 的 postMessage 父子通信来实现跨域通信。
什么是作用域?什么是作用域链?
作用域:作用域其实就是变量或者函数起作用的范围,比如定义一个 var a
在一个函数里,那这个 var a
的作用域就是在这个函数里,外面是访问不到的。
作用域链:是 javascript 中一种变量、函数查找机制。当访问一个变量时候 js 引擎会在当前作用域下寻找,如果找不到,则到它的上层作用域去找,以此类推,直到找到 window 或 global 全局作用域为止,如果最终全局也没找到就会报错。和原型链有一点相近,也是逐级往上去寻找,但是作用域链的顶层是 window 或 global。
栈和堆的区别?
栈(Stack)和堆(Heap)是 JavaScript 中两种核心内存存储区域,主要区别体现在存储内容、分配方式、访问速度等维度,
-
栈(Stack)是一种先进后出的数据结构,用来存储基本数据类型的值,比如 Number,String,Boolean,Undefined,Null,Symbol 等,栈内存由系统自动管理。栈访问速度快,但是容量有限,当栈空间耗尽会发生栈溢出,适合存储短期使用的数据。
-
堆(Heap)是一种动态内存分配的数据结构,用于存储引用数据类型,如 Object,Array,Function 等对象,然后会在栈中存放对应的一个引用地址,通过栈中的指针去在堆中获得真正的值。堆内存需要 js 引擎进行垃圾回收,当内存无法有效释放会导致内存泄漏。堆访问速度慢,容量大,适合存储复杂、体积较大的数据。
js 异步加载的方法
-
在 script 标签中使用 async 属性,虽然可以实现,但是它是乱序执行的,只要加载完了就会执行,所以不能保证执行顺序。
-
在 script 标签里使用 defer 属性,对脚本进行延迟,直到页面元素加载完成为止,而且它是按照加载顺序执行脚本的
-
动态生成 script 标签,在 onload 里动态使用 document 生成标签。也可以用 jquery 的 ready 函数执行,
-
模块加载,script 标签中加
type="module"
属性,可以实现异步加载 -
还可以用
<link/>
中的rel=preload
提前加载,或rel=prefetch
来预加载未来可能用到的资源。
js 中创建对象的方式有哪些?
-
直接通过字面量创建 var obj = {};,这种推荐使用,js 引擎在解析时会对字面量对象进行优化,无需查找构造函数,比 new Object()快 30%以上。
-
通过 new 调用构造函数创建,如
function Person(){}
。 -
直接通过 new 原生的 Object 对象方式创建。
-
原型模式
-
通过
Object.create({name:"parent"})
方式创建,新对象可以继承原型对象的所有属性和方法。 -
通过 es6 的 Class 类创建
-
工厂模式,把创建对象的函数封装,返回新对象。
-
组合模式(原型和构造函数)
js 中实现继承的方式有哪些?
-
原型链继承,缺点是存在属性共享问题,两个子类使用同一个原型对象,当一个子类改变了原型中属性的引用,另外一个也改变。
-
构造函数继承。
-
es6 的 extends 继承,也是现代推荐的方式。
-
组合继承。
-
寄生式继承。
-
原型式继承,
Object.create()
方法实现。
JS 中的== 与 ===的区别
==:叫做相等运算符,比较时候会尝试自动进行类型转换为同一类型,再比较转换后的值,值相等就是相等。
===:叫做严格运算符,只要类型不同,就不会相等。
sessionStorage 、localStorage 和 cookie 之间的区别?
共同点:都是保存在浏览器端、且同源的
区别:
-
创建方式不同:cookie 是由服务器端创建,发送客户端,当用户发送请求后,会在 http 请求中携带传递到服务器端。而 sessionStorage 和 localStorage 是创建保存在客户端,也不会自动把数据发送给服务器。
-
存储大小不同:cookie 数据不能超过 4K,只适合保存很小的数据,比如会话标识。sessionStorage 和 localStorage 本地存储可以达到 5M 以上。
-
数据有效期不同:sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的 cookie 过期时间之前有效,即使窗口关闭或浏览器关闭 。
-
作用域不同:sessionStorage 只能在同一个浏览器会话中有效,如果是 A 链接用
target="_self"
时候当前页面跳转,和用window.open
新打开一个页面也可以有效的,除了这些其他的情况都是不能共享的;localstorage 和 cookie 都可以在所有同源窗口中都是共享的; -
web Storage 支持事件通知机制,可以将数据更新的通知发送给监听者,具体是在页面添加监听事件:
window.addEventListener("storage", function(event){}, false)
,通过回调的event.storageArea===localStorage
判断所属的存储区域,常用的 api 方法有setItem(key,value)
,getItem(key)
,removeItem(key)
,clear()
原生 ajax 的工作原理?
- 创建 XMLHttpRequest 对象
javascript
var xhr = new XMLHttpRequest();
- 配置请求参数(初始化)
javascript
//通过发送get或者post请求到服务器的url地址, 第三个参数是是否async,true是同步,false是异步
xhr.open("get", "http://222.11.11.2/getUser?id=1", false);
- 设置请求头(可选,按需配置)
javascript
// 若发送 JSON 数据
xhr.setRequestHeader("Content-Type", "application/json");
- 发送请求
javascript
xhr.send(); // 发送
- 监听请求状态变化,处理响应
javascript
xhr.onreadystatechange=function() {
if (xhr.readyState==4 && xhr.status==200)
// 服务器正常响应后,获取返回数据
}
js 中 eventloop 是什么?
event loop
称为事件循环机制,javascript 是单线程的,js 中所有的任务都需要按顺序逐个执行,但是一个任务消耗太长,后面的任务就需要等待,为了解决这种情况,javascript 把任务分为两种,一种是同步任务,一种是异步任务,同步任务会在主线程排队执行的任务;异步任务是会进入到任务队列 task queue
,任务队列还分为宏任务macro-task
(setTimeout(),setInterval(),script 块,Promise 构造函数是宏任务)和微任务micro-task
(Promise,process.nextTick(),Promise 的 then 和 catch 是微任务)。当主线程中的任务执行完毕之后,会不断读取异步任务队列中的任务来执行,这就是事件循环。
执行异步任务队列中任务时候,会先执行完所有的微任务
,再执行宏任务队列中取一个宏任务
执行,每执行完一个宏任务,就会再次清空所有微任务,如果没有微任务就会继续执行宏任务,以此循环往复。
什么是防抖和节流?
防抖:如果在定时执行之前,就连续触发事件,就会重新开始延时执行。
js
// 函数防抖
var timer = false;
document.getElementById("debounce").onscroll = function () {
clearTimeout(timer); // 清除未执行的代码,重置回初始化状态
timer = setTimeout(function () {
console.log("函数防抖");
}, 300);
};
节流:当持续触发事件时,保证一定时间段内只调用一次事件处理函数。
js
// 函数节流
var canRun = true;
document.getElementById("throttle").onscroll = function () {
if (!canRun) {
// 判断是否已空闲,如果在执行中,则直接return
return;
}
canRun = false;
setTimeout(function () {
console.log("函数节流");
canRun = true;
}, 300);
};
什么是解构赋值?
解构赋值语法是一种 js 表达式,它使得将值从数组,属性,对象中提取到不同变量里。
比如:[a,b]=[10,20],结果是 a=10,b=20;
js 里求出数组中的最大值
- 使用 for 循环来比较
js
let num = 0;
for (let i = 0; i < array.length; i++) {
if (i > num) {
num = i;
}
}
- 使用 es6 的语法
js
var n = Math.max(...array);
js 实现数字千分位
第一种:直接用数字的原型方法
js
var num = 123456789;
num.toLocaleString();
第二种:使用正则
js
var num = 123456789;
var str = num.toString().replace(/(?=(?!(\b))(\d{3})+$)/g, ",");
React 相关
react 是什么?
React 是一个用于构建用户界面的 JAVASCRIPT 库。 React 主要用于构建 UI,很多人认为 React 是 MVC 中的 V(视图),React 是 Facebook 开发的一个项目,它是单向数据流的,和 vue 数据双向绑定不同。
react 组件之间都是独立互不影响,state 也是组件独立的,如果发生多次渲染,react 的 render 函数自会渲染发生变化的地方,组件之间也是可以互相嵌套的。
react 中使用函数组件和 class 创建组件的区别?
-
函数组件只能接收 props。
-
使用 es6 class 创建的可以不单可以使用 props,还可以使用 this,和 state,以及生命周期函数。
react 中怎么绑定 this?
- 在 constructor 里 bind
js
constructor(props){
super(props);
this.handleClick=this.handleClick.bind(this);
}
- 使用属性初始化器语法
js
handleClick = () => {};
- 在元素中的事件使用箭头函数
html
<div onClick={(e)=> this.handleClick(id,e)}>click me</div>
- 在元素的事件中也可以使用 bind
html
<div onClick="{this.handleClick.bind(this,id)}">click me</div>
react 中的虚拟 dom 和 diff 算法理解
-
虚拟 dom 其实就是用一个对象来描述 dom 树,对数据和状态的变更会同步到虚拟 dom 上,通过计算新旧虚拟 dom 的差异,最终把变化的部分重新渲染。
-
diff 算法是实现比较虚拟 dom 差异的一个算法,它可以对虚拟 dom 进行逐层的比较,如果树类型不同,就会重新创建,如果类型相同,属性不同,就会只更新属性不同的部分,如果在子节点后新增节点,会直接增加节点,如果在子节点前增加节点,会重新生成这几个节点。
-
给节点加 key,key 更方便 diff 算法计算出来节点之间的差异。
什么是高阶组件?
高阶组件就是一个没有副作用的纯函数,且该函数接受一个组件作为参数,并返回一个新的组件。
什么是高阶函数?
高阶函数是一个可以接受函数作为参数,并返回一个新的函数。
什么叫纯函数?
一个函数的返回结果只依赖于它的参数,并且不会对别的对象产生副作用,就叫做纯函数。
react 中 component 和 element 的区别?
element:元素是构成 react 应用的最小单位,用来描述页面上看到的内容,可以用 jsx 语法创建一个元素,比如 const element=
hello
,与浏览器 dom 元素不同的是,react 中的元素实际上是普通的对象,所以没有办法调用 dom 原生的 api。
component:组件就是一个方法或者一个类,可以接受任意的输入值(称之为 props),并且返回一个需要在页面上展示的 react 元素。
什么是 redux 框架?
Redux 是一个流行的 JavaScript 框架,为应用程序提供一个可预测的状态容器。
那什么是可以预测化,我的理解就是根据一个固定的输入,必然会得到一个固定的结果。
核心概念,三大原则:① 单一数据源 ②state 是只读的 ③ 使用纯函数执行修改
处理流程,主要是 action,reducer,store,
组件 dispatch 一个 action,然后到 reducer,reducer 是一个纯函数,它接收到这个 action,根据 action 的类型,来更新状态。
缺点:
-
修改一个 state 可能要动 4,5 个文件,修改复杂。
-
每次 dispatch 一个 action 都会遍历所有的 reducer,重新计算 connect,很费效率。
-
如果 store 较大时候,频繁的修改 store,会明显看到页面卡顿。
-
不支持 typescript。
thunk 和 saga 是什么?
thunk 和 saga 是 redux 的异步解决方案,thunk 可以接受一个函数并且可以在里面 dispath action,但是缺点是 action 的调用会分散在各个文件中,不易管理。saga 的优点是 action 统一管理,集中处理异步操作,但是个人觉得 saga 可能初学起来不是太容易,需要先学习 es6 的 yield 和 async 以及 await 语法。
什么是 mobx?
mobx 是一个简单的可扩展的状态管理框架。它通过@observable 定一个被观察的状态数据,state 的修改是在 action 函数中进行,并且提供了 computed,通过 get 和 set 也可以计算 state,它比起 redux 更加的简洁明了,在页面中是通过@inject("store")注入 store,并且把组件@observer 变为观察的组件。
redux 和 mobx 的区别在哪里?
这两个框架都是可以做 react 状态管理的,mobx 很简洁,上手很快,原理也是比较简单,从页面发起 action 到对应的 action 去处理 state,大量使用了装饰器语法。不需要花大量的时间去研究即可上手编写。
redux 则比较庞大一点,它的衍生出来的框架也有 thunk 和 saga,学习 saga 需要学习 es6 的 await,yield,async 语法,学习成本和维护成本高一点。
redux 是单数据源的,其中一个原则是单一数据源,你需要将所有的 state 放到一个全局的 store 里面,而 mobx 相反,它可以@observable 观察多个 state,也就是可以由多个数据源。
react 的生命周期
分为 4 个阶段:初始化阶段,挂载阶段,更新阶段,卸载阶段
bash
初始化阶段:
defaultProps={}:设置默认的props
constructor() :这里可以获得props和初始化state
挂载阶段:
componentWillMount() :组件被挂载到页面之前调用,这时候还无法获取dom对象。
render() :渲染组件到页面中,这里注意不要使用setState,否则会递归渲染。
componentDidMount():组件已经挂载到页面,dom也可以获得到,也可以发送ajax请求,也可以修改state状态,但是注意这里修改状态会重新渲染。
更新阶段:
componentWillReceiveProps() :组件接收到新的props会触发这个方法,修改state不会触发这个。
shouldComponentUpdate() :这个方法返回true和false,来决定是否重新渲染组件,性能优化就是在这里进行。
componentWillUpdate() :组件将要更新时候调用。
render() :重新渲染组件,这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行
componentDidUpdate():组件已经被更改过了,会调用这个方法。
卸载阶段:
componentWillUnmount():组件将要卸载时调用,一些事件监听和定时器需要在此时清除。
react 性能优化建议
-
bind 函数的使用,在 constructor 中 bind 进去,因为构造函数每次渲染只会执行一次,在 jsx 模版中 bind 会每次 render 都会执行。
-
shouldComponentUpdate 里进行优化,这个钩子可以返回 true 和 false,false 的话就是不渲染,可以在这里面决定是否渲染组件。
-
组件继承 React.PureComponent,因为它比 React.Component 性能更好,当 props 和 state 改变的时候,PureComponent 会对它们进行浅比较,来决定是否渲染组件,而 Component 不会进行比较,当 shouldComponentUpdate 被调用时候,组件默认会重新渲染。
-
使用 redux 和 mobx 这些框架来管理状态。
-
props 尽量只传递需要的数据,多余的就尽量不要代入过去了。
-
如果有组件循环,需要指定 key。
react 是怎么检测数据变化?
react 是单向数据流,所以不像 vue 和 angular 一样可以自动实现检测到数据变化,react 是需要主动触发数据变化,比如 setState 时候,就可以触发更新。
Vue2 相关
vue 是什么?
vue 是一套用于构建用户界面的渐进式框架,它的核心是数据驱动(双向绑定)和组件化(独立可复用页面)。
代码简洁体积小,运行效率高,上手快。
什么是渐进式框架?
是一种软件开发框架的设计理念,它允许我们可以只用我们想要的一部分功能,根据实际需求可以不断引入更多的组件和功能,以及更复杂的特性,而不需要一开始就全面使用框架的所有功能。就比如 vuejs,核心库只关心视图层,可以用最简单的数据绑定,后续还可以逐步使用状态管理,路由,vue cli 构建工具等其他的复杂功能。
简述 vue 响应式原理?
当 vue 的实例被创建的时候,Vue 将遍历 data
中所有的属性,并使用 Object.defineProperty
为每个属性添加 getter/setter
进行拦截,vue 收集它们内部的相关依赖,在属性被访问和修改时候通知变化。
每个组件都对应一个 watcher
实例,它会在组件渲染过程中,把 data 中实际用到的属性记录为依赖,之后当依赖项的 setter
触发时候,会通知 watcher
,使对应的组件重新渲染。
弊端:
- 数据劫持需要递归遍历消耗比较大。
- 对新增和删除属性没有数据劫持的,无法监听到,需要用
vue.$set()
手动更新。 - 数组的一些方法也不能监听到,比如数组的
filter()
,concat()
,slice()
。
vue 框架的优点?
(1)轻量级框架:只关注视图层,大小只有几十 Kb;
(2)简单易学:文档通顺清晰,语法简单;
(3)数据双向绑定,数据视图结构分离,仅需操作数据即可完成页面相应的更新;
(4)组件化开发:工程结构清晰,代码维护方便;
(5)虚拟 DOM 加载 HTML 节点,运行效率高。
Vue 如何监测数组变化的?
同样也是通过 Object.defineProperty
数据劫持,引起数组变化的方法有 pop
,push
,shift
,unshift
,splice
,sort
,reverse
这 7 种。Vue 重写了数组的原型方法,在调用数组这几个方法改变数组时候能正常被监测到,然后相关依赖的视图内容会更新了。
Vue 的事件绑定原理?
本质是通过事件委托机制结合自定义事件系统实现的,既支持原生的 DOM 事件,也可以支持组件的自定义事件。
-
模版解析:在 vue 编辑模版时,会解析@click 这类的指令,将其转换为内部的事件对象,包含事件类型,回调的函数,事件修饰符。
-
事件委托:统一会绑定到父元素,通过事件冒泡触发,减少直接绑定提升性能。
-
事件处理:找到事件实际触发的 dom 元素,匹配对应的回调函数,处理修饰符逻辑,最后执行回调函数,并自动把当前组件的实例 this 绑定进来。
-
组件自定义事件处理:vue 实现了一套自定义事件系统,类似
发布订阅者
模式,子组件通过$emit
发布事件,父组件中订阅
了这个事件名,实现之间的通信。 -
vue 如果绑定在普通 DOM 元素上,则默认是绑定的原生 dom 事件,如果绑定在 vue 组件上,默认是监听子组件的自定义事件,如果想在组件上绑定原生 dom 事件,需要加上.native 修饰符。
vue 的生命周期?
分为 4 个步骤,创建,挂载,更新,销毁。
- 创建
bash
beforeCreate();
created(); // 初始化的内容写在这里,比如请求ajax进行数据处理
- 挂载
bash
beforeMount();
mounted(); // dom渲染在这里就已经完成了,可以获取到dom节点
- 更新
bash
beforeUpdate();
updated();
- 销毁
bash
beforeDestroy();
destroyed();
第一次页面加载会触发:beforeCreate,created,beforeMount,mounted 这几个钩子函数,而且 dom 渲染在 mounted 中就已经完成了。
vue 中的$nextTick()的理解?
在 vue 的 created()钩子中操作 dom,需要用到$nextTick()这个函数,因为这时候 dom 元素还没有渲染完毕,我们取 dom 元素是取不到的,这个函数是可以在 dom 更新之后,把操作放到它的回调函数里。它其实就是一个异步任务来做延迟,底层是使用了 event loop 中的微任务和宏任务,做了设备 api 的兼容,如果支持 Promise,会优先使用 Promise.then(微任务)来实现延迟,如果不支持就用普通的 setTimeout(宏任务)来实现了。
vue 怎么监听键盘事件?
直接在生命周期里绑定 document.onkeydown=function(e){}
即可。
监听事件除了 keycode 对应的数字之外,vue 还添加了事件的别名,比如 enter,tab,delete,esc,space 等。
vuex 是什么?
vuex 是一个专门为 vuejs 设计的状态管理工具,在状态比较多难以管理的时候就用它。
state:状态数据,可以通过 modules 来配置多个数据源操作。
getter:返回计算处理 state 后的函数,相当于 vue 中的计算属性。
mutations:真正处理 state 的地方,并且 mutation 必须是同步函数。
action:用户提交的 action 操作,view 层通过 store.dispath 来调用 action。Action 提交的是 mutation,而不是直接变更状态,Action 可以包含任意异步操作。
modules:如果管理状态的业务比较复杂,可以分为几个模块,最后在 vuex 中使用模块,引用的时候加上模块的名字即可。比如:
js
const moduleA = {
state: { name: "this is a name" },
mutations: {},
actions: {},
};
const store = new Vuex.Store({
modules: { moduleA },
});
this.$store.state.moduleA.name; // 使用的时候加上模块的名称
流程是;用户在 view 上 dispatch 一个 action》vuex 的 actions 接收到数据后 commit 到 mutations》mutations 更改 state 的数据状态》可以通过 vuex 提供的 getters 展示到 view,也可以在 view 里 this.$store.state.text 来获取状态数据。
vuex 中的异步修改怎么实现?
需要在 action 中做异步处理,不在 mutation 中处理是因为 vuex 官网上也有说,mutation 是必须是同步的函数,mutation 操作写异步的话状态很难追踪,这样就会造成很多问题。Action 和 Mutation 的区别是,action 提交的是 mutation,而不是直接操作 state,action 中是可以包含任何异步的操作。在 action 中同步就需要 return new Promise(),或者是使用 async 来定义 action,这样在异步处理的时候才会同步去执行。
Promise 是什么?
Promise 是异步处理的一种解决方案,多个异步的处理不需要通过回调函数来处理,es6 提供的 promise 处理后可以返回一个 resolve,异步执行完后执行 resolve()就会执行 then 里面的代码,这样使异步不再变得不可控,甚至可以多个 then 链式调用来控制异步代码的执行顺序。
Promise 中有 3 种状态:
pending:初始状态,既不是成功也不是失败
fulfilled:意味着操作完全成功
rejected:意味着操作失败
同一时间只有一个状态存在,且状态改变就不能再变了,当调用 resolve()时候会执行后面的 then,当执行 reject()就不会继续进行下去。
vuex 如何缓存,在页面刷新时候不丢失?
登录等信息存储的数据保存到 sessionStorage 中最好,长久保存的信息保存到 localStorage 中,当刷新页面时候,重新合并获取到的数据到 store 中。
vue 模板编译原理?
-
解析(Parse):解析器将模板字符串(如
<span>{``{msg}}</span>
)转换成 AST 抽象语法树(Abstract Syntax Tree) -
优化(Optimize):遍历 ast 抽象语法树,标记其中的静态节点(如
<span>静态文本没变化</span>
),(主要用来做虚拟 DOM 的渲染优化,标记静态节点好处是,每次重新渲染,不需要为静态节点再创建新,可以跳过虚拟 DOM 中 patching 打补丁的过程,提升性能) -
生成(Generate):代码生成器会将 AST 生成 render 函数代码字符串(如
_c('div', [_v(_s(name))])
),然后 render 函数的返回值就是虚拟 dom,vue 再通过虚拟 dom 的 diff 算法更新真实的 dom。
Vue 中的 diff 算法
diff 算法是比较虚拟 dom 差异的一个算法,它可以对虚拟 dom 进行逐层的比较。如果树类型不同,就会重新创建;如果类型相同,属性不同,就会只更新属性不同的部分;如果在子节点后新增节点,会直接增加节点;如果在子节点前增加节点,会重新生成这几个节点;而且 diff 在比较新旧节点的时候,只会同级进行,不会跨层级比较。
vue 和 react 的区别?
相同点:
-
都可以做 spa 单页面应用。
-
都支持服务器端渲染
-
都有 Virtual Dom,页面的操作数据会反应到虚拟 dom 上,通过 diff 算法计算后再渲染到页面。
-
都有组件,都有 props,并且支持父子组件之间通信,也都有状态管理工具,react 的 redux,mobx 和 vue 的 vuex。
-
都有生命周期。
不同点:
-
react 是 mvc 框架,vue 是 mvvm 框架。
-
react 是单向数据流,用事件来操作数据改变状态,vue 是双向数据绑定,通过数据劫持和订阅者观察者来实现。
-
写法不同,react 用的是 jsx 模版,vue 就像是写一个普通 HTML 页面一样,同时可以包含有 css 和 js。两者在渲染数据,更改 data 是不一样的写法,react 渲染是必须 return 元素在下面引用,vue 是直接在标签上用指令就可以,react 用 setState 设置数据,vue 直接用等号就可以。
-
数据发生改变,vue 会自动跟踪组件的依赖 关系,不需要重新渲染组件数。而 react 则状态改变,全部组件都会重新渲染,所以需要在 shouldComponentUpdate 这个生命周期里来优化
vue 中更新数组时,可以触发视图更新的方法有哪些?
会更新视图的方法:push(),pop(),shift(),unshift(),splice(),sort(),reverse(),还可以使用 Vue.set( target, key, value )
不会更新视图的方法:filter(),concat(),slice()
mvc 和 mvvm 的区别?
MVC:(Model 模型
- View 视图
- Controller 控制器
),mvc 是最经典的开发模式之一,流程是用户操作,view 层负责接收输入,然后到 Controller 控制层,对 Model 数据进行处理,最后将结果通过 View 反馈到用户。mvc 的通信是单向的,简单说就是:
用户-View-Controller-Model-Controller-View,缺点是:M 和 V 层耦合度高,所有逻辑都在 C 层,导致庞大难以维护。
MVVM:(Model 模型
- View 视图
- ViewModel 视图模型
),也就是数据双向绑定模型,Model 和 View 之间解耦,通过 ViewModel 进行交互,它就是一个同步 View 和 Model 的对象,View 和 Model 之间的同步是自动的,不需要人为干涉,开发者只需要关注业务逻辑,不需要手动操作 Dom。
vue 中的 keep-alive 是什么?
概念:它是 vue 的一个内置组件,主要作用是可以缓存组件,避免重新渲染。
它是一个抽象组件,不会被渲染到真实 DOM 中,提供了 include 与 exclude 两个属性,允许组件有条件地进行缓存。
原理:其实就是在 created 时将需要缓存的 VNode 节点保存在 this.cache 中,在 render 时,如果 VNode 的 name 符合在缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行展示。
什么遍历的时候使用 key?
因为可以给每个遍历元素添加一个唯一标识,可以让 diff 更快的计算出来进行渲染。
怎么让 css 只在当前组件起作用
在当前的 vue 页面中的 style 标签纸红加上 scoped 即可,如:<style scoped></style>
vue 的两个核心是什么?
数据驱动和组件化
数据驱动:简单可以解释为数据(Model
)的改变,使视图(View
)自动更新。也就是数据双向绑定(ViewModel
),Model 的改变会通过 ViewModel
改变 View
,同时也会监听 View
的变化,也会通过 ViewModel
响应到数据 Model
,实现数据双向绑定。
组件化:组件化实现了可扩展和可重用可维护的 html 元素,通过封装可用的代码,页面上每个独立交互的功能都可以抽取出来成一个组件。
vue 的 http 请求都有哪些框架,ajax、axios、fetch 之间的详细区别以及优缺点?
-
原生 AJAX 本质是浏览器提供的 XMLHttpRequest(XHR)对象,是最早实现异步网络请求的原生 API,无任何依赖。仅作为底层原理理解,实际开发中因语法繁琐、功能缺失,已被淘汰。
-
axios 基于原生 XMLHttpRequest 封装的第三方库,支持浏览器和 Node.js 环境,专注于 HTTP 请求的完整解决方案。Vue 项目的最佳选择,功能全面(拦截器、自动转换、超时控制等),语法简洁,兼容性好,社区生态成熟。
-
fetch 浏览器原生提供的新一代请求 API,基于 Promise 设计,旨在替代 XMLHttpRequest,属于 ES6+ 规范的一部分。但需手动处理诸多细节(错误判断、Cookie、超时等),适合现代浏览器环境且需轻量方案的场景。
为什么 new vue({data:{name:''}})
中 data 是一个对象,而在组件中,data(){return{name:''}}
,data 必须是一个函数呢?
首先每一个组件其实都是一个 vue 实例化得来的,引用的是同一个实例,所以一个发生改变,其他的引用肯定也会改变的,但是换成函数的话,因为变量在函数里外部不能直接访问,所以也就不会影响到别的组件。所以 单独再次new Vue()
的时候实例其实只有一个,用new Vue({data:{}})
也就可以了。
怎么自定义一个 vue 的过滤器?
- 在组件中自定义过滤器
在组件的 js 配置中写:
js
export default {
filters: {
myFilter(value) {
return value.toLowerCase();
},
},
};
使用:
直接输出属性时候{``{name | myFilter}}
或者在 v-bind 中<div v-bind:name="name | myFilter"></div>
- 自定义全局过滤器
js
Vue.filter("allFilter", function (value) {
return value.toLowerCase();
});
new Vue({
// ...
});
vue 中自定义指令怎么写?
参考官方的文档的写法,指令是有几个钩子函数的,bind(只调用一次,首次绑定元素时调用)
,inserted(绑定元素插入父节点时调用)
,update(元素所在组件更新时调用)
,componentUpdated(指定所在组件的vnode和子vnode全部更新后调用)
,unbind(只调用一次,指令与元素解绑时调用)
。
bash
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时......
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
v-on可以绑定多个方法,
<div v-on="click1(),click2()")>click here</div>
什么是 vue 中的计算属性?
-
计算属性可以用来计算模版中比较复杂的逻辑,运算,并返回计算后的结果。
-
计算属性监听 vue 中的数据依赖,只要依赖的其中一个数据变化了,就会重新计算,相当于 watch 监听数据一样。
-
计算属性以属性形式定义,使用时像普通属性一样直接引用,无需加小括号,如果需要传值,可以内部
return function(arg){}
返回一个函数,使用时就可以<span :data="calcResult({})"/>
。 4.计算属性默认只是getter
直接返回,还可以显示的声明getter和setter
,拦截获取和设置值。 -
计算属性还可以进行缓存,只要依赖的数据没有变,再次请求也不会重新计算,而是去取的缓存。
vue 中组件传值方式有哪些?
-
父-子:调用子组件
<Child :name='name'></Child>
,在子组件里用props
接收props:{name:{type:String,default:'dd'}}
来接收值。 -
子-父:调用子组件
<Child @getChild="fromChild"></Child>
,使用@事件名
来监听并接收,在子组件里调用this.$emit("fromChild",传父组件的值)
来传值; -
可以使用
$parent
和$children
来直接操作父组件或子组件,可以访问到对应组件的实例,风险较大。 -
定义公用组件 bus 来传值,父组件用
bus.$on("fromChild",function(data){});
子组件用bus.$emit("fromChild",传的值)
,支持跨层级。 -
如果是跨层级传值通信,也可以在子组件里一层层的传递属性
$attrs
和 事件$listeners
。 -
使用
provide
和inject
依赖注入来跨层级,所有后代组件都可以通过inject
注入进来使用。 -
使用 Vuex 来传值,支持跨层级。
v-for 和 v-if 为什么不能连用?
在 Vue2.0 中 v-for 的优先级是高于 v-if 的,所以遍历的时候会把 v-if 的元素都添加一遍又隐藏,这样是无意义的,会造成性能浪费。
在 Vue3.0 中 v-if 的优先级是高于 v-for 的,同样是不建议放在一起用的。
v-html 会导致哪些问题?
-
xss 攻击
v-html
会将绑定的字符串直接解析为 HTML 并插入 DOM,若内容包含恶意脚本(如用户输入的<script> 标签
、onclick 事件
等),会被执行并导致安全漏洞: -
v-html
会替换标签内部的元素
v-html
会完全替换元素的 innerHTML,导致该元素内的其他内容(包括子组件、指令)被覆盖 -
不支持 Vue 模板语法
v-html
渲染的 HTML 是纯原生 DOM,其中的 Vue 语法(如{``{ }}、v-bind、@click
等)不会被编译,无法使用 Vue 响应式系统。
vue 中添加全局变量的方法?
-
第一种:定义一个全局变量的 vue 页面,在每个需要引用的页面 import 进来。
-
第二种:全局变量挂载到
Vue.prototype
原型上。
js
Vue.prototype.color = "红色";
Vue.prototype.getColor = function () {
return "红色";
};
在 vue 页面中直接可以{``{this.color}} {``{this.getColor()}}
来使用。
- 第三种:在
main.js
的同级目录创建一个 global.js,里面需要放到 install 里
js
exports.install = function (Vue, options) {
Vue.prototype.getColor = function () {
return "红色";
};
};
最后在 main.js 中 Vue.use(global)即可,页面中的使用方法还是{``{this.getColor()}}
这样即可。
js
import global from "./global";
Vue.use(global);
- 第四种:使用 Vuex 来做全局的变量,也可以在任何页面都能够获取。
js
// 挂在到vue实例
new Vue({ store });
// 组件里使用
this.$store.state.userInfo;
- 通过
provide
和inject
依赖注入
在最顶层的父组件里声明全局变量,provide
进去,在任意子孙级组件里inject
进来使用。
js
// 在父组件里
export default {
provide() {
return {
globalData: {}, // 全局变量
};
},
};
// 在任意子组件里
export default{
inject:['globalData'],
}
- 单独在 js 文件里定义全局变量并 export 导出,在组件页面里 import 进来使用。
js
// 定义全局变量的js文件,如global.js
export const globalData = {};
// 在组件里导入使用
import { globalData } from "global.js";
vue 中如何响应路由器参数的变化?
页面路由跳转时候,如果从/use?id=a
跳到/use?id=b
,这时候组件是会复用的,不会重新创建,所以想对路由参数变化作出响应的话,就需要使用 watch
来监听 $route
对象。或者在组件中使用 beforeRouteUpdate(to,from,next){}
钩子函数来监听。
vue-router 有几种导航守卫?
-
全局前置守卫:router.beforeEach
-
全局解析守卫:router.beforeResolve
-
全局后置钩子:router.afterEach
-
路由配置独享守卫:beforeEnter
-
组件内守卫:beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave
vue-router 路由跳转解析流程,当 A 路由跳转到 B 路由都经过什么?
-
在 A 组件里调用离开守卫 beforeRouteLeave
-
调用全局前置守卫 beforeEach
-
在 B 路由配置里调用路由守卫 beforeEnter
-
再执行 B 组件的进入守卫 beforeRouteEnter
-
调用全局解析守卫 beforeResolve
-
导航被确认
-
调用全局的后置钩子 afterEach
-
触发 DOM 更新
vue-router 配置动态路由?
路由文件:
js
import HelloWorld from "@/components/HelloWorld.vue";
export default new Route({
router: [
// 动态路径参数 以冒号开头
{ path: "/user/:id", component: HelloWorld },
],
});
匹配的使用方法:
bash
<router-link :to="'/user/1">hi页面</router-link>
// 会请求/user/1
this.$router.push({name:'/user',params:{id:1} })
vue 的嵌套路由?
由多个层次构成的路由就是嵌套路由,每个路由的父级定义<router-view></router-view>
来显示子路由的页面,在路由配置里需要配置children:[{}]
子路由的信息。
vue 中<router-link>
标签解释和包含的属性
这个标签相当于 a 标签,用来做路由的跳转。
to: 属性是路由的地址。
replace: 跳转后不会留下 history 记录。
tag :把 router-link 标签变为某个标签,比如 li,span,等。
active-class:当链接被激活的时候使用的 class 样式。
vue 中路由普通加载和懒加载的写法?
第一种,普通的加载方式
js
import Hello from "Hello.vue";
const router = new VueRouter({
routes: [{ path: "/hello", component: Hello }],
});
第二种,vue 动态组件 require 进来实现的懒加载
js
const router = new VueRouter({
routes: [
{
path: "/hello",
component: (resolve) => {
require(["@/components/Hello.vue"], resolve);
},
},
],
});
第三种,es6 的 import 实现的懒加载,推荐这种,vue-router 官方也写的这种。
js
const Hello = () => import("@/componsnts/Hello.vue");
const router = new VueRouter({
routes: [{ path: "/hello", component: Hello }],
});
vue 路由的两种模式的区别
vue 路由有两种模式,hash 和 history,默认是 hash,前端路由的核心就是改变页面路径的同时不会向后台发送请求。
hash:地址栏有带一个#号,比如:http://www.abc.com/#/hello
,刷新页面和前进后退都可以用。这个是不会请求后台的,是前端页面中使用的路由。
history:需要在路由里配置 mode 为 history,利用了 html5 的 history 新增的 pushState()
和 replaceState()
方法方法,比如http://www.abc.com/user/1
,依赖 html5 特性,url 变化更灵活,但是需要服务端配合处理刷新请求,否则会返回 404 错误,在服务端比如 nginx 配置try_files $uri $uri/ /index.html;
,有前端路由处理路径匹配。
r o u t e 和 route和 route和router 区别?
-
$route 是路由信息对象,包括
path,params,query
等路由参数。 -
r o u t e r 是路由实例,包括了路由的跳转方法 ' t h i s . router是路由实例,包括了路由的跳转方法`this. router是路由实例,包括了路由的跳转方法'this.router.push()
,
this. r o u t e r . r e p l a c e ( ) ' , ' t h i s . router.replace()`,`this. router.replace()','this.router.go(),
this.$router.back()等,和一些钩子函数
router.beforeEach(),
router.afterEach()`等。
Vue3 相关
Vue 2 与 Vue 3 的主要区别
- 响应式系统
Vue 2: 使用 Object.defineProperty()进行数据劫持。
Vue 3: 使用 Proxy 进行响应式处理,提供了更全面的数据监听和更好的性能。 - API 设计
Vue 2: 使用 Options API,将同等属性的数据定义在一起。
Vue 3: 引入了 Composition API,允许你根据功能模块将同一逻辑的变量和方法放在一起。 - 性能提升
Vue 3 在性能上有所提升,尤其是响应式系统和虚拟 DOM 的改进。
新增特性
Vue 3 新增了 Fragment、Teleport 和 Suspense 等特性。 - TypeScript 支持
Vue 3 对 TypeScript 的支持更加完善。 - 生命周期函数和 API 变化
Vue 3 中一些生命周期函数的名字和用法有所变化,如 beforeCreate 和 created 被 setup 替代。
引入了一些新的 API,如 ref、reactive、watchEffect 等。
Vue3 响应式原理?
主要是使用了 es6
引入的新特性 Proxy
对象来对数据进行代理拦截,new Proxy(target, handler)
对象接受两个参数。
target:需要被代理的对象,它可以是任何类型的对象,比如数组、函数等等,注意不能是基础数据类型。
handler 它是一个对象,该对象的属性通常都是一些函数,handler 对象中的这些函数也就是我们的处理器函数,主要定义我们在代理对象后的拦截或者自定义的行为。
提供了有 get,set,has,apply,construct,deleteProperty、defineProperty 等等方法。
-
get(target, prop, receiver): 拦截对象属性的读取操作(如 obj.prop 或 obj[prop])。 实现属性访问控制、数据劫持(如 Vue3 响应式)、默认值设置等。
-
set(target, prop, value, receiver): 拦截对象属性的赋值操作(如 obj.prop = value)。 数据验证、禁止修改只读属性、触发响应式更新(如 Vue3 依赖收集)等。
-
has(target, prop): 拦截 in 操作符的判断(如 prop in obj)。 隐藏某些属性(如禁止通过 in 检测敏感属性)。
-
deleteProperty(target, prop): 拦截 delete 操作(如 delete obj.prop)。 禁止删除某些关键属性。
-
apply():拦截函数的调用操作(仅当目标对象是函数时生效)。函数参数验证、日志记录、防抖节流等。
-
construct():拦截 new 操作符创建实例的过程(仅当目标对象是构造函数时生效)。实例创建拦截、参数校验、单例模式实现等。
-
defineProperty(target, prop, desc):拦截 Object.defineProperty() 操作,用于定义或修改属性。限制属性的定义(如禁止添加特定属性)。
代码示例:
js
let obj = {
name: "小猪课堂",
age: 23,
};
let p = new Proxy(obj, {
get(target, property) {
// target 是这个obj对象 ,property就是我们访问的这个属性
console.log("执行了get", target, property);
return target[property];
},
set(target, property, newValue) {
// 数据劫持的时候 执行这里的代码 newVal就是 boj2.age = 9 =号 后面的这个值
console.log("执行了set", target, property, newValue);
// 在这里进行修改这个属性成为新的值,下次访问,才能拿到新值
target[property] = newValue;
},
});
// 触发get
console.log(obj2.age);
//修改值 触发set
obj2.age = 9;
console.log(obj2.age);
通过判断当前的 Reflect.get 返回值是 Object 类型,则再通过 reactive 方法做代理,这样就实现了深度观测。
Vue 3 的优势
更好的性能:通过 Proxy 和优化的虚拟 DOM 算法,Vue 3 提供了更快的渲染速度和更好的运行时效率。
更灵活的代码组织:Composition API 允许更灵活地组织代码,提高代码的可读性和可维护性。
更完善的 TypeScript 支持:Vue 3 为 TypeScript 提供了更好的支持,使得在 Vue 3 中使用 TypeScript 编写代码更加容易和可靠。
更多的新特性:如 Fragment、Teleport 和 Suspense 等特性为开发者提供了更多的选择和可能性。
webpack 相关
Vite 相关
Vite 是什么?
Vite 是一个新一代的前端构建工具,由 Vue.js 的创始人 Evan You 开发。它旨在提供更快的开发体验,尤其是在现代前端项目中。Vite 通过利用 ES 模块(ESM)和原生浏览器支持来实现快速的冷启动和热模块替换(HMR)。
Vite 与传统构建工具(如 Webpack)的区别是什么?
开发速度:Vite 在开发模式下使用原生 ES 模块,避免了传统构建工具在开发时打包整个应用的开销,因此启动速度更快。
按需加载:Vite 在开发模式下按需加载模块,而不是预先打包所有文件,减少了初始加载时间。
构建速度:在生产模式下,Vite 使用 Rollup 进行打包,优化了构建速度。
HMR:Vite 的热模块替换(HMR)速度更快,因为它直接更新浏览器中的模块,而不需要重新打包。
Vite 如何实现按需加载?
Vite 在开发模式下利用浏览器的原生 ES 模块支持,按需加载模块。当浏览器请求某个模块时,Vite 服务器会动态编译并返回该模块,而不是预先打包所有文件。这种方式减少了初始加载时间,提升了开发体验。
Vite 中的环境变量是如何处理的?
Vite 通过 import.meta.env 对象来访问环境变量。开发者可以在项目根目录下创建.env 文件来定义环境变量,Vite 会自动加载这些变量。例如,.env 文件中的 VITE_API_URL 可以通过 import.meta.env.VITE_API_URL 访问。
Vite 的生产构建是如何进行的?
Vite 生产构建以 "高效优化 + 兼容生产环境" 为目标,核心依赖 Rollup 实现,具体流程如下:
- 构建核心工具:
Vite 开发时用原生 ESM,但生产环境选择 Rollup 而非自研打包器,因为 Rollup 对 ESM 的 Tree-shaking 更彻底、打包产物体积更小,且支持多入口、代码分割等生产级优化。 - 自动优化逻辑:
执行 vite build 时,Vite 会自动完成以下优化:
Tree-shaking:剔除未使用的代码(如未引用的函数、组件),减少产物体积;
代码分割:按异步逻辑(如 import() 动态导入)拆分 chunk,避免单文件过大;
资源处理:压缩 CSS、JS、图片等静态资源(默认用 esbuild 压缩 JS,cssnano 压缩 CSS);
兼容处理:通过 @vitejs/plugin-legacy 插件可生成兼容旧浏览器(如 IE11)的代码(需手动安装配置);
产物分析:通过 vite build --report 生成构建报告,可视化分析产物体积。 - 配置扩展:
可在 vite.config.js 中通过 build 选项自定义生产构建逻辑,例如指定输出目录、禁用代码分割、调整压缩规则等:
Vite 的插件系统是如何工作的?
Vite 的插件系统基于 Rollup 的插件 API,开发者可以通过编写插件来扩展 Vite 的功能。Vite 插件可以用于处理文件转换、优化构建、添加自定义功能等。Vite 还提供了一些内置插件,例如@vitejs/plugin-vue 用于支持 Vue 项目。
Vite 如何优化开发体验?
-
快速冷启动:开发时不做全量打包,直接利用浏览器原生 ESM 加载模块,避免传统工具(如 Webpack)"打包所有模块再启动" 的开销,大型项目启动时间可从分钟级缩短至秒级。
-
按需加载:浏览器仅在需要时请求对应模块,服务器实时编译返回,减少初始加载的模块数量和体积(例如仅加载当前页面组件,而非整个项目组件)。
-
快速 HMR:
Vite HMR 不重新打包整个模块树,而是直接更新修改后的模块:
对 JS/TS 模块:仅更新修改的模块及其依赖;
对 Vue 组件:仅更新组件的模板、脚本或样式,保留组件状态(如表单输入值);
对 CSS:直接注入修改后的样式,无需刷新页面。
-
内置 TypeScript 支持:无需额外配置,原生支持 TypeScript、JSX、CSS Modules、PostCSS、JSON 等,开发者可直接使用,减少 "配置工具链" 的成本。
-
友好的错误提示:
编译错误(如语法错误、Vue 模板错误)会在浏览器中显示清晰的错误信息,包含文件路径、错误行号,甚至修复建议,降低调试成本。
Vite 如何处理静态资源?
原生 ES 模块支持、路径处理、资源优化和插件扩展等方式,为静态资源提供了高效且灵活的处理机制。
Vite 为什么比 Webpack 快很多?
Vite 快的原因主要有:
-
原生 ES Modules 机制:
开发阶段不需要打包,浏览器直接加载模块,减少编译步骤。
-
按需编译:
只有访问到的模块才会被编译,Webpack 则需一次性编译整个项目。
-
高效缓存策略:
Vite 大量利用 HTTP 缓存
Cache-Control: max-age=31536000,immutable
和文件系统缓存node_modules/.vite
,减少重复编译。 -
Go 语言开发的 esbuild:
预构建阶段用 esbuild 快速编译依赖库,速度比传统编译器快 10-100 倍。
Vite 如何处理 CommonJS 和 ES Modules 混用的情况?
Vite 开发阶段通过 es-module-lexer 和 @rollup/plugin-commonjs 实时识别模块类型,进行转换。
对第三方库预构建时使用 esbuild 统一转为 ESM。
如果遇到无法转换的特殊情况,需使用手动配置 optimizeDeps 显式声明依赖。
详细解释 Vite 项目启动时经历的完整流程是什么?
启动流程(开发模式):
-
初始化:
读取 vite.config.js 配置文件,合并用户和默认配置。
-
预构建阶段(依赖扫描和构建):
使用 esbuild 扫描项目依赖,识别 CommonJS 和 ESM,预编译依赖到 node_modules/.vite。
-
启动开发服务器:
-
启动一个内置 HTTP 服务,拦截请求进行动态转换(如 TS 转 JS、Sass 转 CSS)。
-
文件变化监听与热更新:
启动 websocket 服务,当文件变化,通过 websocket 向浏览器发出模块热更新信号。
-
首次加载页面:
浏览器请求 index.html,服务器解析
并拦截
<script type="module">
请求,实时编译后返回。
如何使用 Vite 构建一个多页面应用(MPA)?
js
// vite.config.js
export default {
build: {
rollupOptions: {
input: {
main: resolve(__dirname, "index.html"),
admin: resolve(__dirname, "admin/index.html"),
},
},
},
};
输出构建产物会生成对应多个入口的 JS 和 HTML 文件,实现多页面项目。
Vite 中如何优化大规模项目的冷启动时间?
- 提前对大型依赖进行手动预构建:
vite optimize
- 配置 optimizeDeps.include 明确需提前构建的依赖:
js
optimizeDeps: {
include: ["lodash", "moment", "echarts"];
}
- 减少非必要文件扫描,合理设置:
js
server: {
watch: {
ignored: ["!**/node_modules/**"];
}
}
- 代码拆分和动态导入优化:
按需加载模块,避免过多首屏加载。
Vite 中如何实现代码分割(Code Splitting)?
- 按路由分割代码(常用):
js
// 路由文件(Vue示例)
const routes = [
{
path: "/",
component: () => import("@/views/Home.vue"), // 动态导入实现代码分割
},
{
path: "/about",
component: () => import("@/views/About.vue"),
},
];
- 显式控制分割文件名:
js
const routes = [
{
path: "/dashboard",
component: () =>
import(/* webpackChunkName: "dashboard" */ "@/views/Dashboard.vue"),
},
];
什么是 webpack
webpack 是一个前端模块化打包工具,可以把项目中的资源打包成浏览器可以识别的资源。主要由 entry 入口,output 出口,loader,plugins 四个部分。
什么是 webpack 中的 bundle,chunk,module?
bundle:是由 webpack 打包出来的文件,
chunk:是指 webpack 在进行模块的依赖分析的时候,代码分割出来的代码块。
module:是指开发中的单个模块。
什么是 loader,plugin?
loader 就是一个加载器一样,使 webpack 有能力处理这些非 javascript 类型的文件。比如处理 css 的 less-loader,图片,字体等类型的,配置在 module.rules 中
plugin 就是打包用到的插件,可以参与到打包的流程中,比如最常用到的 htmlPlugin,可以把 js 和 css 打包到 html 模版文件中。
webpack 的构建流程?
-
初始化配置参数
-
初始化 compiler 对象,准备编译
-
确定 entry 入口文件并遍历
-
使用 loader 和 plugin 编译解析文件
-
输出资源文件
webpack 热更新原理?
大致原理:
-
启动本地服务后,服务端和客户端使用 websocket 作长连接;
-
webpack 监听到源文件变化,然后 webpack 会重新编译,每次编译都会生成 hash 值、已改动模块的 json 文件、已改动模块代码的 js 文件;
-
编译完成后,会通过 socket 向客户端推送当前编译的 hash;
-
客户端 websocket 监听到所有文件改动推送过来的 hash,会和上一次的对比,一致的会走缓存,不一致的会通过 ajax 向服务器获取最新资源,使用内存文件系统去替换有修改内容的地方实习局部刷新。
webpack 打包性能优化方法?
-
定位体积比较大的模块,可以用 webpack-bundle-analyzer 插件来查看。
-
提取公共模块。
-
移除不必要的文件。
-
可以通过 cdn 引入一些库文件,可以见效打包文件的体积。
-
利用缓存。
-
更换 js 的压缩插件为 uglifyjs-webpack-plugin。
-
使用 dllpllugin,这个插件可以实现把依赖的第三方的包(比如 react,vue,jquery)完全分离开,因为这些第三方包不会经常改变,除非是版本升级,所以这些第三方包只需编译一次,生成*.dll.js 文件,以后每次只打包项目自身的业务代码,可以提高编译速度。
-
因为 webpack 是单线程的,所以可以使用 happypack 用多个进程并行处理,可以提高打包速度。
webpack 和 gulp 的区别?
webpack 和 gulp 都是前端自动化构建工具,但是他们的定位不一样,webpack 侧重于模块打包,根据模块之间的依赖,生成最终生产部署的前端资源。
gulp 侧重于前端开发的流程,在 gulpfile.js 里通过配置 task 任务使用一些插件,来实现 js,css 压缩,图片压缩,编译 less,热加载。gulp 利用流的方式进行文件的处理,gulp 处理的文件是写在内存中,通过管道将多个任务和操作连接起来,所以处理更快,流程更清晰。
webpack 常用的插件?
- autoprefixer 自动补全 css3 的前缀
2. html-webpack-plugin
生成 html 文件,可以使用指定的模板 html。
-
extract-text-webpack-plugin
打包时候分离 css,形成独立的文件 -
copy-webpack-plugin
拷贝文件和文件夹到指定目录,也可以配置 ignore 忽略文件。 -
webpack.ProvidePlugin
自动配置全局加载模块,比如配置 jquery,不用每次都 import 使用。
js
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
});
-
HotModuleReplacementPlugin
模块热替换插件,修改可以实时看到。 -
uglifyjs-webpack-plugin
webpack4 中的压缩 js 插件
8. clean-webpack-plugin
清理打包目录插件,可以配置清理的文件夹目录
-
· webpack 是单线程的,这个插件是可以多线程执行任务,加快编译速度
-
webpack-dev-server
启动 server 后,可以把打包的文件存储到内存中,这样可以使用热加载功能,修改代码不需要刷新页面。
webpack 怎么自定义插件
webpack 自定义插件可以通过 es6 的 class 写一个类,或者用函数,最后再模块导出,在 webpack.config.js 中 new 插件即可。
第一种实现方式,使用函数方式,并且在函数的原型上绑定 apply 方法
js
function MyHelloPlugin(options) {
console.log("接收插件的参数:", options);
}
MyHelloPlugin.prototype.apply = function (compiler) {
compiler.plugin("emit", function (compilation) {
console.log("生成资源到output目录之前:", compilation);
});
compiler.plugin("done", function () {
console.log("编译完成了");
});
};
module.exports = MyHelloPlugin;
// 在webpack.config.js中引入并入使用
const MyHelloPlugin = require("./webpack.myplugin.js");
new MyHelloPlugin({ option: true });
第二种方式,使用 class,并在里面添加 apply 方法
js
class MyHelloPlugin {
constructor(options) {
console.log("传递的参数:", options);
}
apply(compiler) {
compiler.plugin("done", function (compilation) {
console.log("编译完成");
});
}
}
module.exports = MyHelloPlugin;
// 在webpack.config.js中引入并入使用
const MyHelloPlugin = require("./webpack.myplugin.js");
new MyHelloPlugin({ option: true });
以上两种方式都可以,但是必须要写 apply 方法,因为自定义插件运行的时候会自动应用插件对象绑定的这个 apply 方法。
apply
方法的回调返回一个 compiler 对象,这个对象是继承自 Tapable 类,compiler 可以监听整个编译的过程,它里面包含整个 webpack 声明周期钩子,比如有 beforeRun,watchRun,run,beforeCompile,compile,compilation,emit,afterEmit,done,watchClose 等等钩子。
Tapable
这个类暴露很多的钩子函数,compiler 中的钩子其实用到的是这里面的,比如写插件需要的 plugin 方法,apply 方法都是继承的 tapable 里面。这个类的作用实际上就是一个事件管理器,是一个基于发布订阅者的模式,专注于事件的触发和处理。
compiler
的钩子的回调是返回一个 Compilation 对象,这个对象是继承 Compiler,它的主要作用是在打包的过程中可以获得打包的模块和依赖,同时也提供了一些钩子函数,在这个阶段模块可以被加载(loaded),封存(sealed),优化(optimized),分块(chunked),哈希(hashed),重新创建(restored),当然它也是属于 compiler 声明周期中的一个钩子。
Koa 相关
koa 中的 koa-body 是什么?
是 koa 中的一个插件,可以处理请求和实现上传文件等操作,可以代替请求处理的 koa-bodyparser 和图片文件上传的 koa-multer。
Nodejs 相关
什么是 PM2?
pm2 是一个 node 进程管理器,可以利用它来简化很多 node 应用管理的繁琐任务,如性能监控、自动重启、负载均衡等。
Web 前端 相关
理解 web 标准,了解可用性,可访问性和安全性
web 标准是一些列标准的集合,主要包括 结构,表现,行为。
结构标准:是指用 html,xhtml,xml 来描述页面的结构。
表现标准:是指使用 css 样式来美化页面结构,使它更具有美感。
行为标准:是指用户和页面之间有一定的交互,可以使页面结构和表现发生改变,比如 W3c 制定的 EcmaScript 标准来实现。
可用性:是指页面从用户的感官上来说是否容易上手,容易理解,容易操作完成,可用性好说明产品质量高。
可维护性:是指系统出现问题时候可以快速定位解决,成本低,可维护性高,而且代码结构是否清晰符合标准,让其他的开发者更容易上手维护。
可访问性:是指在不同浏览器,不同屏幕下是否可以很好的去展示,并且对不同的用户或者有残疾的用户是否都可以使用。
什么是 html 语义化?
使用语义适当的标签去构建 html 页面的结构,比如使用 h1-h6 设置标题,使用 header,footer,nav,aside 标签来划分结构。html 语义化可以使 html 的页面结构变得更加清晰,即使没有 css 的情况下页面也能够很好的呈现出结构,还能提升用户体验,方便维护,方便爬虫抓取和 seo 搜索引擎优化,也便于不同设备之间的访问。
什么是响应式网页设计?
网页可以在不同尺寸的设备上面可以自动调整显示和布局,以适应不同尺寸屏幕的浏览体验。
web 安全及防护
-
sql 注入:就是通过把 SQL 命令插入到 Web 表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令。
-
xss 攻击:指的是攻击者往 Web 页面里注入恶意脚本代码,当用户浏览网页时候执行恶意代码来窃取用户的信息。
-
csrf 攻击:指的是打开未受信任的网站,导致本地存储的 cookie 和个人信息泄露,而造成的被盗用了个人信息。比如你刚转了钱,本地保存了你的银行的个人信息,接着你又不小心访问了一个网站,这个网站就会盗取你的信息,去盗取你的银行信息。
web 性能优化有哪些?
优化的目的:
用户角度:网页加载速度快,操作响应及时,用户体验好。
服务商角度:能减少页面请求、和占用的带宽资源,提高服务器的处理响应速度。
主要分为:
一、页面级别优化
-
减少 http 请求。
-
使用外部脚本和 css,可以缓存下来。
-
打包资源优化。
-
避免请求重复的资源,减少不必要的 http 跳转。
-
按需加载资源,比如 js 和图片懒加载。
-
从设计层面上简化页面,避免过于复杂的交互操作。
-
把 js 放到底部,把 css 放到 header,可以避免造成阻塞。
-
使用骨架屏减缓白屏等待焦虑,可以用 base64 图片代替展示,数据加载完了就替换当前图片。
二、代码级别优化
-
减少 dom 的操作,避免重绘和回流而影响页面性能。
-
减少闭包的使用,因为闭包的变量都会在内存中,过多会造成内存消耗。
-
css 优化,减少使用
!import
和行内标签,可以用类和标签选择器,同样的功能建议抽取出来一个 common css。 -
在手机端的页面,可以添加样式 transform:transition3d(0,0,0)来开启硬件加速,会使页面流畅。
-
避免大量的声明全局变量,因为这些变量会绑定到 window 全局变量上,会可能造成内存溢出。
三、服务器优化
-
提高硬件环境。
-
优化 nginx,比如加大进程数,配置 gzip 压缩减少网络上所传输的数据量,配置超时时间,缓存策略优化等
移动端性能优化有哪些?
-
尽量使用 css3 的动画开启硬件加速,比如 transform:transition3d(0,0,0)。
-
使用 touch 事件代替 click 事件。
-
避免使用 css3 的阴影效果,复杂场景下渲染成本高。
-
不滥用 float,因为 float 在渲染时候计算了比较大,会影响性能。
-
不滥用 web 字体,web 字体需要下载,解析,重绘,尽量减少使用。
-
触摸和滚动优化,减少后台任务,优化定位使用,减少电量消耗。
-
pc 端的性能优化同样适用在手机端。
什么是 web 语义化?
Web 语义化是指使用语义恰当的标签,使页面有良好的结构,页面元素有含义,能够让人和搜索引擎都容易理解。比如使用 footer,body,header,section 标签等,好处是便于阅读和理解,便于爬虫抓取和搜索引擎优化。
什么是前端模块化?
可以理解为一组自定义业务的抽象封装,是根据项目的情况来进行封装组合到一起的,比如我们可以分为登录模块,评论模块。模块可维护性好,组合灵活,方便调用,多人协作互不干扰。因为有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。
什么是前端组件化?
指对具体的某个功能的封装,比如所有的分页可以封装为分页组件来统一使用,以此来达到组件复用,提高开发效率。
什么是前端工程化?
概念:指使用软件工程的技术和方法来进行前端项目的开发、维护和管理。
前端工程化包含如下:
-
代码规范: 保证团队所有成员以同样的规范开发代码。
-
分支管理: 不同的开发人员开发不同的功能或组件,按照统一的流程合并到主干。
-
模块管理: 一方面,团队引用的模块应该是规范的;另一方面,必须保证这些模块可以正确的加入到最终编译好的包文件中。(以上两点可以总结为模块化或者组件化开发。)
-
自动化测试:为了保证和并进主干的代码达到质量标准,必须有测试,而且测试应该是自动化的,可以回归的。
-
构建:主干更新以后,自动将代码编译为最终的目标格式,并且准备好各种静态资源,
-
部署。 将构建好的代码部署到生产环境。
什么是 spa 单页面应用?
spa(single page web application)单页面应用,就是只有一个 Web 页面的应用,浏览器一开始会加载所需要的 HTML、CSS 和 js 资源,所有的操作通过 js 来动态更新该页面,这样的程序就叫做 spa 单页面应用。
优点:
-
用户体验好,避免不必要的跳转页面和重复渲染。
-
减轻服务器端压力。
-
前后端分离,各司其责,便于优化管理。
缺点:
-
不利于 seo 搜索优化。(可以通过 ssr 服务器端渲染等方法)
-
首屏加载时间过长。
什么是 SSR 服务器端渲染?
就是在服务器端生成页面的 html,再发送到浏览器。与单页面应用相比,服务器端渲染更好的利于 seo 搜索优化,和减少首页加载时间。但是同时也存在缺点,比如学习成本比较大,也会加大服务器的压力。
浏览器相关
浏览器内核有哪些?
-
Trident: 主要是 IE,一些双核浏览器也会用到,比如 360 浏览器,腾讯浏览器,百度浏览器
-
Gecko:现在主要有火狐
-
Webkit:苹果自主研发的内核,使用的浏览器相当多,比如 safari,Google chrome,opera,以及 360,腾讯这些浏览器的高速模式都是用的这个。
-
Blink:基于 webkit,Google 与 Opera 共同开发,现在 google 在用。
渲染引擎工作流程?
-
解析 html 生成 dom 树
-
解析 css 生成 cssom 规则树
-
将 dom 树和 cssom 规则树合并在一起生成 render 渲染树
-
遍历 render 渲染树的节点开始布局,计算每个节点的位置大小信息。
-
将 render 渲染树每个节点绘制到页面来显示。
注意:渲染的时候会出现阻塞的情况,当浏览器碰到 script 标记时候,dom 构建将暂停,直到脚本执行完毕,所以要把脚本放到最下面,避免阻塞。css 的话要放在最上面。
什么是重绘(repaint)和回流(reflow)
重绘:当 render 渲染树 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如添加个背景色,设置个文字颜色,则就叫称为重绘。
回流:当 render 渲染树 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,就称为回流(reflow)。
回流何时发生:
当页面布局和几何属性改变时就需要回流。下述情况会发生浏览器回流:
-
添加或者删除可见的 DOM 元素;
-
元素位置改变;
-
元素尺寸改变------边距、填充、边框、宽度和高度
-
内容改变------比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
-
页面渲染初始化;
-
浏览器窗口尺寸改变------resize 事件发生时;
注意:回流必定触发重绘,重绘不一定触发回流,重绘的开销比较小,回流的代价比较高。
网络协议相关
http 协议的介绍
http 协议(Hyper Text Transfer Protocol 超文本传输协议),是一个应用层的协议。
http 的特点是?
-
支持客户端/服务器模式,也就是请求/响应的模式。
-
简单快速:是说我们只需要指定 get,post,head 这些请求方式和访问路径,就可以进行访问了。
-
灵活:是指现在的版本中可以传输任意的数据,文件,xml,json 都可以,只需要指定 content-type 对应的类型即可。
-
无连接:是指每次链接只处理一个请求,服务器处理完请求后,响应给客户端并收到客户的应答后就断开链接。
-
无状态:是说 http 协议对事务的处理没有记忆能力,如果中断了,就必须要重新传输了。
http 每个版本的区别有哪些?
http 有 4 个版本,http/0.9,http/1.0,http/1.1,http/2.0,http/3.0。
- http/0.9:在 1991 年发布,是 http 协议最初的版本,功能简陋,只支持 get 的请求方式,也不支持请求头,服务器只能返回 html 格式的字符串。
缺点:功能比较简陋,只能处理 get 请求,返回类型比较少。
- http/1.0:在 1996 年发布,比 0.9 支持了很多的内容;
bash
支持get,post,head请求
支持请求头和响应头,状态码,缓存,内容编码。
服务器响应对象不仅限于超文本了,content-type可以设置更多的格式,图片,视频等。
新建一个tcp连接,只能发送一个请求,服务器也只能处理一个请求。
部分浏览器支持connection:keep-alive;长连接,可以在请求结束后服务器不关闭连接。
缺点:tcp 连接新建需要三次握手,每个 tcp 连接只能发送一个请求,而且发送后服务器就会断开此次连接,这样的处理比较耗费效率,也没办法复用。虽然也有一些浏览器实现了 Connection:keep-alive;长连接,但是并不是每个浏览器都支持这个。
- http/1.1:在 1997 年发布,添加了很多优化性的内容;
bash
支持持久连接,不用声明Connection: keep-alive;tcp新建连接后默认不关闭,可以被对个请求复用。客户端也可以主动在最后一个请求发送Connection:close;来关闭连接。
支持在一个tcp连接里,支持客户端可以同时请求多个连接,但是服务器还是需要按照顺序来处理,谁先发送的就先处理谁。
新增了多个请求的方法,options,put,delete,connect,trace。
新增了一些状态码,身份认证机制,支持文件断点续传。
添加了一些cache的新特性,增加Cache-control属性。
缺点:虽然支持多个请求,但是在服务器端需要排队处理,会出现请求堵塞的情况。消息头部无法压缩,比较占字节,会浪费一些网络资源。
- http/2.0:在 2015 年发布,在谷歌,ie11 以及火狐等现代浏览器已经可以支持 http/2.0 协议了;
bash
二进制协议,在http1.1里是超文本传输协议,http2.0已经可以支持二进制协议了,体积更小,易于解析不会出错。
真正的多请求处理,客户端请求多个到服务器端,在服务器端处理是可以多对多的,也不用服务器按照请求的顺序去处理,即使某个请求堵塞了也不会影响到其他的请求处理。
③解决了http1.1的请求头部压缩问题,加快请求传输速度。
服务器推送,服务器可以主动向客户端推送资源,资源包括页面,css,js,图片等资源,客户端检测到后解析处理,这样这些资源已经在本地了,浏览器可以更快的拿到而不是再通过网络重新请求,这样减少了客户端去请求服务器再到服务器响应的过程,大大提高了效率和资源的利用。
- http/3.0:是在 2022 年发布,它基于 QUIC 传输协议,该协议运行在 UDP 之上。与之前的版本相比,HTTP/3 有以下一些显著特点:
bash
解决 TCP 队头阻塞:在 HTTP/2 中,虽然解决了应用层的队头阻塞,但底层依赖的 TCP 协议本身存在队头阻塞问题。而在 HTTP/3 中,QUIC 在连接级别和流级别都实现了可靠的、有序的传输,不同流的数据包丢失互不影响,丢失的包只影响它所属的那个流,其他流的数据包只要到达就能被处理。
更快的连接建立:QUIC 深度集成了 TLS 1.3,通常建立安全连接只需 0-RTT(零往返延迟)或 1-RTT,首次连接也比 TCP+TLS 快,能够有效减少连接建立的延迟。
连接迁移:QUIC 使用连接 ID 而不是 IP 地址 + 端口来标识连接。当用户网络切换,如从 WiFi 切到 4G,IP 改变时,QUIC 连接可以无缝迁移而不断开,而 TCP 则需要重新握手。
http 和 https 的区别?
-
https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。
-
http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。
-
http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。
-
http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。
注:ssl 是一个位于 tcp 和 http 之间的安全协议,为数据通讯提供安全支持
http 状态码 301 和 302 的区别?
301 redirect: 301 代表永久性转移(Permanently Moved)
302 redirect: 302 代表暂时性转移(Temporarily Moved )
ETag 是什么?
etag 是 entity tag 的缩写,意思就是实体标签的意思,它是 http 协议中请求 head 中的一个属性,用来帮助服务器控制 web 端的缓存验证。
tcp 和 udp 的区别?
两个都是传输层的协议
-
TCP 面向连接(如打电话要先拨号建立连接);UDP 是无连接的,即发送数据之前不需要建立连接
-
TCP 提供可靠的服务。也就是说,通过 TCP 连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP 尽最大努力交付,即不保证可靠交付
-
UDP 具有较好的实时性,工作效率比 TCP 高,适用于对高速传输和实时性有较高的通信或广播通信。
-
每一条 TCP 连接只能是点到点的;UDP 支持一对一,一对多,多对一和多对多的交互通信
-
TCP 对系统资源要求较多,UDP 对系统资源要求较少。
get 和 post 的区别?
-
get 用于获取数据,post 是提交数据。
-
get 提交的参数追加在 url 的后面,用?和&来连接,post 的参数放在 Request Body 提交。
-
get 的 url 会有长度限制,最多 2K,post 则不会限制数据大小,而且还可以设置提交的数据类型(文件,json 这些)。
-
get 是不安全的,因为 url 上参数都已经暴露了,post 是安全的。
-
get 的请求参数会被保存在浏览历史记录里,post 则没有。
-
get 请求可以被浏览器缓存,post 则没有。
http 请求的过程?
-
当我们输入某个地址后,首先需要 dns 域名解析这个地址,根据一系列的 dns 解析域名的步骤找到对应服务器的 ip。
-
tcp 的三次握手建立连接。
-
建立 tcp 的链接后发送 http 请求的报文,包括请求行,消息报头,请求正文这些。
-
http 响应报文,包括状态行,消息报头,响应正文,浏览器得到 html 代码。
-
最后浏览器解析得到的 html,将结果呈现给用户。