前端一些心得笔记

HTML基础

1.HTML文件中的DOCTYPE是什么作用?

HTML超文本标记语言:是一个标记语言,就有对应的语法标准

DOCTYPE即Document Type,网页文件的文档类型标准。

主要作用是告诉浏览器的解析器要使用哪种HTML规范或XHTML规范来解析页面。

DOCTYPE需要放置在HTML文件的标签之前

2.HTML、XML、XHTML之间有什么区别

他们都属于标记语言

语言中文名说明
HTML4超文本标记语言主要用于做截面呈现。HTML是先有实现,后面才慢慢制定标准的,导致HTML非常混乱,语法非常的不严谨
XML可扩展标记语言主要用于存储数据和结构。语法严谨,可扩展性强。由于JSON也有类似作用但更轻量高效,XML的市场变得越来越小。
XHTML可扩展超文本标记语言属于加强版的HTML,为解决HTML的混乱问题而生,在语法方面变得和XML一样严格。另外,XHTML的出现也催生了HTML5,让HTML向规范化严谨化过渡。
HTML5超文本标记语言在HTML的基础上进行拓展,用于页面呈现(目前标准)

XML的要求会比较严格:

1.有且只能有一个根元素

2.大小写敏感

3.正确嵌套

4.必须双引号

5.必须闭合标签

3.前缀为data- 开头的元素属性是什么?

这是一种为HTML元素添加额外数据信息的方式,被称为自定义属性。

4.对HTML语义话的一个理解

语义化的好处(利于SEO、可阅读性好)

5.HTML5对比HTML4有哪些不同之处?

1.只有一种DOCTYPE文件类型声明(统一标准)

2.增加了一些新的标签元素(功能,语义化)

常用功能:vedio、canvas

常用语义化:section、footer、header、nav…

3.input支持了几个新的类型值,如:date、email、url等

4.新增了一些标签属性,如:charset、async

5.新增的全域属性,如:contenteditable、draggable、hidden…

6.新增API,如:本地存储,地理定位,canvas绘图,拖拽API,即时通信…

6.meta标签哪些常用用法?

mate标签的具体功能一般由name/http-equiv和content两部分属性来定义。

  • 如果设置name属性,则它描述的是网页文档的信息(例如:作者、日期和时间、网页描述、关键词)
  • 如果设置http-equiv属性,则它描述的相当于是HTTP响应头信息(例如:网页内容信息、网页缓存等)

一些常用的功能:

1.设置网页关键词(SEO)

2.设置网页视口(viewport)控制视口的大小、缩放和比例等

3.设置http响应头:Content-Type网页内容类型(字符集)

7.img标签的srcset的作用是什么?

处理响应式图片的方式(css媒体查询换的是背景图片,而不是img标签的src)

开发者和设计师们竞相寻求处理响应式图片的方法。这的确是一个棘手的问题,因为我们对同一个网站在众多设备宽度下,使用同一个网站在众多设备宽度下,使用同一图像源。

其实通过使用img标签的srcset属性,可定义一组额外的图片集合,让浏览器根据不同的屏幕状况选取合适的图片来显示。

如果你的响应式需求比较简单,只需要针对屏幕的不同dpr(device pixel ratio,设备像素倍率)来决定图片的显示的话,那么就只要这么写:

对于可变宽度的图像,我们使用srcset搭配w描述符以及sizes属性。

  • w描述符告诉浏览器列表中的每个图像的宽度
  • sizes属性需要至少包含两个值,是由逗号分隔的列表

根据最新规范,如果srcset中任何图像使用了w描述符,那么必须要设置sizes属性。

sizes属性有两个值:

1.第一个是媒体查询条件

2.第二个是图片对应的尺寸值,

在特定媒体条件下,此值决定了图片的宽度。

需要注意是,源图尺寸值不能使用百分比,如果要用100%,vw是唯一可用的CSS单位。

注意:测试时,清除缓存测试,因为一旦加载了高清图,就不会也没必要,回过去再用小图替换了。且我们无法确定究竟显示哪张图像,因为每个浏览器根据我们提供的信息挑选适当图像的算法是有差异的。

8.响应式图片处理优化:Picture标签

picture元素就像是图像和其源的容器。浏览器仍然需要img元素,用来表明需要加载的图片在picture下放置零个或多个source标签,以及一个img标签,为不同的屏幕设备和场景显示不同的图片。如果source匹配到了,就会优先用匹配到的,如果没有匹配到会往下继续找。

使用picture元素选择图像,不会有歧义。

浏览器的工作流程如下:

  • 浏览器会先根据当前情况,去匹配和使用source提供的图片
  • 如果未匹配到合适的source,就使用img标签提供的图片

9.在script标签上使用defer和async的区别是什么?

script标签存在两个属性,defer和async,因此script标签的使用分为三种情况:

1.< script src = “example.js”>< /script>

没有defer或async属性,浏览器会立即加载并执行相应脚本。

不等待后续加载的文档元素,读到就开始加载和执行,此举会阻塞后续文档的加载

2.< script async src = “exaple.js”>< /script>

有了async属性,表示后续文档的加载和渲染与js脚本的加载和执行是并行进行的,即异步执行

3.< script defer src = “exaple.js”>< /script>

有了defer属性,加载后续文档的过程和js脚本的加载是并行进行的(异步),此时的js脚本不仅加载不执行,js脚本的执行需要等待文档所有元素解析完成后,DOMContentLoaded事件触发执行之前。

async和defer的特点

1.defer和async在网络加载过程是一致的,都是异步执行的

2.两者的区别,脚本加载完成之后,async是立刻执行,defer会等一等

所以,js脚本加上async或defer,放在头部可以减少网页的下载加载时间,如果不考虑兼容性,可以用于优化页面加载的性能。

async使用场景:适合于不依赖于其他js文件的脚本加载

10.前端web存储的方式

1.localStorage (存储大小5M)

2.sessionStorage (存储大小5M,关闭浏览器就会自动销毁数据)

3.cookies(存储大小4k)(可以设置过期时间,缺点存储容量太小,操作不方便)

4.web SQl (用的少,sql语句,会影响网页性能)

5.IndexDB (适合存储大量的数据 大小>=250M,甚至没上限。异步操作,性能好)

CSS基础

1.CSS选择器的优先级是什么样的?

CSS选择器的优先级顺序:

!important>内嵌样式>ID选择器>类选择器>标签选择器>通配符>继承

优先级计算

优先级是由A、B、C、D四个值来决定的,具体计算规则如下

  • A={如果存在内联样式则为1,否则为0}
  • B={ID选择器出现的次数}
  • C={类选择器、属性选择器、伪类选择器出现的总次数}
  • D={标签选择器、伪元素选择器出现的总次数}

2.通过CSS的哪些方式可以实现隐藏页面上的元素?

方式说明
opacity:0通过将元素的透明度设置为0,实现看起来隐藏的效果,但是依旧会占用空间并可以进行交互效果
visibility:hidden与透明度为0的方案非常类似,会占据空间,但不可以进行交互
overflow:hidden只会隐藏溢出的部分,占据空间并不可交互
display:none可以彻底隐藏元素并从文档流中消失,不占据空间也不能交互,且不影响布局
z-index:-999通过将元素的层级置于最底层,让其他元素覆盖住它,达到看起来隐藏的效果
transform:scale(0,0)通过将元素进行缩放,缩放为0;依然会占据空间,但不可交互
left:-9999999px通过将元素定位到屏幕外,达到看起来看不到的效果

3.px,em,rem之间的区别?

px:绝对单位

em:相对单位,相对于父盒子的font-size

rem:相对单位,相对于html根标签

=>rem 一般可以用于rem适配

适配原理是什么?

1.使用rem作为单位

2.动态的设置不同屏幕下的html的font-size(媒体查询,js(插件flexible.js))

4.让元素水平居中的方法有哪些?

方法一:使用margin

通过元素设置左右的margin为auto,实现让元素居中

方法二:使用定位

父盒子相对位移,子盒子绝对位移去实现居中

方法三:text-align:center

子盒子转成行内块元素,父盒子添加text-align:center

方法四:flex盒子

父盒子的justify-content和align-items设置center

5.在CSS中哪些定位方式?

position:

1.static(默认)

2.fixed 固定定位

3.absolute 绝对定位

4.relative 相对定位

5.sticky 粘性定位(吸顶效果)

6.如何理解z-index?

可以将它看做三维坐标中的z轴方向上的图层层叠顺序

元素默认的z-index为0,可通过修改z-index来控制设置了position值的元素的图层位置。

如果父辈元素有定位,且配置了z-index,优先按照父辈元素定位的z-index进行层级比较。

7.如何清除浮动?

1.定高法

给浮动元素定一个指定高度

2.添加一个子盒子,clear:both;

3.overflow:hidden;

4.利用伪元素 clearfix

8.BFC的理解

BFC:块级格式化上下文,是一块独立的渲染区域。将处于BFC区域内和区域外的元素进行相互隔离。

触发BFC的方式:

1.position:absolute/fixed

2.float:left/right

3.overflow:非visible hidden/auto/scroll

4.display:inline-block

BFC运用:

1.处理块级元素,上下margin合并的问题

2.处理margin塌陷

3.清除浮动

4.实现自适应布局

9.什么是CSS Sprites以及它的好处?

CSS Sprites,俗称雪碧图,精灵图。这是一种CSS图片合并技术,就是将CSS中原先引用的一些较小的图片,合成一张稍大的图片后再引用的技术方案。它可以减少请求多张小图片带来的网络消耗(因为发起的HTTP请求次数变少了),并实现提前加载资源的效果。

缺点:

  • CSS Sprites中的任意一个小图标改动,都需要重新生成大图;并且用户端需要重新下载整张大图,这就降低了浏览器缓存的优势。
  • 随着HTTP2的逐渐普及,HTTP2的多路复用机制可以解决请求多个小图片所创建多个HTTP请求的消耗,让CSS Sprites存在的价值降低了
  • 图片放大就容易失真

目前其他主流的处理图片的方案:iconfont字体图标,svg矢量图标

10.你对媒体查询的理解是什么样的?

@media screen and (min-width :992px)and(max-width:x1200px){

.box{

width:980px

}

}

11.你对盒子的理解是什么?标准盒子模型和怪异盒子模型的区别是什么?

盒子大小的组成:

  • 内容(content)
  • 内边距(padding)
  • 边框(border)
  • 外边距(margin)

标准盒子模型和怪异盒子模型的区别主要在元素尺寸的表示上。

盒模型的指定:

在CSS3中,我们可以通过设置box-sizing的值来决定具体使用何种盒模型

  • content-box 标准盒模型
  • border-box 怪异盒模型

标准盒模型:

box-sizing:content-box(默认值)

12.伪类和伪元素的区别

什么是伪类?

伪类是以冒号:为前缀,可被添加到一个选择器的末尾的关键字。

它用于样式在元素的特定状态下应用到元素上。比如:checked、:hover、:disabled、:first child等。

注意:伪类,虽然是写法比较特殊,css选择器的权重,和类一致的。

什么是伪元素?

伪元素用于创建一些不在DOM树中的元素,并为其添加样式。伪元素的语法和伪类类似,可以一个冒号或两个冒号为前缀。

比如,可以通过:before、:after来在一个元素前、后增加一些额外的文本并为他们添加样式

并且,虽然用户可以看到这些文本,但其实他们并不在DOM树中。(伪元素是无法注册事件的,所以不要通过js控制伪元素。)

两者的区别

虽然他们在语法上是一致的,但是他们的功能区别还是非常明显的

  • 伪类是用来匹配元素的特殊状态的
  • 伪元素是用来匹配元素的隶属元素的,这些隶属元素可以在界面中展示,但在DOM中不体现

13.flex的理解

flex语法小结:

display:flex 设置flex盒子

flex-direction:调整主轴方向 row:主轴方向为水平向右 column:主轴方向为竖直向下

justfy-content:主要用来设置主轴方向的对齐方式 center space-around space-between flex-start flex-end

align-items:主要用来设置侧轴方向的对齐方式 stretch(拉伸,不设高度,会拉伸拉满100%) flex-start flex-end center

flex-wrap:wrap 换行

align-content 用来设置多行的flex容器的排列方式 center space-around space-between flex-start flex-end stretch

JS基础

1.什么是变量提升?

变量提升是负责解析执行代码的JavaScript引擎的工作方式产生的一个特性。

JS引擎在运行一份代码的时候,会按照下面的步骤进行工作:

1.首先,对代码进行预解析,并获取声明的所有变量

2.然后,将这些变量的声明语句统一放到代码的最前面

3.最后,开始一行一行运行代码

2.JS参数是以什么方式进行传递的?

基本数据类型和复杂数据类型在传递时,会有不同的表现

简单类型,进行参数传递,传递的是值本身

复杂类型,进行参数传递时,传递的是地址

3.JS垃圾回收是怎么做的?

JS中内存的分配和回收都是自动完成的,内存在不使用会被垃圾回收器自动回收。

正因为垃圾回收器的存在,许多人认为JS不用太关心内存管理的问题。

但如果不了解JS内存管理机制,我们同样非常容易内存泄漏(内存无法被回收)的情况。

3.1内存的生命周期

JS环境中分配的内存,一般有如下生命周期:

1.内存分配:当我们声明变量、函数、对象的时候,系统会自动为他们分配内存

2.内存使用:即读写内存,也就是使用变量、函数等

3.内存回收:使用完毕,由垃圾自动回收不再使用的内存

全局变量一般不会回收,一般局部变量的值,不用了,会被自动回收

3.2垃圾回收算法说明

所谓垃圾回收,核心思想就是如何判定内存是否已经不再使用了,如果是,就视为垃圾,释放掉

3.3引用计数

IE采用的引用计数算法,定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他条件指向它了,说明该对象已经不再需要了。

它有一个致命的问题:循环引用。

如果两个对象相互引用,尽管他们已不再使用了,垃圾回收器不会进行回收,导致内存泄漏。

3.4标记清除法

现代的浏览器已经不再使用引用计数法了。

现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的。

标记清除法:

  • 标记清除算法将“不再使用对象”定义为“无法达到的对象”
  • 简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内部的对象
  • 凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用。稍后进行回收。

从这个概念可以看出,无法触及的对象包括了没有引用的对象这个概念(没有任何引用的对象也是无法触及的对象)

4.JS作用域链的理解

JS在执行过程中会创建一个个的可执行上下文。(每个函数都会创建这么一个可执行上下文)

每个可执行上下文的词法环境中包含了对外部词法环境的引用,可通过该引用来获取外部词法环境中的变量和声明等。这些引用串联起来,一直指向全局的词法环境,形成一个链式结构,被称为作用域链。

简而言之:函数内部可以访问到函数外部作用域变量,而函数外部还可以访问到全局作用域的变量,这样变量的作用域访问的链式结构,被称之为作用域链

5.对闭包的理解

什么是闭包?

闭包是函数和声明该函数的词法环境的组合

更通俗一点的解释是:

内层函数,引用外层函数上的变量,就可以形成闭包(常用于实现数据私有)

注意:外部函数中,一般需要return引用内部函数(这样内存才不会被释放掉)

会导致内存泄漏,所以结尾要把函数=null,断开了内部函数的引用,对应缓存的变量内容也会被释放掉

6.JS中数据类型的隐式转换规则

在if语句、逻辑语句、数学运算逻辑、==等情况下都可能出现隐式类型转换。

原始值转化数值类型转化为字符串类型转化为Boolean类型
false0‘false’false
true1‘true’true
00‘0’false
11‘1’true
‘0’0‘0’true
‘1’1‘1’true
NaNNaN‘NaN’false
InfinltyInfinlty‘Infinlty’true
-Infinlty-Infinlty‘-Infinlty’true
‘’0‘’false
‘20’20‘20’true
‘twenty’NaN‘twenty’true
[]0“”true
[20]20‘20’true
[10,20]NaN‘10,20’true
[‘twenty’]NaN‘twenty’true
[‘ten’,‘twenty’]NaN‘ten,twenty’true
function(){}NaN‘function(){}’true
{}NaN‘[object,Object]’true
null0‘null’false
undefinedNaN‘undefined’false

注意:判断时尽量不要用‘’,要用‘=’(==判断,如果类型不同,默认会进行隐式转化再比较)

7.对原型链的理解

  • 什么是原型对象
  • 构造函数,原型对象,实例对象
  • 原型链如何形成的

原型对象

在JS中,除去一部分内建函数,绝大多数的函数都会包含一个叫做prototype的属性,指向原型对象,基于构造函数创建出来的实例,都可以共享访问原型对象的属性。

例如我们的hasOwnProperty,toString方法等其实是Object原型对象的方法,它可以被任何对象当做自己的方法来使用。

8.对于继承的理解

为什么要学习继承?

写的构造函数,定义了一个类型(人类),万一项目非常大,又有了细化的多个类型(老师,工人,学生)

学习了继承,可以让多个构造函数之间建立关联,便于管理和复用

什么是继承?

继承:从别人那里,继承东西过来

代码层面的继承:继承一些属性和方法

8.1 继承-原型继承

分析需求:

人类,属性:name,age

学生,属性:name,age,className

工人,属性:name,age,companyName

无论学生,还是工人,=>都是人类,所以人类原型上有的方法,他们都应该要有

为什么要有继承:

将多个构造函数,建立关联,实现方便管理和方便复用

原型继承:通过改造原型链实现的继承,利用原型链的特征实现继承。(继承方法)

8.2继承-组合继承

组合继承有时候也叫做伪经典继承,指的是将原型链和借用构造函数call技术组合到一块,

从而发挥二者之长的一种继承模式,其背后的思路:是使用原型链实现对原型属性和方法的继承(主要是方法)

而通过借用构造函数来实现对实例属性的继承。这样既通过在原型上定义方法实现了函数复用,又能保证每个实例都有它自己的属性。

8.3寄生组合继承

实例上有参数,而原型__ proto __ 上不需要再有这些属性,所以利用Object.create改装下Object.create(参数对象),Object.create会创建一个新对象,而且这个新对象 __ proto __会指向传入参数对象

8.4es6-class实现继承extends

例子:

class Person {

constructor(name, age) {

​ this.name = name

​ this.age = age

}

run() {

​ console.log(‘我会跑’);

}

}

class Student extends Person {

constructor(name, age, className) {

​ super(name, age)

​ this.className = className

}

study() {

​ console.log(‘我会学习’);

}

}

const Stu = new Student(‘zs’, 23, 1812321)

console.log(Stu);

9.如何判断是否是数组?

Object.prototype.toString方法,可以很方便的判断类型

判断数组的话,用Object.prototype.toString.call(要判断的元素)或者Array.isArray(要判断的元素)

10.this的理解

this指向情况:

1.函数调用模式 fn() 指向window (默认绑定)

2.方法调用模式 obj.fn() 指向调用者 指向obj(隐式绑定)

3.上下文调用模式 call apply bind 想指向谁就指向谁(显示绑定,硬绑定)

call和apply、bind区别

call是一个个传递 fn.call(this指向内容,参数1,参数2,参数3…)

apply是数组形式传递 fn.apply(this指向内容,[参数1,参数2,参数3…])

bind:const fn = fn.bind(this指向的内容)

4.构造函数模式 new Person() 指向创建实例 (new绑定)

11.箭头函数中的this指向什么

箭头函数不同于传统函数,它其实没有属于自己的this,

它所谓的this是捕获其外层上下文的this值作为自己的this值。

并且由于箭头函数没有属于自己的this,它是不能被new调用的。

12.Promise的静态方法

promise的三个状态:pending(默认) fullfilled(成功) rejected(失败)

1.resolve函数被执行时,会将promise的状态从pending改成fullfilled成功

2.reject函数被执行时,会将promise的状态从pending改成rejected失败

Promise.reject()

Promise.resolve()

Promise.all([promise1,promise2,promise3])等待原则,是在所有promise都完成后执行,可以用于处理一些并发的任务

Promise.race([promise1,promise2,promise3])赛跑,只要三个promise有一个满足条件,就会执行.then(用的比较少)

13.宏任务与微任务是什么

宏任务:主线程代码,setTimeout等属于宏任务,上一个宏任务执行完成,才会考虑下一个宏任务。

微任务:promise.then.catch的内容,属于微任务,满足条件的微任务,会被添加到当前宏任务的最后去执行(上一个宏任务完成后,下一个宏任务开始前执行)

js执行主线程,代码一行一行往下执行,js是单线程的

js执行时,只要遇到了异步的函数,不会停留,会将异步函数交给浏览器

浏览器是多线程,可以同时处理多个事件(等待,监听)

只有主线程空闲了,才会考虑任务队列的代码

满足条件,将需要执行的内容,在任务队列中排队

执行顺序

1.先执行所有同步任务,碰到异步任务放到任务队列中

2.同步任务执行完毕,开始执行当前所有的异步任务

3.先执行任务队列里面所有的微任务

4.然后执行一个宏任务

5.然后再执行所有的微任务

6.再执行一个宏任务,再执行所有的微任务·······依次类推到执行结束。

14.async/await是什么?

ES7 标准中新增的 async 函数,从目前的内部实现来说其实就是 Generator 函数的语法糖。

它基于 Promise,并与所有现存的基于Promise 的 API 兼容。

async 关键字

  1. async 关键字用于声明⼀个异步函数(如 async function asyncTask1() {...}

  2. async 会⾃动将常规函数转换成 Promise,返回值也是⼀个 Promise 对象

  3. async 函数内部可以使⽤ await

await 关键字

  1. await 用于等待异步的功能执⾏完毕 var result = await someAsyncCall()
  2. await 放置在 Promise 调⽤之前,会强制async函数中其他代码等待,直到 Promise 完成并返回结果
  3. await 只能与 Promise ⼀起使⽤
  4. await 只能在 async 函数内部使⽤

15.相较于 Promise,async/await有何优势?

  1. 同步化代码的阅读体验(Promise 虽然摆脱了回调地狱,但 then 链式调⽤的阅读负担还是存在的)
  2. 和同步代码更一致的错误处理方式( async/await 可以⽤成熟的 try/catch 做处理,比 Promise 的错误捕获更简洁直观)
  3. 调试时的阅读性, 也相对更友好

16. 深拷贝 浅拷贝

引用类型, 进行赋值时, 赋值的是地址

  1. 浅拷贝

    let obj = {
    	name: 'zs',
    	age: 18
    }
    let obj2 = {
        ...obj
    }
    
  2. 深拷贝

    let obj = {
    	name: 'zs',
    	age: 18,
        car: {
            brand: '宝马',
            price: 100
        }
    }
    
    let obj2 = JSON.parse(JSON.stringify(obj))
    console.log(obj2)
    

    当然递归也能解决, 只是比较麻烦~

其他方案, 可以参考一些博客

HTTP协议

1. HTTP有哪些⽅法?

HTTP 1.0 标准中,定义了3种请求⽅法:GET、POST、HEAD

HTTP 1.1 标准中,新增了请求⽅法:PUT、PATCH、DELETE、OPTIONS、TRACE、CONNECT

2. 各个HTTP方法的具体作用是什么?

方法功能
GET通常⽤于请求服务器发送某些资源
POST发送数据给服务器
HEAD请求资源的头部信息, 并且这些头部与 HTTP GET ⽅法请求时返回的⼀致。
该请求⽅法的⼀个使⽤场景是在下载⼀个⼤⽂件前先获取其⼤⼩再决定是否要下载, 以此可以节约带宽资源
PUT⽤于全量修改⽬标资源 (看接口, 也可以用于添加)
DELETE⽤于删除指定的资源
OPTIONS⽤于获取⽬的资源所⽀持的通信选项 (跨域请求前, 预检请求, 判断目标是否安全)
TRACE该方法会 让服务器 原样返回任意客户端请求的信息内容, 用于诊断和判断
CONNECTHTTP/1.1协议中预留给能够将连接改为管道⽅式的代理服务器
(把服务器作为跳板,让服务器代替用户去访问其它网页, 之后把数据原原本本的返回给用户)
PATCH⽤于对资源进⾏部分修改

3. GET方法和POST方法有何区别?

默认的http请求的内容, 在网络中传输, 明文的形式传递的 (https 对内容加密)

GET方法POST方法
数据传输⽅式通过URL传输数据 (地址栏拼接参数)通过请求体传输
数据安全数据暴露在URL中,可通过浏览历史记录、缓存等很容易查到数据信息数据因为在请求主体内,
所以有⼀定的安全性保证
数据类型只允许 ASCII 字符⽆限制
GET⽆害刷新、后退等浏览器操作是⽆害的可能会引起重复提交表单
功能特性安全且幂等(这⾥的安全是指只读特性,就是使⽤这个⽅法不会引起服务器状态变化。
幂等的概念是指同⼀个请求⽅法执⾏多次和仅执⾏⼀次的效果完全相同)
⾮安全(会引起服务器端的变化)、⾮幂等

4. HTTP请求报文是什么样的?

HTTP 请求报⽂的组成:请求⾏、请求头、(空⾏)、请求体。

请求行

包含了请求⽅法、URL、HTTP 协议版本,它们之间⽤空格进行分隔。例如:

GET http://www.abc.com/articles HTTP/1.1

请求头

请求头由键值对组成,每⾏⼀对,键值之间⽤英⽂冒号:进行分隔。例如:

Content-Type: application/json
Host: www.abc.com

请求体

请求体中放置 POST、PUT、PATCH 等请求方法所需要携带的数据。

5. HTTP响应报文是什么样的?

HTTP 响应报⽂的组成: 响应⾏、响应头、空⾏、响应体。

响应行

响应行由协议版本、状态码、状态码的原因短语3个内容组成,中间以空格分隔。例如:

HTTP/1.1 200 OK

响应头

响应头由键值对组成,每⾏⼀对,键值之间⽤英⽂冒号:进行分隔。例如:

Content-Length: 1024
Content-Type: application/json

响应体

服务器发送过来的数据。

6. 你了解的HTTP状态码有哪些?

成功(2XX)

状态码原因短语说明
200OK表示从客户端发来的请求在服务器端被正确处理
201Created请求已经被实现,⽽且有⼀个新的资源已经依据请求的需要⽽建⽴
通常是在POST请求,或是某些PUT请求之后创建了内容, 进行的返回的响应
202Accepted请求服务器已接受,但是尚未处理,不保证完成请求
适合异步任务或者说需要处理时间比较长的请求,避免HTTP连接一直占用
204No content表示请求成功,但响应报⽂不含实体的主体部分
206Partial Content进⾏的是范围请求, 表示服务器已经成功处理了部分 GET 请求
响应头中会包含获取的内容范围 (常用于分段下载)

重定向(3XX)

状态码原因短语说明
301Moved Permanently永久性重定向,表示资源已被分配了新的 URL
比如,我们访问 http😕/www.baidu.com 会跳转到 https😕/www.baidu.com
302Found临时性重定向,表示资源临时被分配了新的 URL, 支持搜索引擎优化
首页, 个人中心, 遇到了需要登录才能操作的内容, 重定向 到 登录页
303See Other对于POST请求,它表示请求已经被处理,客户端可以接着使用GET方法去请求Location里的URI。
304Not Modified自从上次请求后,请求的网页内容未修改过。
服务器返回此响应时,不会返回网页内容。(协商缓存)
307Temporary Redirect对于POST请求,表示请求还没有被处理,客户端应该向Location里的URI重新发起POST请求。
不对请求做额外处理, 正常发送请求, 请求location中的url地址

因为post请求, 是非幂等的, 从302中, 细化出了 303 和 307

简而言之:

  • 301 302 307 都是重定向
  • 304 协商缓存

客户端错误(4XX)

状态码原因短语说明
400Bad Request请求报⽂存在语法错误((传参格式不正确)
401UnAuthorized权限认证未通过(没有权限)
403Forbidden表示对请求资源的访问被服务器拒绝
404Not Found表示在服务器上没有找到请求的资源
408Request Timeout客户端请求超时
409Confict请求的资源可能引起冲突

服务端错误(5XX)

状态码原因短语说明
500Internal Sever Error表示服务器端在执⾏请求时发⽣了错误
501Not Implemented请求超出服务器能⼒范围,例如服务器不⽀持当前请求所需要的某个功能,
或者请求是服务器不⽀持的某个⽅法
503Service Unavailable表明服务器暂时处于超负载或正在停机维护,⽆法处理请求
505Http Version Not Supported服务器不⽀持,或者拒绝⽀持在请求中使⽤的 HTTP 版本

7. HTTP的keep-alive是什么作用?

作用:使客户端到服务器端的连接持续有效(长连接),当出现对服务器的后继请求时,

Keep-Alive功能避免了建立或者重新建立连接。

早期 HTTP/1.0 在每次请求的时候,都要创建⼀个新的连接,⽽创建连接的过程需要消耗资源和时间,

为了减少资源消耗、缩短响应时间,就需要复⽤已有连接。

在后来的 HTTP/1.0 以及 HTTP/1.1 中引⼊了复⽤连接的机制,也就是在请求头中加⼊Connection: keep-alive,

以此告诉对⽅这个请求响应完成后不要关闭连接,下⼀次还⽤这个请求的连接进行后续交流。

协议规定,如果想要保持连接,则需要在请求头中加上 Connection: keep-alive。

keep-alive 的优点 (复用连接)

  • 较少的 CPU 和内存的占⽤(因为要打开的连接数变少了, 复用了连接)
  • 减少了后续请求的延迟(⽆需再进⾏握⼿)

缺点: 因为在处理的暂停期间,本来可以释放的资源仍旧被占用。请求已经都结束了, 但是还一直连接着也不合适

解决:Keep-Alive: timeout=5, max=100

  • timeout:过期时间5秒(对应httpd.conf里的参数是:KeepAliveTimeout),

  • max是最多一百次请求,强制断掉连接。

    就是在timeout时间内又有新的连接过来,同时max会自动减1,直到为0,强制断掉。

8. 为什么需要HTTPS?

HTTPS 是安全版的 HTTP。

HTTP 协议在传输数据时采用的是明⽂方式传递,因此,⼀些敏感信息的传输就变得很不安全。

而 HTTPS 就是为了解决 HTTP 的不安全⽽产⽣的。

9. HTTPS是如何保证安全的?

HTTPS 在传输数据的过程中会对数据进行加密处理,保证安全性。

那HTTPS采用的什么样的加密方式呢?我们来了解下一些加密的基本概念。

目前常见的加密算法可以分成三类,对称加密算法(可逆的),非对称加密算法(可逆的)和Hash算法(不可逆的)。

9.1 什么是对称加密?

对称加密的特点是文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥,

这种方法在密码学中叫做对称加密算法,对称加密算法使用起来简单快捷,密钥较短,且破译困难

通信的双⽅都使⽤同⼀个秘钥进⾏加解密。⽐如,两个人事先约定的暗号,就属于对称加密。

对称加密的特点是:

  • 优点:

    计算量小、加密速度快、加密效率高。

  • 缺点:

    在数据传送前,发送方和接收方必须商定好秘钥,然后双方保存好秘钥。

    如果一方的秘钥被泄露,那么加密信息也就不安全了

使用场景:本地数据加密、https通信、网络传输等

常见算法:AES、DES、3DES、DESX、Blowfish、IDEA、RC4、RC5、RC6

9.2 什么是⾮对称加密?

而加密和解密其实可以使用不同的规则,只要这两种规则之间存在某种对应关系即可,

这样就避免了直接传递密钥。这种新的加密模式被称为"非对称加密算法"。

通信的双方使用不同的秘钥进行加密解密,即秘钥对(私钥 + 公钥)。

特征: 私钥可以解密公钥加密的内容, 公钥可以解密私钥加密的内容

非对称加密的特点是:

  • 优点:非对称加密与对称加密相比其安全性更好

  • 缺点:加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

使用场景:https会话前期、CA数字证书、信息加密、登录认证等

常见算法:RSA、ECC(移动设备用)、Diffie-Hellman、El Gamal、DSA(数字签名用)

9.3 HTTPS 加密解决⽅案

结合了两种加密⽅式:

  • 对称加密的密钥 ⽤非对称加密的公钥, 进⾏加密并发送出去,接收⽅使⽤私钥解密得到 对称加密密钥

  • 双⽅沟通时使⽤ 对称加密密钥 进⾏

可以看到,只有在发送秘钥阶段才使用非对称加密,而后续的通信都使用对称加密,这样解决了性能问题。

HTTPS 目前所使用的 TLS或SSL协议, 就是目前采用的加密通道的规范协议

它利用对称加密、(公私钥)非对称加密, 以及其密钥交换算法,可完成可信任的信息传输

9.4 数字证书

为了安全性, 一般还需要签发数字证书!

客户端 和 服务器端要初步互通消息时, 客户端发送请求可以拿到公开的公钥信息

进而进行非对称加密, 使用公钥, 加密对称加密密钥, 传递给服务器, 后续通信都使用对称加密!

问题是: 初步互通消息时, 如果请求拿到的公钥信息, 就是假的, 或者不安全的! 那么后续的所有操作, 都将是不安全的!

所以, 就需要有数字证书(CA证书), 一般是CA机构颁发的, 证明这个公钥是安全可靠的!

CA证书中心会对你网站的公钥, 网站的域名地址, 证书到期时间, 等一些相关信息一起加密签发数字证书, 保证你网站的安全性

当公司申请了 CA 证书后, 就应该在响应时, 将数字证书一起发送给客户端

而客户端, 接收到消息后, 就可以查看证书

  1. 如果正在访问的网站 和 证书记载的网址 不一致, 说明不安全, 可能被冒用, 浏览器就会发出警告!!!

  2. 如果签发证书的机构, 不权威, 发出警告

  3. 如果证书过期了, 浏览器也会发出警告

9.5 数字签名

但这还是有问题:如果证书被篡改了怎么办?

这时就需要用⼀个技术:数字签名。 (根据证书内容, 生成的一个唯一标识)

数字签名就是先⽤ CA ⾃带的 Hash 算法来计算出证书内容的⼀个摘要,然后使⽤ CA 私钥进行加密,组成数字签名。

当别⼈把他的证书发过来时,接收方⽤同样的算法再次⽣成摘要,⽤ CA 公钥解密后得到CA生成的摘要,两者进行对⽐后,

就能确定中间是否被⼈篡改。这样就能最⼤程度的保证通信的安全了。

10. HTTP2和HTTP1.x比,有什么优势和特点?

  1. HTTP/2 采⽤⼆进制格式来传输数据,⽽⾮ HTTP 1.x 的⽂本格式,⼆进制协议解析起来更⾼效
  2. HTTP/2 采用一些头部压缩技术,减少在请求和响应头中重复携带的数据,降低网络负担
  3. HTTP/2 采⽤服务器推送方式,主动向客户端推送资源,提高页面加载效率
  4. HTTP/2 采⽤多路复用机制,减少需要创建的连接数量,降低资源占用和性能消耗

下面是一些与之关联的技术知识。

⼆进制格式分帧

帧:HTTP/2 数据通信的最⼩单位消息,是指 HTTP/2 中逻辑上的 HTTP 消息(例如请求、响应等)。消息由⼀个或多个帧组成

流:存在于连接中的⼀个虚拟通道,它可以承载双向消息,且每个流都有唯⼀的整数ID

头部压缩

在 HTTP/1.x 中,请求和响应中会重复携带一些不常改变、冗⻓的头数据,给⽹络带来额外负担。

在 HTTP/2 中,客户端和服务端使⽤ “⾸部表” 来跟踪和存储之前发送过的键值对,

相同的数据不再随着每次请求和响应发送。⾸部表在连接存续期间始终存在,由客户端和服务器共同渐进更新。

每个新的⾸部键值对,要么被追加到当前表的末尾,要么替换表中已存在的键值对。

可以简单的理解为:只发送差异数据,⽽不是全部发送,从⽽减少头部的信息量

服务器推送

服务端可以在发送⻚⾯ HTML 内容时,再主动推送一些其它资源,⽽不⽤等到浏览器解析到相应的位置时发起请求后再作响应。

例如,服务端可以主动把 JS 和 CSS ⽂件推送给客户端,⽽不需要客户端解析 HTML 时再发送这些请求。

不过,服务端的主动推送行为,客户端有权利选择是否要接收。

如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送 RST_STREAM 帧来拒收。

多路复用

在 HTTP 1.x 中如果想并发多个请求的话,必须使⽤多个 TCP 链接,但浏览器为了控制资源,

会对单个域名有 6-8 个 TCP 链接的数量限制。而在 HTTP 2 中:

  • 同域名下的所有通信,都在单个连接上完成
  • 单个连接可以承载任意数量的双向数据流
  • 数据流以消息的形式发送,⽽消息⼜由⼀个或多个帧组成(多个帧可以乱序发送,因为可以根据帧⾸部的流标识来重新组装)

11. http缓存控制

11.1 基本认知

Web 服务缓存 大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 服务器缓存)、浏览器缓存。

浏览器缓存 也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。这里我们只讨论 HTTP 缓存相关内容

HTTP缓存:

  • 强缓存
  • 协商缓存

在具体了解 HTTP 缓存之前先来明确几个术语:

  • 缓存命中率:从缓存中得到数据的请求数 与 所有请求数的比率。理想状态是越高越好。
  • 过期内容:超过设置的有效时间,被标记为“陈旧”的内容。
  • 验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。
  • 失效:失效就是把内容从缓存中移除。

浏览器缓存主要是 HTTP 协议定义的缓存机制。

浏览器缓存, HTTP缓存分类

浏览器缓存分为强缓存 协商缓存,浏览器加载一个页面的简单流程如下:

  1. 浏览器先根据这个资源的 http头信息判断是否命中强缓存

    如果命中则直接加载在缓存中的资源,并不会将请求发送到服务器。(强缓存)

  2. 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。

    服务器来判断浏览器本地缓存是否失效。

    若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。(协商缓存)

  3. 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。(新的请求)

11.2 强缓存 (食品过期时间判断)

(进行判断, 是否资源过期, 如果未过期, 直接用缓存)

强缓存

命中强缓存时,浏览器并不会将请求发送给服务器。

在Chrome的开发者工具中看到http的返回码是200,但是在Size列会显示为(from cache)。

强缓存是利用http的返回的响应头中的Expires或者Cache-Control (优先级更高) 两个字段来控制的,用来表示资源的缓存时间。

Expires: 指定一个具体时间(2020年12月12日 17:00), 到了这个时间了, 缓存过期了, 在时间内, 都是有效的, 可以直接读

Cache-Control : 指定一个过期时间 (3600s), 这个资源你加载到后, 可以用 3600s

Expires

缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。但在上面我们提到过,cache-control的优先级更高。

Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。

该字段会返回一个时间,比如Expires: Wed, 23 Nov 2050 16:00:01 GMT 。这个时间代表着这个资源的失效时间,也就是说在xx年xx月xx日时间之前都是有效的,即命中缓存。

这种方式有一个明显的缺点,由于失效时间是一个绝对时间,所以当 服务器与客户端 时间偏差很大 以后,就会导致缓存混乱。于是发展出了Cache-Control。

Cache-Control

Cache-Control是一个相对时间,例如Cache-Control:max-age 3600,代表着资源的有效期是3600秒。

由于是相对时间,并且都是与客户端时间比较,所以服务器与客户端时间偏差也不会导致问题。

Cache-Control与Expires可以在服务端配置同时启用或者启用任意一个,同时启用的时候Cache-Control优先级高。

Cache-Control 可以由多个字段组合而成,主要有以下几个取值:

  1. max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。

    例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 / 60)天,

    第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。

    在没有禁用缓存并且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,不会向服务器请求资源而是直接从浏览器缓存中取。

  2. no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证的请求到服务器, 问服务器是否可以读缓存。

    不是字面意思上的不缓存。

  3. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

注意:如果命中强缓存,在有效期内,使用了本地浏览器的缓存,不会向服务器发送请求的

11.3 协商缓存 (找供货商专家协商)

看看过期时间, 食品没过期, 直接吃 (直接读缓存, 不发请求) 强缓存

食品过期时间过了, 能不能吃呢? 问问专家(服务器), 专家瞅了一眼, 没过期 (响应304, 不返回内容) , 直接吃 (协商缓存)

如果问过专家(服务器), 专家瞅了一眼, 呀真过期了, 原来的不要了, 我重新给你发一个 (响应200, 并返回内容)

协商缓存

若未命中强缓存(强缓存过期了),则浏览器会将请求发送至服务器。

服务器根据http头信息中的Last-Modify/If-Modify-SinceEtag/If-None-Match来判断是否命中协商缓存。

如果命中,则http返回码为304 (你本地之前加载的资源是有效的),浏览器从缓存中加载资源。

Last-Modify/If-Modify-Since

浏览器第一次请求一个资源的时候, 服务器返回的header中会加上Last-Modify,

Last-modify是一个时间标识该资源的最后修改时间,例如Last-Modify: Thu,31 Dec 2037 23:59:59 GMT

当浏览器再次请求该资源时,发送的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify

服务器收到If-Modify-Since后,根据实际服务器的资源的最后修改时间, 进行判断是否命中缓存。

如果命中缓存,则返回 http304,并且不会返回资源内容,并且不会返回Last-Modify。

由于对比的是服务端时间,所以客户端与服务端时间差距不会导致问题。

但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致)。

比如: 最后修改只能精确到秒级, 一秒进行了多次修改, 就不行了, 于是出现了ETag/If-None-Match。

ETag/If-None-Match

与Last-Modify/If-Modify-Since (最后修改时间)不同的是,Etag/If-None-Match返回的是一个校验码(ETag: entity tag)。

ETag可以保证每一个资源是唯一的,资源变化都会导致ETag变化。

ETag值的变更则说明资源状态已经被修改。

服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。

ETag生成靠以下几种因子

  1. 文件的i-node编号,是Linux/Unix用来识别文件的编号。

  2. 文件最后修改时间

  3. 文件大小

生成Etag的时候,可以使用其中一种或几种因子,使用抗碰撞散列函数来生成。生成一个标记文件的唯一值

既生 Last-Modified 何生 Etag ?

你可能会觉得使用Last-Modified已经足以让浏览器知道本地的缓存副本是否足够新,为什么还需要Etag(实体标识)呢?

Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  1. Last-Modified标注的最后修改只能精确到秒级

    如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  2. 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加 准确的控制缓存。

不会仅仅只根据最后的修改时间判断是否进行使用缓存

Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,

最后才决定是否返回304。

小结:

  • 强缓存: 大大的减少了 服务器的请求次数, 在过期时间内, 直接从客户端内存中读

  • 协商缓存: 强缓存命中失效了, 超过过期时间了, 拿着标识(最后的修改时间, 唯一标识etag), 去问服务器, 是否真的过期了

    如果验证通过, 服务器会直接响应 304, 且不会返回资源

TCP协议

1. TCP协议是什么?

TCP(Transmission Control Protocol 传输控制协议) 是一种面向连接(连接导向) 的、可靠的、 基于IP的传输层协议。

TCP 使⽤校验、确认和重传机制来保证可靠传输

而 HTTP协议 就是建立在 TCP / IP 协议 之上的一种应用。

TCP: 三次握手, 四次挥手~

2. 一次完整的HTTP服务过程是什么

当我们在web浏览器的地址栏中输入:www.baidu.com,具体发生了什么?

  1. www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
  2. 根据这个IP,找到对应的服务器,发起TCP的三次握手
  3. 建立TCP连接后, 发起HTTP请求
  4. 服务器响应HTTP请求,浏览器得到html代码
  5. 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)(先得到html代码,才能去找这些资源)
  6. 浏览器对页面进行渲染呈现给用户
  7. 服务过程完毕, 关闭TCP连接, 四次挥手

注:

1.DNS怎么找到域名的?

DNS域名解析采用的是递归查询的方式,过程是,先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器

2.为什么HTTP协议要基于TCP来实现?

TCP是一个端到端的可靠面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,可以重传)

3.最后一步浏览器是如何对页面进行渲染的?

a)解析html文件构成 DOM树
b)解析CSS文件构成渲染树
c)边解析,边渲染
d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载

3. 什么是DNS 解析

DNS解析(域名解析服务器) 将 域名 转换成 ip地址 (一个域名和ip的映射关系, 具体登记在哪里, 看我们如何申请关联的!)

假定请求的是 www.baidu.com

a)首先会搜索浏览器自身的DNS缓存(缓存时间比较短,大概只有1分钟,且只能容纳1000条缓存)

b)如果浏览器自身的缓存里面没有找到,那么浏览器会搜索系统自身的DNS缓存

c)如果还没有找到,那么尝试从 hosts 文件里面去找 (一个系统电脑的文件, 可以编辑, 可以存 域名 和 ip 的对应关系)

d)在前面三个过程都没获取到的情况下,就递归地去域名服务器去查找(就近查找),具体过程如下

DNS优化两个方面:DNS缓存、DNS负载均衡 (准备多台dns服务器, 进行dns解析)

4. TCP 三次握手理解 (双方确认)

TCP是一个端到端的 可靠 面相连接的协议,

HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,可以重传)

根据这个IP,找到对应的服务器,发起TCP的三次握手 (tcp 三次握手四次挥手 )

为什么要3次握手

我们假定第一次发送的请求, 因为网络延迟很慢才到达服务端,

然后客户端以为这服务器居然不理睬我,然后默默的关闭的等待连接的请求,走开了(好比追女神);

但事实呢?女神(服务器)是因为各种各样的原因,很晚才看到,然后说我接受你了, 同意你的要求咱们两结婚吧!

但是,A早已经远走高飞,这个请求A完全不会收到(在第二次握手,服务端打开连接,等待客户端的响应),

那么女生呢,以为对方收到了,就会一直等待,这样B的资源就会被浪费的(创建连接的时候,空间浪费以及端口消耗);

而三次握手, 就不会发生,服务端同意连接了,但是A缺一直没有下一步的动作,导致资源浪费;

5. 关闭TCP连接四次挥手的理解 (客气挽留)

目标: 关闭连接(四次挥手)

不能直接一次性断开连接(双方知晓), 万一还有什么数据没有传完, 造成数据的丢失!

1.一方发起断开连接信息

2.另一方会确认收到断开的需求,但是要求等待一会儿,确认数据是否传输完毕

3.当确认完毕之后,确实数据都传输完了,告知断开方,连接可以断开了

4.断开方确认收到消息

DMO

1. DOM的事件流是什么?

事件流

⼜称为事件传播,是⻚⾯中接收事件的顺序。DOM2级事件规定的事件流包括了3个阶段:

  • 事件捕获阶段(capture phase)
  • 处于⽬标阶段(target phase)
  • 事件冒泡阶段(bubbling phase)

事件流的触发顺序是:

  1. 事件捕获阶段,为截获事件提供了机会
  2. 实际的⽬标元素接收到事件
  3. 事件冒泡阶段,可在这个阶段对事件做出响应

事件冒泡(Event Bubbling)

事件开始由最具体的元素(⽂档中嵌套层次最深的那个节点)接收到后,开始逐级向上传播到较为不具体的节点。

<html>
  
  <head> 
    <title>Document</title> 
  </head>
  
  <body> 
    <button>按钮</button> 
  </body> 
  
</html>

如果点击了上面页面代码中的 <button> 按钮,那么该 click 点击事件会沿着 DOM 树向上逐级传播,在途经的每个节点上都会发生,具体顺序如下:

  1. button 元素
  2. body 元素
  3. html 元素
  4. document 对象

事件捕获(Event Capturing)

事件开始由较为不具体的节点接收后,然后开始逐级向下传播到最具体的元素上。

事件捕获的最大作用在于:事件在到达预定⽬标之前就可以捕获到它。

如果仍以上面那段 HTML 代码为例,当点击按钮后,在事件捕获的过程中,document 对象会首先接收到这个 click 事件,然后再沿着 DOM 树依次向下,直到 <button>。具体顺序如下:

  1. document 对象
  2. html 元素
  3. body 元素
  4. button 元素

2. 说说什么是事件委托?

事件委托,就是利用了事件冒泡的机制,在较上层位置的元素上添加一个事件监听函数,

来管理该元素及其所有子孙元素上的某一类的所有事件。

示例

<ul id="list">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
    <li>555</li>
</ul>

<script type="text/javascript">
    // ⽗元素 
    var list = document.getElementById('list');

    // 为⽗元素绑定事件,委托管理它的所有⼦元素li的点击事件 
    list.onclick = function (event) {
        var currentTarget = event.target;
        if (currentTarget.tagName.toLowerCase() === 'li') {
            alert(currentTarget.innerText)
        }
    }
</script>

适用场景:在绑定大量事件的时候,可以选择事件委托

优点

  • 事件委托可以减少事件注册数量,节省内存占⽤!
  • 当新增⼦元素时,⽆需再次做事件绑定,因此非常适合动态添加元素 (vue解析模板时, 会对新创建的元素, 额外进行绑定的)

Vue

1. 什么是 M V VM

Model-View-ViewModel 模式

Model 层: 数据模型层

通过 Ajaxfetch 等 API 完成客户端和服务端业务模型的同步。

View 层: 视图层

作为视图模板存在,其实View 就是⼀个动态模板。

ViewModel 层: 视图模型层

负责暴露数据给 View 层,并对 View 层中的数据绑定声明、 指令声明、 事件绑定声明, 进行实际的业务逻辑实现。

数据变化了, 视图自动更新 => ViewModel 底层会做好监听Object.defineProperty,当数据变化时,View 层会自动更新

视图变化了, 绑定的数据自动更新 => 会监听双向绑定的表单元素的变化,⼀旦变化,绑定的数据也会得到⾃动更新。

2. MVVM的优缺点有哪些?

优点

  1. 实现了视图(View)和模型(Model)的分离,降低代码耦合、提⾼视图或逻辑的复⽤性

  2. 提⾼了可测试性:ViewModel 的存在可以帮助开发者更好地编写测试代码

  3. 能⾃动更新 DOM:利⽤双向绑定,数据更新后视图⾃动更新,让开发者从繁琐的⼿动操作 DOM 中解放出来

缺点

  1. Bug 难被调试:因为使⽤了双向绑定的模式,当我们看到界⾯发生异常了,有可能是 View 的代码产生的 Bug,

    也有可能是Model 代码的问题。数据绑定使得⼀个位置的 Bug 被快速传递到别的位置,

    要定位原始出问题的地⽅就变得不那么容易了

    可采用的调试方案:

    (1) 注释掉一段代码, 确定代码的位置

    (2) debugger 打断点 或者 console 进行调试

  2. 在⼀个⼤的模块中 Model 也会很⼤,虽然使⽤上来说⽅便了,但如果⻓期持有不释放内存,就会造成更多的内存消耗

    占用的是 浏览器的 内存

3. 谈谈对Vue生命周期的理解?

生命周期的概念

每个 Vue 实例(每个组件也都是一个vue实例)都有⼀个完整的⽣命周期:

  1. 开始创建 (空实例)
  2. 初始化数据
  3. 编译模版
  4. 挂载 DOM
  5. 渲染、更新数据 => 重新渲染
  6. 卸载

这⼀系列过程我们称之为 Vue 的⽣命周期。

各个生命周期的作用

生命周期执行时机
beforeCreate在组件实例被创建之初、组件的属性⽣效之前被调用
created在组件实例已创建完毕。此时属性也已绑定,但真实DOM还未⽣成,$el 还不可⽤
beforeMount在组件挂载开始之前被调⽤。相关的 render 函数⾸次被调⽤
mounted在 el 被新建的 vm.$el 替换并挂载到实例上之后被调用
beforeUpdate在组件数据修改了, 视图更新之前调⽤。发⽣在虚拟 DOM 打补丁之前
updated在组件数据修改了, 视图更新之后被调用
activited在组件被激活时调⽤(使用了 <keep-alive> 的情况下)
deactivated在组件被停用时调⽤(使用了 <keep-alive> 的情况下)
beforeDestory在组件销毁前调⽤ (销毁: vue默认会进行释放掉实例所有的监听, 释放掉所有的组件…)
destoryed在组件销毁后调⽤ (像定时器, webscoket连接, … 跟vue没有太大关联的资源, 需要手动释放!)

5. Vue组件之间如何进行通信?

父传子, 子传父, 非父子, Vuex

5.1 props 和 $emit

(1) 通过 props 将数据在组件树中进行⾃上⽽下的传递;

<jack :money="count" :obj="myobj"></jack>
export default {
  // props: ['money']
  props: {
      money: {
          type: Number,
          default: 1
      },
      obj: {
          type: Object,
          default: () => {
              return {
                  name: 'zs',
                  age: 18
              }
          }
      }
  }
}

(2) 通过 $emit@ 来作信息的向上传递。

this.$emit('add-action', 参数1, 参数2, ...)
<jack @add-action="fatherFn"></jack>

5.2 eventBus事件总线

可通过 EventBus 进⾏信息的发布与订阅。 (创建一个都能访问到的事件总线)

Vue.prototype.$eventBus = new Vue()   // this.$eventBus
// A组件中, 监听 bus的事件
this.$eventBus.$on('事件名', function(参数1, 参数2, ...) {
	...
})

// B组件中, 触发 bus的事件
this.$eventBus.$emit('事件名', 参数1, 参数2, ...)

5.3 $children $parent $refs

(1) $children

父组件中, $children 返回的是一个组件集合,如果你能清楚的知道子组件的顺序,你也可以使用下标来操作

// 父组件中
<template>
  <div class="hello_world">
    <com-a></com-a>
    <com-b></com-b>
  </div>
</template>

this.$children[0] => <com-a></com-a>

this.$children[1] => <com-b></com-b>

(2) $parent

子组件中, this.$parent 指向父组件

this.$parent.xxx = 200

this.$parent.fn()

(3) $refs

通过添加 ref 和 $refs 配合, 也可以很方便的获取子组件, 访问调用子组件的属性或方法

// 父组件中
<template>
  <div class="hello_world">
    <com-a ref="coma"></com-a>  // this.$refs.coma.count = 200
    <com-b ref="comb"></com-b>  // this.$refs.comb.addFn()
  </div>
</template>

this.$refs.coma => <com-a></com-a>

this.$refs.comb => <com-b></com-b>

5.4 provide inject

**成对出现:**provide和inject是成对出现的

作用:用于父组件向子孙组件传递数据

使用方法:

  • provide在父组件中, 返回要传给下级的数据
  • inject在需要使用这个数据的子孙组件中注入数据。(不论组件层次有多深)

父组件

export default {
	provide () {
        return {
            value: this.value // 共享给子孙组件的数据
        }
    },
    data () {
        return {
            value: '父组件的数据',
            money: 100
        }
    }
}

子孙组件

export default {
    inject: ['value'],
    props: {
        ...
    }
}

5.5 $attrs $listeners

在 Vue 2.4 版本中加⼊的 $attrs$listeners 可以用来作为跨级组件之间的通信机制。 (父传孙)

父组件

<template>
  <div>
    <my-child1 :money="100" desc='你好哇' @test1="fn1" @test2="fn2"></my-child1>
  </div>
</template>

子组件

<template>
  <div class="my-child1">
    <!-- $attrs => { "money": 100, "desc": "你好哇" } -->
    <div>{{ $attrs }}</div>
    <my-child2 v-bind="$attrs" v-on="$listeners" ></my-child2>
  </div>
</template>

<script>
import MyChild2 from './my-child2'
export default {
  created () {
    console.log(this.$listeners)
  },
  components: {
    MyChild2
  }
}
</script>

孙组件

<template>
  <div>
    我是child2 - {{ money }} - {{ desc }}
    <button @click="clickFn">按钮</button>
  </div>
</template>

<script>
export default {
  props: ['money', 'desc'],
  methods: {
    clickFn () {
      this.$emit('test1', '嘎嘎')
      this.$emit('test2', '嘿嘿')
    }
  }
}
</script>

5.6 Vuex

全局状态管理库。可通过它来进行全局数据流的管理。

state: 存放数据

mutations: 存放操作数据的方法

actions: 存放一些异步操作 (也可以进行一些同步处理) 注意: actions是不能直接修改state数据的, 需要提交mutation

getters: 存放基于state计算出来的一些值 (计算属性)

modules: 分模块, 项目大了, 也推荐分模块管理 (同模块的vuex操作, 就会在一起)

注意点: 分模块了, 默认muations, actions, getters 注册到全局的, 一般会开启命名空间

语法: namespaced: true

6. computed 和 watch的区别是什么?

computed

  1. 它是计算属性。主要用于值的计算并一般会返回一个值。所以它更多⽤于计算值的场景
  2. 它具有缓存性。当访问它来获取值时,它的 getter 函数所计算出来的值会进行缓存
  3. 只有当它依赖的属性值发生了改变,那下⼀次再访问时才会重新调⽤ getter 函数来计算
  4. 它适⽤于计算⽐较消耗性能的计算场景
  5. 必须要有一个返回值

watch

  1. 它更多的是起到 “观察” 的作⽤,类似于对数据进行变化的监听并执行回调。

    主要⽤于观察 props 或 本组件data的值,当这些值发生变化时,执⾏处理操作

  2. 不一定要返回某个值

建议

  1. 当目的是进⾏数值计算,且依赖于其他数据,那么推荐使用 computed

  2. 当需要在某个数据发生变化的, 同时做⼀些稍复杂的逻辑操作,那么推荐使⽤ watch

7. Vue双向绑定原理?

7.1 基本认知

在 Vue 2.x 中,利⽤的是 Object.defineProperty 去劫持对象的访问器(Getter、Setter),

当对象属性值发⽣变化时可获取变化,然后根据变化来作后续响应;(一个一个的劫持)

在 Vue 3.0 中,则是通过 Proxy 代理对象进⾏类似的操作。劫持的是整个对象, 只要对象中的属性变化了, 都能劫持到

7.2 Object.defineProperty和Proxy的优缺点?

Proxy

  • 可以直接监听整个对象,⽽⾮是对象的某个属性

  • 可以直接监听数组的变化

  • 拦截⽅法丰富:多达13种,不限于get set deletePropertyhas 等。

    Object.defineProperty 强大很多

Object.defineProperty

  • 兼容性较好(可⽀持到 IE9)

8. 如何理解Vue的响应式系统?

(考察MVVM) M: model数据模型, V:view视图模型, VM: viewModel视图数据模型

双向:

  1. 视图变化了, 数据自动更新 => 监听原生的事件即可, 输入框变了, 监听输入框input事件
  2. 数据变化了, 视图要自动更新 => vue2 和 vue3

8.1 基本原理

vue2.0 数据劫持: Object.defineProperty (es5)

vue3.0 数据劫持: Proxy (es6)

分析 :此题考查 Vue的 MVVM 原理

解答: Vue的双向绑定原理其实就是 MVVM 的基本原理, Vuejs官网已经说明, 实际就是通过 Object.defineProperty方法 完成了对于Vue实例中数据的 劫持, 通过对于 data中数据 进行set的劫持监听, 然后通过**观察者模式**, 通知 对应的绑定节点 进行节点数据更新, 完成数据驱动视图的更新

简单概述 : 通过Object.defineProperty 完成对于数据的劫持, 通过观察者模式, 完成对于节点的数据更新

8.2 观察者模式

观察者模式: 当对象间存在 一对多 关系时,则使用观察者模式(Observer Pattern)。

比如,当一个对象或者数据被修改时,则会自动通知依赖它的对象。

**意图:**定义对象间的一种 一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

拍卖会的时候,大家相互叫价,拍卖师(Dep) 会观察 最高标价(利用Object.defineProperty监听),

一旦最高价变化了, 然后通知给其它竞价者(watcher观察者 - 订阅者, 订阅了价格的变化),这就是一个观察者模式

Dep要进行 依赖收集,并通过一个subs数组, 记录观察者Watcher,

Watcher 分为 渲染 watcher、计算属性 watcher、侦听器 watcher 三种

收集依赖: 简单点说就是谁借了我的钱,我就把那个人 记下来 ,以后我的钱少了 我就通知他们说我没钱了

<div>
  <p>{{ msg }}</p>   // Watcher1(渲染), 依赖于msg
</div>

<div>
  <h1>{{ car }}</h1>   // Watcher2(渲染),  依赖于car
</div>

<div>
  <h1>{{ myMsg }}</h1>   // Watcher3(渲染), 依赖于myMsg
</div>

computed: {
  myMsg () {
    console.log('计算属性重新计算了')
    return this.msg + '20'  // Watcher4(计算属性中), 依赖于msg, msg变了重新计算
  }
}

watch: {
  msg (newValue) {
    console.log('新的msg', newValue) // Watcher5(侦听器), 将来msg变化, 这边要执行这个函数
  }
}

------------------------------------------------------------------

// 收集依赖 (dep结构有点类似于二维数组, (Map结构))   arr.type="msgDep"
dep: [
  msgDep: [Watcher5(侦听器), Watcher4(计算属性中), Watcher1(渲染)],
  carDep: [Watcher2(渲染)],
  myMsgDep: [Watcher3(渲染)]
]


// Watcher
{
  callback: Function, (数据变化后, 需要执行的回调)
  isRenderWatcher: Boolean, (是否是render的watcher, 是否要触发视图的更新, 往后放, 最后统一虚拟dom对比, 统一更新)
  ...
}

比如: 假定数据 money 变了, 那么没有任何与money相关的观察者, 就不需要进行任何更新操作, 也不需要执行任何的监视函数

然而: 假定数据 msg 变了, 就会通知到相关的Watcher, 且优先通知侦听器Watcher和计算属性Watcher, 后进行统一的渲染更新

  1. 通知侦听器Watcher, 立刻执行配置的函数, console.log(‘新的msg’, newValue)
  2. 通知计算属性Watcher, 计算属性依赖的值变了, 需要重新计算
    且更新后, myMsg 变化了, 需要进行进行视图的渲染 (render) (— 要更新, 等着—)
  3. 通过到watcher1, 渲染Watcher (—要更新—)
  4. 最后统一进行, 新旧虚拟dom的对比, 完成视图的更新

当数据状态发生改变时,会被 Object.defineProperty 监听劫持到, 会通知到 Dep, 并根据收集的依赖关系, 让订阅者Watcher进行数据更新(update)操作 , 派发更新

总结概述: vue采用的是观察者模式, 是一种一对多的关系, 一上来vue在解析渲染时, 会进行依赖收集, 会将渲染 watcher、计算属性 watcher、侦听器 watcher, 都收集到对应的dep中, 将来Object.defineProperty 监听到数据变化, 就根据依赖关系, 派发更新

9. Vue中的key到底有什么用?

key 是为 Vue 中的虚拟 DOM 节点(vNode)标记唯⼀性的 id。

9.1 key的作用

作用: 给虚拟dom添加标识, (优化复用对比策略, 优化渲染性能)

主要考察:

  1. vue 的更新机制 (差异化更新) 对比新旧虚拟dom, 找出不同的部分, 进行更新视图

    为什么对比虚拟dom, 而不对比真实的dom ? 真实的dom太复杂, 对比起来性能太差

  2. 虚拟dom: 使用 js 对象的方式, 模拟真实的 dom 结构 { type: ‘div’, className: ‘box’ , children: [] }

    属性的量大大的减少了, 没有真实dom的那么多无效的属性, 对比起来性能高很多

  3. diff 算法: 默认的对比(diff) 机制, 同层兄弟元素, 是按照下标进行对比的, 但是加了 key, 就相当于给虚拟dom加了个标识

    对比策略, 就是对相同key的元素进行对比了, 在列表v-for中, key的使用尤为常见, 可以用于优化渲染性能

9.2 key的常见应用场景

key 的常见应用场景 => v-for, v-for 遍历的列表中的项的顺序, 非常的容易改变

1 往后面加, 默认的对比策略, 按照下标, 没有任何问题

// 旧
<ul>
  <li>张三</li>
  <li>李四</li>
</ul>

// 新
<ul>
  <li>张三</li>
  <li>李四</li>
  <li>王五</li>
</ul>

2 往前面加, 由于下标变了, 如果按照之前的下标对比, 元素是混乱的, 策略: 加上key

​ 一旦加上了key, 就是按照 key 进行新旧dom的对比了

// 旧 
<ul>
  <li key="17">张三</li>
  <li key="31">李四</li>
</ul>

// 新  [ { id: 17, name: '张三' }, ... ]
<ul>
  <li key="52">王五</li>
  <li key="17">张三</li>
  <li key="31">李四</li>
</ul>

总结: key 就是给 虚拟dom 添加了一个 标识, 优化了对比策略!!!

10. Vue 跳转路由时的传参方式 (query和params的区别)

  1. 通过 query 传参
this.$router.push('/login?username=pp&age=18&desc=xx')

this.$router.push({
    path: '/login',
    query: {
        username: 'pp',
        age: 18,
        desc: 'xxx'
    }
})

this.$router.push({
    name: 'login',
    query: {
        username: 'pp',
        age: 18,
        desc: 'xxx'
    }
})

获取: this.$route.query.username

  1. 通过 params 传参, 必须通过命名路由的方式传递!
this.$router.push({
	name: 'login',
	params: {
	    username: 'pp',
        age: 18
	}
})

获取: this.$route.params.username

区别:

  1. params传参, 必须要用命名路由的方式传值

  2. params传参, 不会显示在地址栏中, 刷新会丢失

    可以配合 localStorage 使用

    (1) A 跳转路由到 B, 通过 params 传值

    (2) B 页面中, 立刻通过 this.$route.params 获取参数

    ​ (获取参数的逻辑, 优先从$route中拿, 如果拿不到(说明刷新了), 从本地取即可)

    (3) 拿到参数后, 立刻存到本地 (保证刷新丢失后, 还能从本地拿)

    (4) 实现功能…

B页面的逻辑

created () {
    let username = this.$route.params.username
    if (username) {
        // 刚跳过来, 有参数, 立刻存起来
        localStorage.setItem('myParams', JSON.stringify(this.$route.params))
    } else {
        // 没有, 说明用户刷新了, 丢失了params, username参数, 本地拿
        username = JSON.parse(localStorage.getItem('myParams')).username
    }
}

11. Vue 项目进行 SEO 优化

Vue SPA单页面应用对SEO不太友好,当然也有相应的解决方案,下面列出几种SEO方案

  1. SSR服务器渲染

    服务端渲染, 在服务端html页面节点, 已经解析创建完了, 浏览器直接拿到的是解析完成的页面解构

    关于服务器渲染:Vue官网介绍 ,对Vue版本有要求,对服务器也有一定要求,需要支持nodejs环境。

    优势: 更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面

    缺点: 服务器nodejs环境的要求, 且对原代码的改造成本高! nuxt.js (坑比较多, 做好踩坑的准备)

  2. 静态化 (博客, 介绍性官网)

    Nuxt.js 可以进行 generate 静态化打包, 缺点: 动态路由会被忽略。 /users/:id

    优势:

    • 编译打包时, 就会帮你处理, 纯静态文件,访问速度超快;
    • 对比SSR,不涉及到服务器负载方面问题;
    • 静态网页不宜遭到黑客攻击,安全性更高。

    不足:

    • 如果动态路由参数多的话不适用。
  3. 预渲染 prerender-spa-plugin (插件)

    如果你只是对少数页面需要做SEO处理(例如 / 首页, /about 关于等页面)

    预渲染是一个非常好的方式, 预渲染会在构建时, 简单的针对特定路由, 生成静态 HTML 文件 (打包时可以帮你解析静态化)

    优势: 设置预渲染简单, 对代码的改动小

    缺点: 只适合于做少数页面进行SEO的情况, 如果页面几百上千, 就不推荐了 (会打包很慢)

  4. 使用Phantomjs 针对爬虫 做处理

    Phantomjs是一个基于webkit内核的无头浏览器,没有UI界面,就是一个浏览器,

    其内的点击、翻页等人为相关操作需要程序设计实现。

    这种解决方案其实是一种旁路机制,原理就是通过Nginx配置, 判断访问的来源UA是否是爬虫访问,

    如果是则将搜索引擎的爬虫请求转发到一个node server,再通过PhantomJS来解析完整的HTML,返回给爬虫

    优势:

    • 完全不用改动项目代码,按原本的SPA开发即可,对比开发SSR成本小太多了;
    • 对已用SPA开发完成的项目,这是不二之选。

    不足:

    • 部署需要node服务器支持;

    • 爬虫访问比网页访问要慢一些,因为定时要定时资源加载完成才返回给爬虫;(不影响用户的访问)

    • 如果被恶意模拟百度爬虫大量循环爬取,会造成服务器负载方面问题,

      解决方法是判断访问的IP,是否是百度官方爬虫的IP。

小结:

  • 如果构建大型网站,如商城类 => SSR服务器渲染

  • 如果只是正常公司官网, 博客网站等 => 预渲染/静态化/Phantomjs 都比较方便

  • 如果是已用SPA开发完成的项目进行SEO优化,而且部署环境支持node服务器,使用 Phantomjs

12. Vue 项目权限处理

现在权限相关管理系统用的框架都是element提供的vue-element-admin模板框架比较常见。

权限控制常见分为三大块

  • 菜单权限控制
  • 按钮权限控制
  • 请求url权限控制。

权限管理在后端中主要体现在对接口访问权限的控制,在前端中主要体现在对菜单访问权限的控制。

  1. 按钮权限控制比较容易,主要采取的方式是从后端返回按钮的权限标识,然后在前端进行显隐操作 v-if / disabled。

  2. url权限控制,主要是后端代码来控制,前端只需要规范好格式即可。

  3. 剩下的菜单权限控制,是相对复杂一些的

    (1) 需要在路由设计时, 就拆分成静态路由和动态路由

    ​ 静态路由: 所有用户都能访问到的路由, 不会动态变化的 (登录页, 首页, 404, …)

    ​ 动态路由: 动态控制的路由, 只有用户有这个权限, 才将这个路由添加给你 (审批页, 社保页, 权限管理页…)

    (2) 用户登录进入首页时, 需要立刻发送请求, 获取个人信息 (包含权限的标识)

    (3) 利用权限信息的标识, 筛选出合适的动态路由, 通过路由的 addRoutes 方法, 动态添加路由即可!

    (4) router.options.routes (拿的是默认配置的项, 拿不到动态新增的) 不是响应式的!

    ​ 为了能正确的显示菜单, 为了能够将来正确的获取到用户路由, 我们需要用vuex管理routes路由数组

    (5) 利用vuex中的 routes, 动态渲染菜单

13. Vue 项目支付功能

支付宝方式:点击支付宝支付, 调用后台接口(携带订单号),后台返回一个form表单(HTML字符串结构),

提交form就可以调用支付宝支付

代码:

//  alipayWap: 后台接口返回的form 片段
<div v-html="alipayWap" ref="alipayWap"></div>

methods: {
	toAlipay () {
		this.$axios.get('xxx').then (res = > {
			this.alipayWap = res;
             // 等待dom更新, 等页面中有这个form表单了
			this.$nextTick(() => {
            	this.$refs.alipayWap.children[0].submit()
          	})
		})
	}
}

微信支付:需要自己根据后台返回的url生成二维码页面,如图所示

14. 如何处理 打包出来的项目(首屏)加载过慢的问题

SPA应用: 单页应用程序, 所有的功能, 都在一个页面中, 如果第一次将所有的路由资源, 组件都加载了, 就会很慢!

加载过慢 => 一次性加载了过多的资源, 一次性加载了过大的资源

  • 加载过多 => 路由懒加载, 访问到路由, 再加载该路由相关的组件内容
  • 加载过大 => 图片压缩, 文件压缩合并处理, 开启gzip压缩等

比如:

  1. 配置异步组件, 路由懒加载

    const login = () => import('../pages/login.vue')
    
  2. 图片压缩: 使用 webp 格式的图片, 提升首页加载的速度

  3. CDN加速: 配置CDN加速, 加快资源的加载效率 (花钱)

  4. 开启 gzip 压缩 (一般默认服务器开启的, 如果没开, 确实可能会很慢, 可以让后台开一下)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值