TreeMind树图在线AI思维导图
当前位置:树图思维导图模板行业/职业模板其他超高频面试题思维导图

超高频面试题思维导图

  收藏
  分享
免费下载
免费使用文件
U843674413 浏览量:1262023-08-28 14:07:08
已被使用16次
查看详情超高频面试题思维导图

树图思维导图提供 超高频面试题 在线思维导图免费制作,点击“编辑”按钮,可对 超高频面试题  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:b736d351b33f190fc42c80e35d48737a

思维导图大纲

超高频面试题思维导图模板大纲

html模块

script标签中defer和async的区别

如果没有defer或async属性,浏览器会立即加载并执行相应的脚本。它不会等待后续加载的文档元素,读取到就会开始加载和执行,这样就阻塞了后续文档的加载。使用defer和async都会使得解析HTML的同时进行js脚本的异步下载,不会阻碍文档的解析,区别是,async在下载完js脚本之后立即执行js,有可能会阻碍文档的解析,且多个标记async的js脚本可能不会按照顺序执行;使用defer会在html解析完成之后再执行js脚本文件,而且多个defer的脚本会按照顺序执行。

HTML5有哪些更新

语义化标签

header、nav、artical、section、aside、footer

媒体标签

audio

<audio src='' controls autoplay loop='true'></audio>

video

<video src='' poster='imgs/aa.jpg' controls></video>

source标签,因为浏览器对视频格式支持程度不一样,为了能够兼容不同的浏览器,可以通过source来指定视频源。

表单

表单类型

tel、search、number、email、url、date、time、month、week、color、

表单属性

required、autofocus、autocomplate、multiple、pattern、form

表单事件

oninput:每当input里的输入框内容发生变化都会触发此事件。

oninvalid:当验证不通过时触发此事件。

进度条、度量器

进度条:progress标签:用来表示任务的进度(IE、Safari不支持),max用来表示任务的进度,value表示已完成多少

度量器 ● meter属性:用来显示剩余容量或剩余库存(IE、Safari不支持) ○ high/low:规定被视作高/低的范围 ○ max/min:规定最大/小值 ○ value:规定当前度量值 设置规则:min < low < high < max

DOM查询操作

document.querySelector(‘’“)

document.querySelectorAll(‘’”)

Web存储

sessionStorage:针对一个 session 的数据存储(会话存储:关闭页面会消失)

localStorage:没有时间限制的数据存储(本地存储:关闭页面不会消失)

history API

history.go(num)(前进或后退,可正可负)

history.forward(num)(前进)

history.back(num)(后退)

pushstate

其他

拖放:<img draggable="true" />

画布(canvas ):canvas 元素使用 JavaScript 在网页上绘制图像。画布是一个矩形区域,可以控制其每一像素。canvas 拥有多种绘制路径、矩形、圆形、字符以及添加图像的方法。 <canvas id="myCanvas" width="200" height="100"></canvas>

SVG:SVG 指可伸缩矢量图形,用于定义用于网络的基于矢量的图形,使用 XML 格式定义图形,图像在放大或改变尺寸的情况下其图形质量不会有损失,它是万维网联盟的标准

地理定位:Geolocation(地理定位)用于定位用户的位置。

css模块

CSS选择器及优先级

选择器

基础选择器

通配符选择器

标签选择器/元素选择器

类选择器

id选择器

复合选择器

后代选择器

子代选择器

并集选择器

伪类选择器

新增选择器

属性选择器

结构伪类选择器

伪元素选择器

优先级

0:通配符选择器(继承)

1:标签选择器、伪元素选择器

10:类选择器、属性选择器、结构伪类选择器

100:id选择器

1000:内联样式

无穷大:!important

需计算累加权重:复合选择器

注意

!important声明的样式的优先级最高;

如果优先级相同,则最后出现的样式生效;

继承得到的样式的优先级最低;

通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;

样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。

display的属性值及其作用

none:元素不显示,并且会从文档流中移除。

block:元素显示/块类型。默认宽度为父元素宽度,可设置宽高,换行显示。

inline:行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。

inline-block:行内块元素类型。默认宽度为内容宽度,可以设置宽高,同行显示(有间隙)。

list-item:像块类型元素一样显示,并添加样式列表标记。

table:此元素会作为块级表格来显示。

inherit:规定应该从父元素继承display属性的值。

隐藏元素的方法有哪些

display:none

渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置

visibility:hidden

元素在页面中仍占据空间

opacity: 0

将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间

position: absolute

通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。

z-index: 负值

来使其他元素遮盖住该元素,以此来实现隐藏。

clip/clip-path

使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置

transform: scale(0,0)

将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置

display:none与visibility:hidden的区别

这两个属性都是让元素隐藏,不可见。两者区别如下:

在渲染树中

display:none会让元素完全从渲染树中消失,渲染时不会占据任何空间;

visibility:hidden不会让元素从渲染树中消失,渲染的元素还会占据相应的空间,只是内容不可见。

是否是继承属性

display:none是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;

visibility:hidden是继承属性,子孙节点消失是由于继承了hidden,通过设置visibility:visible可以让子孙节点显示;

修改常规文档流中元素的 display 通常会造成文档的重排,但是修改visibility属性只会造成本元素的重绘;

如果使用读屏器,设置为display:none的内容不会被读取,设置为visibility:hidden的内容会被读取。

CSS3中有哪些新特性

新增选择器

属性选择器

结构伪类选择器

伪元素选择器

2D转换:transform

位移:transform:translate(100px,100px)

缩放:transform:scale(1,1)

旋转:transform:rotate(45deg)

3D转换:transform

位移:transform:translate(100px,100px,100px)

旋转:transform:rotate(0,0,1,45deg)

3D呈现:transform-style

flat:子元素不开启3d空间

preserve-3d:子元素开启3d空间

透视:perspective(单位px)

人的眼睛到屏幕的距离;近大远小。

动画

@keyframes 动画名称 {0% {width:100px} 100% {width:200px}}

过渡:transition

谁过度给谁加

其他特性

背景渐变background:linear-gradient(top left,red,pink)

图片模糊:filter:blur(5px)数值越大越模糊

calc函数:width:calc(100% - 80px)

圆角:border-radius

文字特效 (text-shadow)

文字渲染 (text-decoration)

position的属性有哪些?区别是什么

属性

static

默认值,没有定位,元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列。

relative

生成相对定位的元素,相对于其原来的位置进行定位。元素的位置通过left、top、right、bottom属性进行规定。

元素的定位永远是相对于元素自身位置的,和其他元素没关系,也不会影响其他元素。

absolute

生成绝对定位的元素,相对于static定位以外的一个有定位的父元素进行定位。元素的位置通过left、top、right、bottom属性进行规定。

元素的定位相对于前两者要复杂许多。如果为 absolute 设置了 top、left,浏览器会根据什么去确定它的纵向和横向的偏移量呢?答案是浏览器会递归查找该元素的所有父元素,如果找到一个设置了position:relative/absolute/fixed的元素,就以该元素为基准定位,如果没找到,就以浏览器边界定位。

fixed

生成固定定位的元素,指定元素相对于屏幕视⼝(viewport)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变,⽐如回到顶部的按钮⼀般都是⽤此定位⽅式。

元素的定位是相对于 window (或者 iframe)边界的,和其他元素没有关系。但是它具有破坏性,会导致其他元素位置的变化。

sticky

粘性定位,基于用户的滚动位置来定位。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。

inherit

规定从父元素继承position属性的值

absolute和fixed的共同点和不同点

共同点

改变行内元素的呈现方式,将display置为inline-block

子主题使元素脱离普通文档流,不再占据文档物理空间

覆盖非定位文档元素

不同点

absolute与fixed的根元素不同,absolute的根元素可以设置,fixed根元素是浏览器。

在有滚动条的页面中,absolute会跟着父元素进行移动,fixed固定在页面的具体位置。

对sticky定位的理解

sticky 英文字面意思是粘贴,所以可以把它称之为粘性定位。语法:position: sticky; 基于用户的滚动位置来定位。

粘性定位的元素是依赖于用户的滚动,在 position:relative 与 position:fixed 定位之间切换。它的行为就像 position:relative; 而当页面滚动超出目标区域时,它的表现就像 position:fixed;,它会固定在目标位置。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。

常见的CSS布局单位

像素(px):是页面布局的基础,一个像素表示终端(电脑、手机、平板等)屏幕所能显示的最小的区域,像素分为两种类型:CSS像素和物理像素:

CSS像素:为web开发者提供,在CSS中使用的一个抽象单位;

物理像素:只与设备的硬件密度有关,任何设备的物理像素都是固定的。

百分比(%):当浏览器的宽度或者高度发生变化时,通过百分比单位可以使得浏览器中的组件的宽和高随着浏览器的变化而变化,从而实现响应式的效果。一般认为子元素的百分比相对于直接父元素。

em和rem:对于px更具灵活性,它们都是相对长度单位,它们之间的区别:em相对于自身或者父元素字体,rem相对于根元素(html字体大小)。

em: 文本相对长度单位。相对于当前对象内文本的字体尺寸。如果当前行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(默认16px)。(相对父元素的字体大小倍数)。

rem: rem是CSS3新增的一个相对单位,相对于根元素(html元素)的font-size的倍数。作用:利用rem可以实现简单的响应式布局,可以利用html元素中字体的大小与屏幕间的比值来设置font-size的值,以此实现当屏幕分辨率变化时让元素也随之变化。

vw/vh是与视口有关的单位,vw表示相对于视图窗口的宽度,vh表示相对于视图窗口高度,除了vw和vh外,还有vmin和vmax两个相关的单位。

vw:相对于视窗的宽度,1vw = 1/100视口宽度;

vh:相对于视窗的高度,1vh = 1/100视口高度;

vmin:vw和vh中的较小值;

vmax:vw和vh中的较大值;

px、em、rem的区别及使用场景

区别

px是固定的像素,一旦设置了就无法因为适应页面大小而改变。

em和rem相对于px更具有灵活性,他们是相对长度单位,其长度不是固定的,更适用于响应式布局。

em是以自身的font-size为参考的,当em没有设置自身的font-size,则会以父元素的font-size为标准;

rem是相对于根元素,这样就意味着,只需要在根元素确定一个参考值。

适用场景

对于只需要适配少部分移动设备,且分辨率对页面影响不大的,使用px即可 。

对于需要适配各种移动设备,使用rem,例如需要适配iPhone和iPad等分辨率差别比较挺大的设备。

水平垂直居中的实现

绝对定位+transform

父亲给一个高度,position:relative;子盒子position:absolute,top:50%;left:50%;transform:translate(-50%,-50%)

绝对定位+margin:auto

父容器给一个高度,position:relative;子容器有宽度和高度,position:absolute,上下左右都是0,margin:auto;

绝对定位+margin负值

父容器给一个高度,position:relative;子盒子绝对定位,top:50%;left:50%,margin-top: - 高度/2 px;margin-left:- 宽度/2 px

flex布局

父容器给一个高度,display:flex,justify-content:center,align-items:center;

对Flex布局的理解及其使用场景

Flex是FlexibleBox的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为Flex布局。行内元素也可以使用Flex布局。注意,设为Flex布局以后,子元素的float、clear和vertical-align属性将失效。采用Flex布局的元素,称为Flex容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为Flex项目(flex item),简称"项目"。容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis),项目默认沿水平主轴排列。

容器属性

flex-direction属性决定主轴的方向(即项目的排列方向)。

flex-wrap属性定义,如果一条轴线排不下,是否换行。

flex-flow属性是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。

justify-content属性定义了项目在主轴上的对齐方式。

align-items属性定义单行项目在侧轴上如何对齐。

align-content属性定义了多行项目在侧轴的对齐方式。如果项目只有一行,该属性不起作用。

项目属性

order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。

flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。

flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。

flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。

flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。

flex:1 表示什么

flex属性是flex-grow,flex-shrink和flex-basis的简写,默认值为0 1 auto。flex:1 表示 flex: 1 1 auto,它还有另外两种完整写法, 分别是 initial (0 1 auto) 和 none (0 0 auto)

align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。

如何解决1px问题

问题

1px 问题指的是:在一些 Retina屏幕 的机型上,移动端页面的 1px 会变得很粗,呈现出不止 1px 的效果。原因很简单——CSS 中的 1px 并不能和移动设备上的 1px 划等号。

原因

window.devicePixelRatio = 设备的物理像素 / CSS像素。

打开 Chrome 浏览器,启动移动端调试模式,在控制台去输出这个 devicePixelRatio 的值。这里选中 iPhone6/7/8 这系列的机型,输出的结果就是2:

这就意味着设置的 1px CSS 像素,在这个设备上实际会用 2 个物理像素单元来进行渲染,所以实际看到的一定会比 1px 粗一些。

解决方案

直接写 0.5px

目前为止最简单的一种方法。这种方法的缺陷在于兼容性不行,IOS 系统需要8及以上的版本,安卓系统则直接不兼容。

伪元素先放大后缩小

思路是先放大、后缩小:在目标元素的后面追加一个 ::after 伪元素,让这个元素布局为 absolute 之后、整个伸展开铺在目标元素上,然后把它的宽和高都设置为目标元素的两倍,border值设为 1px。接着借助 CSS 动画特效中的放缩能力,把整个伪元素缩小为原来的 50%。此时,伪元素的宽高刚好可以和原有的目标元素对齐,而 border 也缩小为了 1px 的二分之一,间接地实现了 0.5px 的效果。

这个方法的可行性会更高,兼容性也更好。唯一的缺点是代码会变多。

思路三:viewport 缩放来解决

<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">

整个页面被缩放了。这时 1px 已经被处理成物理像素大小,这样的大小在手机上显示边框很合适。但是,一些原本不需要被缩小的内容,比如文字、图片等,也被无差别缩小掉了。

js模块

JavaScript有哪些数据类型,它们的区别?

数据类型

基本数据类型

字符串String

数字型Number

布尔型Boolean

未定义undefined

Null

Symbol

引用数据类型

对象型Object

数组型Array

函数型Function

Date

Math

正则RegExp

Map

Set

区别

两种类型的区别在于存储位置的不同

基本数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储

引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

数据类型检测的方式有哪些

typeof xxx

用于检测基本数据类型(除Null外,Null会被检测为object)

检测引用数据类型除(除function外,function会被检测为function)会被检测为object

返回结果为字符串类型,且类型第一个字母均为小写

xxx instanceof constructor

适用于精准检测引用数据类型

可以正确判断对象的类型,用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上,object instanceof constructor,object为实例对象,constructor为构造函数

Object.prototype.toString.call()

适用于所有数据类型

调用该方法,统一返回格式“[object Xxx]”的字符串

判断数组的方式有哪些

xxx instanceof constructor

返回布尔值

Object.prototype.toString.call( )

返回“[object Xxx]”的字符串

数组.__proto__ === Array.prototype(通过原型链做判断)

返回布尔值

通过ES6的Array.isArray()做判断

返回布尔值

通过Array.prototype.isPrototypeOf(数组名)

返回布尔值

null和undefined区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

Object.is(value1, value2) 与比较操作符 “===”、“==” 的区别?

使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较

使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false

使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的

object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别

Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。

扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

如何判断一个对象是空对象

使用JSON自带的JSON.stringify方法来判断

使用ES6新增的方法Object.keys()来判断

ES6有哪些新增?

新增了变量的声明方式、解构赋值、模板字符串、简化对象写法、箭头函数、函数形参默认值、rest参数、拓展运算符、新增数据类型(Set、Map、Symbol、BigInt)、promise、async/await

let、const、var的区别

区别主要体现在七个方面

是否有块级作用域

块作用域由 { }包括,let和const具有块级作用域,var不存在块级作用域。块级作用域解决了ES5中的两个问题:

内层变量可能覆盖外层变量

用来计数的循环变量泄露为全局变量

是否存在变量提升

var存在变量提升

let、const不存在变量提升,即变量只能在声明之后使用,否在会报错

是否添加全局属性

浏览器的全局对象是window,Node的全局对象是global。var声明的变量为全局变量,并且会将该变量添加为全局对象的属性,但是let和const不会。

能否重复声明变量

var可以重复声明变量,后声明的同名变量会覆盖之前声明的变量

const和let不允许重复声明变量

是否存在暂时性死区

在使用let、const命令声明变量之前,该变量都是不可用的。这在语法上,称为暂时性死区 let a = a/const a = a(会报错)

使用var声明的变量不存在暂时性死区:var a = a(不会报错)

是否必须设置初始值

var和let不需要赋初始值,只声明就可以

const声明时必须赋初始值,否则会报错

能否改变指针指向(重复赋值)

let创建的变量是可以更改指针指向(可以重新赋值)

const声明的变量是不允许改变指针的指向

const对象的属性可以修改吗

const保证的并不是变量的值不能改动,而是变量指向的那个内存地址不能改动。对于基本类型的数据(数值、字符串、布尔值),其值就保存在变量指向的那个内存地址,因此等同于常量;但对于引用类型的数据(主要是对象和数组)来说,变量指向数据的内存地址,保存的只是一个指针,const只能保证这个指针是固定不变的,至于它指向的数据结构是不是可变的,就完全不能控制了。

箭头函数与普通函数的区别

箭头函数比普通函数更加简洁

如果只有一个参数,可以省去参数的括号

如果函数体的返回值只有一句,可以省略大括号,且必须省略return

箭头函数没有自己的this

箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。

call()、apply()、bind()等方法不能改变箭头函数中this的指向

箭头函数的this指向要么是window,要么是他的外层

箭头函数不能作为构造函数使用

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数;new操作符的实现步骤如下:

1. 创建一个对象

2. 将构造函数的作用域赋给新对象(也就是将对象的__proto__属性指向构造函数的prototype属性)

3. 指向构造函数中的代码,构造函数中的this指向该对象(也就是为这个对象添加属性和方法)

4. 返回新的对象 所以,上面的第二、三步,箭头函数都是没有办法执行的。

箭头函数没有自己的arguments

箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。

箭头函数没有prototype

箭头函数不能用作Generator函数,不能使用yield关键字

箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。箭头函数的this指向外层函数的this

new操作符的实现原理

new操作符的执行过程:

(1)首先创建了一个新的空对象(创建一个新的内存空间)

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象添加属性)

(4)返回新对象(所以构造函数不需要return)

Map和Object的区别

同名碰撞

对象其实就是在堆开辟了一块内存,其实Map的键存的就是这块内存的地址。只要地址不一样,就是两个不同的键,这就解决了同名属性的碰撞问题,而传统的Object显然做不到这一点。

键的类型

Map的键可以是任意值,包括函数、对象或任意基本类型。

Object 的键必须是 String 或是Symbol。

键的顺序

Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。

Object 的键是无序的

Size

Map 的键值对个数可以轻易地通过size 属性获取

Object 的键值对个数只能手动计算

迭代

Map 是 iterable 的,所以可以直接被迭代,可用for...of遍历

Object不是 iterable,不可以被迭代,不能用for...of遍历

JavaScript 类数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数

常见的类数组转换为数组的方法

通过 call 调用数组的 slice 方法来实现转换

Array.prototype.slice.call(arrayLike);

通过 call 调用数组的 splice 方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

通过 apply 调用数组的 concat 方法来实现转换

Array.prototype.concat.apply([], arrayLike);

通过 Array.from 方法来实现转换

Array.from(arrayLike);

展开运算符

数组有哪些原生方法?

数组和字符串的转换方法

toString()、toLocalString()、join()

数组尾部操作的方法

pop() 和 push(),push 方法可以传入多个参数

数组首部操作的方法

shift() 和 unshift() unshift方法可以传递多个参数,表示在数组开头增加

重排序的方法

reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,则交换两个参数的位置

数组连接的方法

concat() ,返回的是拼接好的数组,不影响原数组

数组截取(浅拷贝)办法

slice(begin【end】),用于截取数组中的一部分返回,不影响原数组。

数组插入/删除/新增方法

array.splice(start[, deleteCount[, item1[, item2[, ...]]]]),改变原数组

数组归并方法

reduce() 和 reduceRight() 方法

什么是 DOM 和 BOM?

DOM 指的是文档对象模型,它指的是把文档当做一个对象,这个对象主要定义了处理网页内容的方法和接口。

BOM 指的是浏览器对象模型,它指的是把浏览器当做一个对象来对待,这个对象主要定义了与浏览器进行交互的方法和接口。BOM的核心是 window,而 window 对象具有双重角色,它既是通过 js 访问浏览器窗口的一个接口,又是一个 Global(全局)对象。这意味着在网页中定义的任何对象,变量和函数,都作为全局对象的一个属性或者方法存在。window 对象含有 location 对象、navigator 对象、screen 对象、history对象等子对象,并且 DOM 的最根本的对象 document 对象也是 BOM 的 window 对象的子对象。

ES6模块与CommonJS模块有什么异同?

不同点

1.CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

2.CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载

3.CommonJS是对模块的浅拷贝,ES6 Module是对模块的引入,即ES6 Module只存只读,不能改变其值,具体点就是指针指向不能变,类似const 。

4.import的接口是read-only(只读状态),不能修改其变量值。 即不能修改其变量的指针指向,但可以改变变量内部指针指向。可以对commonJS重新赋值(改变指针指向),但是对ES6 Module赋值会编译报错。

相同点

CommonJS和ES6 Module都可以对引⼊的对象进⾏赋值,即对对象内部属性的值进⾏改变

for...in和for...of的区别

区别

for… in 会遍历对象的整个原型链,性能非常差不推荐使用,而 for … of 只遍历当前对象不会遍历原型链

对于数组的遍历,for…in 会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for…of 只返回数组的下标对应的属性值

数组的遍历方法有哪些

for...of

不改变原数组

for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象

forEach()

视情况是否改变原数组

没有返回值

filter()

不改变原数组

数组方法,不改变原数组,有返回值,返回一个符合筛选规则的新数组

every() 和 some()

不改变原数组

数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false.

map()

不改变原数组

数组方法,不改变原数组,有返回值,生成一个一一对应的新数组

find() 和 findIndex()

不改变原数组

数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值

reduce() 和 reduceRight()

不改变原数组

数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作

forEach和map方法有什么区别

forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值

map()方法不会改变原数组的值,有返回值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值

如何实现深拷贝?

JSON.stringify()

● JSON.parse(JSON.stringify(obj))是目前比较常用的深拷贝方法之一,它的原理就是利用JSON.stringify 将js对象序列化(JSON字符串),再使用JSON.parse来反序列化(还原)js对象。

● 这个方法可以简单粗暴的实现深拷贝,但是还存在问题,拷贝的对象中如果有函数,undefined,symbol,当使用过JSON.stringify()进行处理之后,都会消失。

函数库lodash的_.cloneDeep方法

手写实现深拷贝函数

对原型、原型链的理解

在JavaScript中是使用构造函数来初始化一个对象的,每一个构造函数的内部都有一个 prototype(原型对象) 属性,它的属性值是一个对象,这个对象包含了可以由该构造函数的所有实例共享的属性和方法。当使用构造函数新建一个对象后,在这个对象的内部将包含一个指针,这个指针指向构造函数的 prototype 属性对应的值,在 ES5 中这个指针被称为对象的原型。一般来说不应该能够获取到这个值的,但是现在浏览器中都实现了 __proto__ 属性来访问这个属性(prototype原型对象),但是最好不要使用这个属性,因为它不是规范中规定的。ES5 中新增了一个 Object.getPrototypeOf() 方法,可以通过这个方法来获取对象的原型。当访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是 Object.prototype(除null外)

Event Loop(JS执行机制)

JS是单线程,也就是说,同一时间只能做一件事。

同步(按顺序来)

同步任务:同步任务都在主流程上执行,形成一个执行栈。(宏任务)

异步(同时进行)

异步任务:JS的异步是通过回调函数实现的。异步任务相关回调函数添加到任务队列中(任务队列也称消息队列)。一般而言,异步任务有三种类型:

普通事件,如click、resize等

资源加载,如load、error等

定时器,包括setTimeout、setInterval等。

JS执行机制--Event Loop

先执行执行栈中的同步任务;

异步任务(回调函数)放入任务队列中;

一旦执行栈中的所有同步任务执行完毕,系统会按次序取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行。

什么是回调函数?回调函数有什么缺点?如何解决回调地狱问题?

回调函数概念

回调函数是一个作为变量传递给另一个函数的函数,它在主体函数执行完之后再执行

回调函数特点

你定义的

你没有调用

但是最终执行了

缺点

回调函数有一个致命的弱点,就是容易写出回调地狱(Callback hell)

嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身

嵌套函数一多,就很难处理错误

不能使用 try catch 捕获错误

不能直接 return

常见的回调函数

DOM事件回调函数

定时器回调函数

ajax请求回调函数

生命周期回调函数

如何解决回调函数

promise

async/await

generator

对Promise的理解

Promise本身是同步的立即执行函数,当在executor中执行resolve()或者reject()的时候, 此时是异步操作,也就是说promise中函数体内部的非异步操作正常顺序执行,resolve()和reject()异步操作为promise实例对象的返回结果,这个返回结果后面的then或者catch需要用,所以then和catch要放到异步任务中等待所有同步任务执行完毕之后再按顺序(或者如果有定时器,需要遵循定时器的时间)执行。

Promise是异步编程的一种解决方案,它是一个对象,可以获取异步操作的消息,他的出现大大改善了异步编程的困境,避免了地狱回调,它比传统的解决方案回调函数和事件更合理和更强大。所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理

Promise的实例有三个状态

Pending(进行中)

Resolved(已完成)

Rejected(已拒绝)

Promise的实例有两个过程

pending -> fulfilled : Resolved(已完成)

pending -> rejected:Rejected(已拒绝)

Promise的特点

对象的状态不受外界影响。promise对象代表一个异步操作,有三种状态,pending(进行中)、fulfilled(已成功)、rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态,这也是promise这个名字的由来——“承诺”

一旦状态改变就不会再变,任何时候都可以得到这个结果。promise对象的状态改变,只有两种可能:从pending变为fulfilled,从pending变为rejected。这时就称为resolved(已定型)。如果改变已经发生了,你再对promise对象添加回调函数,也会立即得到这个结果。这与事件(event)完全不同,事件的特点是:如果你错过了它,再去监听是得不到结果的

Promise的缺点

无法取消Promise,一旦新建它就会立即执行,无法中途取消

如果不设置回调函数,Promise内部抛出的错误,不会反应到外部

当处于pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)

总结

Promise 对象是异步编程的一种解决方案,最早由社区提出。Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行

Promise.all和Promise.race的区别的使用场景

Promise.all()

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。Promise.all中传入的是数组,返回的也是是数组,并且会将进行映射,传入的promise对象返回的值是按照顺序在数组中排列的,但是注意的是他们执行的顺序并不是按照顺序的,除非可迭代对象为空。需要注意,Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,这样当遇到发送多个请求并根据请求顺序获取和使用数据的场景,就可以使用Promise.all来解决。

Promise.race()

Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。当要做一件事,超过多长时间就不做了,可以用这个方法来解决

对async/await 的理解

async/await其实是Generator 的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await则为等待,所以很好理解async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

async函数返回的是一个Promise 对象,如果在函数中 return 一个直接量,async 会把这个直接量通过 Promise.resolve() 封装成 Promise 对象,async 函数返回的是一个 Promise 对象,所以在最外层不能用 await 获取其返回值的情况下,当然应该用原来的方式:then() 链来处理这个 Promise 对象

async函数返回的是一个promise对象

可以用then()方法或者catch()方法来处理async函数(promise对象)

对promise/then、async/await 的理解

async函数实际上就是promise对象,promise是同步的立即执行函数,所以aysnc函数也是立即执行函数,也就是说含有async或者promise(下面还有普通函数,或者console.log(...))会按照代码的顺序进行执行(若async函数中有await需要重新考虑执行顺序,具体看下面的tip)

promise(async函数)异步任务只针对promise(async函数)中调用的then()方法或者catch()方法等里面的函数执行,promise(async函数)中的同步任务正常执行,不会阻塞,此时then或者catch方法里面的代码会放到异步任务队列中,待页面所有同步任务完成后再按照顺序(或时间顺序)执行

没有await的async函数也是立即执行函数,会正常执行async函数里面的代码

tip:有await的async函数,会暂时阻碍await下面所有代码的执行,在async函数中并且在await前面的代码(以及await紧跟着的那个异步函数也会正常运行)会正常运行,待await等待到他后面异步函数的结果之后,再执行await下面的代码。注意:await在没有等待到它后面的异步函数的结果时只是阻碍async函数中且在await下面的代码的执行,async函数中且在await前面、await后面紧跟着的那个异步任务的代码,以及async函数之后的代码不会受到影响,会正常运行

对闭包的理解

概念

闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。

比如,函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

注意:必须要先调用A,B才可以访问到A里面的变量

用途

闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。

闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。

经典面试题:循环中使用闭包解决 var 定义函数的问题

首先因为 setTimeout 是个异步函数,所以会先把循环全部执行完毕,这时候 i 就是 6 了,所以会输出一堆 6。解决办法

第一种是使用闭包的方式+立即执行函数

在上述代码中,首先使用了立即执行函数将 i 传入函数内部,这个时候值就被固定在了参数 j 上面不会改变,当下次执行 timer 这个闭包的时候,就可以使用外部函数的变量 j,从而达到目的

使用 setTimeout 的第三个参数,这个参数会被当成 timer 函数的参数传入

第三种就是使用 let 定义 i 了来解决问题

对作用域、作用域链的理解

作用域

全局作用域

最外层函数和最外层函数外面定义的变量拥有全局作用域(函数本身也是一个特殊的变量,其名字就是函数名字)

所有未定义直接赋值的变量自动声明为全局作用域

所有window对象的属性拥有全局作用域

局部作用域

声明在函数内部的变量,一般只有固定的代码片段可以访问到

作用域是分层的,内层作用域可以访问外层作用域,反之不行

块级作用域

使用ES6中新增的let和const指令可以声明块级作用域,块级作用域可以在函数中创建也可以在一个代码块中的创建(由{ }包裹的代码片段)

let和const声明的变量不会有变量提升,也不可以重复声明

在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部

作用域链

概念

在当前作用域中查找所需变量,如果在自己作用域找不到该变量就去父级作用域查找,依次向上级作用域查找,直到访问到window对象就被终止,这一层层的关系就是作用域链。

作用

保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。

本质

一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象

对执行上下文的理解

执行上下文类型

全局执行上下文

任何不在函数内部的都是全局执行上下文,它首先会创建一个全局的window对象,并且设置this的值等于这个全局对象,一个程序中只有一个全局执行上下文

函数执行上下文

当一个函数被调用时,就会为该函数创建一个新的执行上下文,函数的上下文可以有任意多个

eval函数执行上下文

执行在eval函数中的代码会有属于他自己的执行上下文,不过eval函数不常使用

执行上下文栈

JavaScript引擎使用执行上下文栈来管理执行上下文

当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文

call() 和 apply() 的区别

它们的作用一模一样,区别仅在于传入参数的形式的不同。

● apply 接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组,apply 方法把这个集合中的元素作为参数传递给被调用的函数。

● call 传入的参数数量不固定,跟 apply 相同的是,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数。

如何理解面向对象?

面向对象程序设计以对象为核心,该方法认为程序由一系列对象组成。类是对现实世界的抽象,包括表示静态属性的数据和对数据的操作,对象是类的实例化。面向对象有三个特点:

封装性

将描述每一个对象的属性以及其行为的程序代码组装到一起,一并封装在一个有机的实体中,把它们封装在一个“模块”中,也就是一个类中

继承性

继承性是面向对象技术中的另外一个重要特点,继承在面向对象技术是指一个对象针对于另一个对象的某些独有的特点、能力进行复制或者延续。

多态性

从宏观的角度来讲,多态性是指在面向对象技术中,当不同的多个对象同时接收到同一个完全相同的消息之后,所表现出来的动作是各不相同的,具有多种形态;从微观的角度来讲,多态性是指在一组对象的一个类中,面向对象技术可以使用相同的调用方式来对相同的函数名进行调用

对象创建的方式有哪些?

new Object()方式

字面量方式

工厂模式

构造函数方式

原型方式

构造函数+原型方式

类方式

对象继承的方式有哪些?

构造函数继承(call/apply)

prototype原型链继承

子类原型 = 父类实例

类继承(extends+super)

浏览器的垃圾回收机制

垃圾回收的概念

垃圾回收:JavaScript代码运行时,需要分配内存空间来储存变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。

回收机制

Javascript 具有自动垃圾回收机制,会定期对那些不再使用的变量、对象所占用的内存进行释放,原理就是找到不再使用的变量,然后释放掉其占用的内存。

JavaScript中存在两种变量:局部变量和全局变量。全局变量的生命周期会持续要页面卸载;而局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不再被使用,它们所占有的空间就会被释放。

当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。

垃圾回收的方式

标记清除

标记清除是浏览器常见的垃圾回收方式,当变量进入执行环境时,就标记这个变量“进入环境”,被标记为“进入环境”的变量是不能被回收的,因为他们正在被使用。当变量离开环境时,就会被标记为“离开环境”,被标记为“离开环境”的变量会被内存释放。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。

引用计数

另外一种垃圾回收机制就是引用计数,这个用的相对较少。引用计数就是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变为0时,说明这个变量已经没有价值,因此,在在机回收期下次再运行时,这个变量所占有的内存空间就会被释放出来

这种方法会引起循环引用的问题:例如: obj1和obj2通过属性进行相互引用,两个对象的引用次数都是2。当使用循环计数时,由于函数执行完后,两个对象都离开作用域,函数执行结束,obj1和obj2还将会继续存在,因此它们的引用次数永远不会是0,就会引起循环引用

减少垃圾回收

对数组进行优化

在清空一个数组时,最简单的方法就是给其赋值为[ ],但是与此同时会创建一个新的空对象,可以将数组的长度设置为0,以此来达到清空数组的目的

对object进行优化

对象尽量复用,对于不再使用的对象,就将其设置为null,尽快被回收

对函数进行优化

在循环中的函数表达式,如果可以复用,尽量放在函数的外面

哪些情况会导致内存泄漏

意外的全局变量

由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收

被遗忘的计时器或回调函数

设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法被回收

脱离 DOM 的引用

获取一个 DOM 元素的引用,而后面这个元素被删除,由于一直保留了对这个元素的引用,所以它也无法被回收

闭包

不合理的使用闭包,从而导致某些变量一直被留在内存当中

this指向

全局作用域或者普通函数this指向window

对象方法中的this指向方法的调用者

构造函数或构造函数的原型对象中的this指向实例对象

箭头函数中的this指向外层函数中的this

数组去重的方式

set

遍历

利用创建一个空数组,然后对原数组foreach遍历,使用indexOf判定新数组中是否有该数组元素,若没有,就往新数组中push

对象:利用同名碰撞

vue模块

vue的基本原理

当一个Vue实例创建时,Vue会遍历data中的属性,用 Object.defineProperty(vue3.0使用proxy )将每一个属性身上绑定一个 getter和setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题。

在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用Proxy 的好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为 Proxy 是 ES6 的语法。

双向数据绑定的原理

MVVM、MVC、MVP的区别

MVC、MVP 和 MVVM 是三种常见的软件架构设计模式,主要通过分离关注点的方式来组织代码结构,优化开发效率。在开发单页面应用时,往往一个路由页面对应了一个脚本文件,所有的页面逻辑都在一个脚本文件里。页面的渲染、数据的获取,对用户事件的响应所有的应用逻辑都混合在一起,这样在开发简单项目时,可能看不出什么问题,如果项目变得复杂,那么整个文件就会变得冗长、混乱,这样对项目开发和后期的项目维护是非常不利的。

MVC

M驱动V(数据驱动视图)

MVC 通过分离 Model、View 和 Controller 的方式来组织代码结构。其中 View 负责页面的显示逻辑,Model 负责存储页面的业务数据,以及对相应数据的操作。并且 View 和 Model 应用了观察者模式,当 Model 层发生改变的时候它会通知有关 View 层更新页面。Controller 层是 View 层和 Model 层的纽带,它主要负责用户与应用的响应操作,当用户与页面产生交互的时候,Controller 中的事件触发器就开始工作了,通过调用 Model 层,来完成对 Model 的修改,然后 Model 层再去通知 View 层更新

MVP

MVP 模式与 MVC 唯一不同的在于 Presenter 和 Controller。在 MVC 模式中使用观察者模式,来实现当 Model 层数据发生变化的时候,通知 View 层的更新。这样 View 层和 Model 层耦合在一起,当项目逻辑变得复杂的时候,可能会造成代码的混乱,并且可能会对代码的复用性造成一些问题。MVP 的模式通过使用 Presenter 来实现对 View 层和 Model 层的解耦。MVC 中的Controller 只知道 Model 的接口,因此它没有办法控制 View 层的更新,MVP 模式中,View 层的接口暴露给了 Presenter 因此可以在 Presenter 中将 Model 的变化和 View 的变化绑定在一起,以此来实现 View 和 Model 的同步更新。这样就实现了对 View 和 Model 的解耦,Presenter 还包含了其他的响应逻辑

MVVM

VM作为更新桥梁

MVVM构成

Model代表数据模型,数据和业务逻辑都在Model层中定义

View代表UI视图,负责数据的展示

ViewModel负责监听Model中数据的改变并且控制视图的更新,处理用户交互操作

Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步,这种模式实现了 Model和View的数据自动同步,因此开发者只需要专注于数据的维护操作即可,而不需要自己操作DOM

Computed 和 Watch 的区别

Computed

它支持缓存,只有依赖的数据发生了变化,才会重新计算

不支持异步,当Computed中有异步操作时,无法监听数据的变化

computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的

如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed

如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法

Watch

它不支持缓存,数据变化时,它就会触发相应的操作

支持异步监听

监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值

当一个属性发生变化时,就需要执行相应的操作

监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数

immediate:组件加载立即触发回调函数

deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化

对Vue组件化的理解

组件是独立和可复用的代码组织单元。组件系统是Vue核心特性之一,它使开发者使用小型、独立和通常可复用的组件构建大型应用;

组件化开发能大幅提高应用开发效率、测试性、复用性等;

遵循单向数据流的原则。

对keep-alive的理解

keep-alive是vue中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM;设置了 keep-alive 缓存的组件,会多出两个生命周期钩子activated 和deactivated

v-if和v-show的区别

手段

v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐

编译过程

v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换

编译条件

v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留

性能消耗

v-if有更高的切换消耗;v-show有更高的初始渲染消耗

使用场景

v-if适合不大可能改变;v-show适合频繁切换

v-model的实现原理

vue中v-model可以实现数据的双向绑定,但是为什么这个指令就可以实现数据的双向绑定呢?其实v-model是vue的一个语法糖。即利用v-model绑定数据后,既绑定了数据,又添加了一个input事件监听。

实现原理

● v-bind绑定响应数据

● 触发input事件并传递数据

data为什么是一个函数而不是对象

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也是因为根实例只能有一个,不需要担心这种情况

Vue中封装的数组方法有哪些,其如何实现页面更新

Vue中封装的数组方法

如何实现页面更新

简单来说就是,重写了数组中的那些原生方法,首先获取到这个数组的__ob__,也就是它的Observer对象,如果有新的值,就调用observeArray继续对新的值观察变化(也就是通过target__proto__ == arrayMethods来改变了数组实例的型),然后手动调用notify,通知渲染watcher,执行update。

vue如何监听对象或者数组某个属性的变化

先判断属性是否原本就存在,若存在,则利用自身的setter和getter实现数据的实时更新

当在项目中直接设置数组的某一项的值,或者直接设置对象的某个属性值,这个时候,你会发现页面并没有更新。这是因为Object.defineProperty()限制,监听不到变化。

Vue模版编译原理

vue中的模板template无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的HTML语法,所以需要将template转化成一个JavaScript函数,这样浏览器就可以执行这一个函数并渲染出对应的HTML元素,就可以让视图跑起来了,这一个转化的过程,就称为模板编译。模板编译又分三个阶段,解析parse,优化optimize,生成generate,最终生成可执行函数render。

解析阶段

使用大量的正则表达式对template字符串进行解析,将标签、指令、属性等转化为抽象语法树AST

优化阶段

遍历AST,找到其中的一些静态节点并进行标记,方便在页面重新渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能

生成阶段

将最终的AST转化为render函数字符串,生成render函数,浏览器执行render函数,在页面中渲染出对应的HTML元素

Vue data 中某一个属性的值发生改变后,视图会立即同步执行重新渲染吗?

不会立即同步执行重新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化, Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环tick中,Vue 刷新队列并执行实际(已去重的)工作。

常见的Vue性能优化方法

路由懒加载

keep-alive缓存页面

使用v-show复用DOM

两个模块来回切换

v-for 遍历避免同时使用 v-if

长列表滚动到可视区域动态加载

图片懒加载

对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载。

第三方插件按需引入

像element-ui这样的第三方组件库可以按需引入避免体积太大

SSR(服务端渲染)

变量本地化

子组件分隔

无状态的组件标记为函数式组件

说一下Vue的生命周期

Vue 实例有⼀个完整的⽣命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是Vue的⽣命周期。

beforeCreate(创建前)

数据观测和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的方法和数据

created(创建后)

实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,可以访问data数据以及methods方法,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性(vm实例身上)。

beforeMount(挂载前)

在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上,虚拟DOM生成,此时页面渲染的是未经vue编译的DOM结构

mounted(挂载后)

在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html 页面中。此过程中进行ajax交互。

beforeUpdate(更新前)

响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。

updated(更新后)

在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

beforeDestroy(销毁前)

实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例。

destroyed(销毁后)

实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

另外还有 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,缓存渲染后会执行 activated 钩子函数。

created和mounted的区别

created

在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图,此时可以访问到data数据及methods中的方法等。

mounted

在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作(尽量避免),至此初始化过程结束,一般在此阶段会发送ajax请求,开启定时器、绑定自定义事件,订阅消息等初始化操作。

常用的组件间通信方式有哪些?

父->子组件通信

props

父组件

子组件

props只能是父组件向子组件进行传值,props使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。

props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。

依赖注入((provide/ inject))

provide / inject是Vue提供的两个钩子,和data、methods是同级的。并且provide的书写形式和data一样。● provide 钩子用来发送数据或方法● inject钩子用来接收数据或方法

子->父组件通信

自定义事件($emit)

ref/$refs

ref: 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法。

父子组件通信

$parent / $children

跨代通信

$attrs / $listeners(属性/方法)

全局事件总线

vuex

Vuex 的原理

vuex概述

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

● Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

● 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。

核心流程及主要功能

● Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;

● 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;

● 然后 Mutations 就去改变(Mutate)State 中的数据;

● 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

各模块在核心流程中的主要功能

● Vue Components∶ Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。

● dispatch∶操作行为触发方法,是唯一能执行action的方法。

● actions∶ 操作行为处理模块。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。

● commit∶状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。

● mutations∶状态改变操作方法。是Vuex修改state的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。

● state∶ 页面状态管理容器对象。集中存储Vuecomponents中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,并进行状态更新。

● getters∶ state对象读取方法。

总结

Vuex 实现了一个单向数据流,在全局拥有一个 State 存放数据,当组件要更改 State 中的数据时,必须通过 Mutation 提交修改信息, Mutation 同时提供了订阅者模式供外部插件调用获取 State 数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走 Action ,但 Action 也是无法直接修改 State 的,还是需要通过Mutation 来修改State的数据。最后,根据 State 的变化,渲染到视图上。

Vuex有哪几种属性?

state => 基本数据(数据源存放地)

getters => 从基本数据派生出来的数据

mutations => 提交更改数据的方法,同步

actions => 像一个装饰器,包裹mutations,使之可以异步。

modules => 模块化Vuex

Vuex 和 localStorage 的区别

存储位置区别

● vuex存储在内存中

● localstorage 则以文件的方式存储在本地,只能存储字符串类型的数据,存储对象需要 JSON的stringify和parse方法进行处理。 读取内存比读取硬盘速度要快

应用场景区别

● Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。vuex用于组件之间的传值。

● localstorage是本地存储,是将数据存储到浏览器的方法,一般是在跨页面传递数据时使用 。

响应式区别

Vuex能做到数据的响应式

localstorage不能做到数据的响应式

时效区别

刷新页面时vuex存储的值会丢失

刷新页面时localstorage存储的值不会丢失

$route 和$router 的区别

$router 是“路由实例”对象包括了路由的跳转方法,钩子函数等,可以使用$router.push()、$router.replace()、$router.go()等

$route 是“路由信息对象”,包括 name,path,params,query,meta,fullPath,hash、matched等路由信息参数

params和query的区别

用法

query可以用name和path来引入;接收参数this.$route.query.name;在路由信息配置时路径path不需要占位

params要用name来引入;接收参数this.$route.params.name;在路由信息配置时路径path需要占位

url地址显示

query更加类似于ajax中get传参,在浏览器地址栏中显示参数

params则类似于post,在浏览器地址栏中不显示参数

刷新

query刷新不会丢失query里面的数据

params刷新会丢失 params里面的数据(可考虑采取本地存储解决此问题)

Vue-router 导航守卫有哪些

全局守卫(前置/后置):beforeEach、beforeResolve、afterEach

路由独享的守卫:beforeEnter

组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

对虚拟DOM的理解?

虚拟DOM就是用来描述真实DOM的javaScript对象,可以将多次修改的DOM一次性渲染到页面上,减少页面的重排重绘,提高渲染性能。 在代码渲染到页面之前,vue会把代码转换成一个对象(虚拟 DOM)。在每次数据发生变化前,虚拟DOM都会缓存一份,变化之时,现在的虚拟DOM会与缓存的虚拟DOM进行比较。在vue内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

虚拟DOM的解析过程

● 首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来并将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中。

● 当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异。

● 最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了。

DIFF算法的原理

● 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换

● 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的没有子节点,将旧的子节点移除)

● 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)。

● 匹配时,找到相同的子节点,递归比较子节点

● 更新差异,复用节点

Vue中key的作用

第一种情况是 v-if 中使用 key。由于 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因此当使用 v-if 来实现元素切换的时候,如果切换前后含有相同类型的元素,那么这个元素就会被复用。如果是相同的 input 元素,那么切换前后用户的输入不会被清除掉,这样是不符合需求的。因此可以通过使用 key 来唯一的标识一个元素,这个情况下,使用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。

第二种情况是 v-for 中使用 key。用 v-for 更新已渲染过的元素列表时,它默认使用“就地复用”的策略。如果数据项的顺序发生了改变,Vue 不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处的每个元素。因此通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚拟 DOM。

总结

vue为了更高效的渲染元素,会尽可能的复用元素,而非从头渲染,key可以为节点打标记,而非简单的复用节点。当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则

旧虚拟DOM中找到了与新虚拟DOM相同的key

若虚拟DOM中内容没变, 直接使用之前的真实DOM

若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

旧虚拟DOM中未找到与新虚拟DOM相同的key

创建新的真实DOM,随后渲染到到页面

为什么不建议用index作为key?

若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低

如果逆序添加、逆序删除等破坏顺序的操作且结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题

计算机网络

什么是HTTP及HTTPS?HTTP和HTTPS协议的区别

HTTP

概念

HTTP即超文本运输协议,是实现网络通信的一种规范,它定义了客户端和服务器之间交换报文的格式和方式,默认使用 80 端口。它使用 TCP 作为传输层协议,保证了数据传输的可靠性。HTTP是一个传输协议,即将数据由A传到B或将B传输到A,并且 A 与 B 之间能够存放很多第三方,如: A<=>X<=>Y<=>Z<=>B;传输的数据并不是计算机底层中的二进制包,而是完整的、有意义的数据,如HTML 文件, 图片文件, 查询结果等超文本,能够被上层应用识别;在实际应用中,HTTP常被用于在Web浏览器和网站服务器之间传递信息,以明文方式发送内容,不提供任何方式的数据加密

特点/优点

支持客户/服务器模式

简单快速:客户向服务器请求服务时,只需传送请求方法和路径。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快

灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记

无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间

无状态:HTTP协议无法根据之前的状态进行本次的请求处理

缺点

无状态

HTTP 是一个无状态的协议,HTTP 服务器不会保存关于客户的任何信息。

明文传输

协议中的报文使用的是文本形式,这就直接暴露给外界,不安全。

不安全

通信使用明文(不加密),内容可能会被窃听;

不验证通信方的身份,因此有可能遭遇伪装;

无法证明报文的完整性,所以有可能已遭篡改;

HTTPS

概念

为了保证这些隐私数据能加密传输,让HTTP运行安全的SSL/TLS协议上,即 HTTPS = HTTP + SSL/TLS,通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密;SSL 协议位于TCP/IP 协议与各种应用层协议之间,浏览器和服务器在使用 SSL 建立连接时需要选择一组恰当的加密算法来实现安全通信,为数据通讯提供安全支持

流程

首先客户端通过URL访问服务器建立SSL连接

服务端收到客户端请求后,会将网站支持的证书信息(证书中包含公钥)传送一份给客户端

客户端的服务器开始协商SSL连接的安全等级,也就是信息加密的等级

客户端的浏览器根据双方同意的安全等级,建立会话密钥,然后利用网站的公钥将会话密钥加密,并传送给网站

服务器利用自己的私钥解密出会话密钥

服务器利用会话密钥加密与客户端之间的通信

HTTPS的特点

HTTPS的优点

● 使用HTTPS协议可以认证用户和服务器,确保数据发送到正确的客户端和服务器;

● 使用HTTPS协议可以进行加密传输、身份认证,通信更加安全,防止数据在传输过程中被窃取、修改,确保数据安全性;

● HTTPS是现行架构下最安全的解决方案,虽然不是绝对的安全,但是大幅增加了中间人攻击的成本;

HTTPS的缺点

● HTTPS需要做服务器和客户端双方的加密个解密处理,耗费更多服务器资源,过程复杂;

● HTTPS协议握手阶段比较费时,增加页面的加载时间;

● SSL证书是收费的,功能越强大的证书费用越高;

● HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本;

● SSL证书需要绑定IP,不能再同一个IP上绑定多个域名。

区别

HTTPS是HTTP协议的安全版本,HTTP协议的数据传输是明文的,是不安全的,HTTPS使用了SSL/TLS协议进行了加密处理,相对更安全

HTTP 和 HTTPS 使用连接方式不同,默认端口也不一样,HTTP是80,HTTPS是443

HTTPS 由于需要设计加密以及多次握手,性能方面不如 HTTP

HTTPS需要SSL,SSL 证书需要钱,功能越强大的证书费用越高

常见的HTTP请求方法

● GET: 向服务器获取数据;

● POST:将实体提交到指定的资源,通常会造成服务器资源的修改;

● PUT:上传文件,更新数据;

● DELETE:删除服务器上的对象;

● HEAD:获取报文首部,与GET相比,不返回报文主体部分;

● OPTIONS:询问支持的请求方法,用来跨域请求;

● CONNECT:要求在与代理服务器通信时建立隧道,使用隧道进行TCP通信;

● TRACE: 回显服务器收到的请求,主要⽤于测试或诊断。

GET和POST的请求的区别

GET: 向服务器获取数据,POST:将实体提交到指定的资源,通常会造成服务器资源的修改;区别为

应用场景

GET 请求是一个幂等的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。

是否缓存

因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。

发送的报文格式

Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。

参数传递方式

GET参数通过URL传递,POST放在Request body中

安全性

Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。

请求长度

浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。

参数类型

get的参数类型只接受ASCII字符,post 的参数传递支持更多的数据类型。

当在浏览器中输入 Google.com 并且按下回车之后发生了什么?

解析URL

首先会对 URL 进行解析,分析所需要使用的传输协议和请求的资源的路径。如果输入的 URL 中的协议或者主机名不合法,将会把地址栏中输入的内容传递给搜索引擎。如果没有问题,浏览器会检查 URL 中是否出现了非法字符,如果存在非法字符,则对非法字符进行转义后再进行下一过程。

缓存判断

浏览器会判断所请求的资源是否在缓存里,如果请求的资源在缓存里并且没有失效,那么就直接使用,否则向服务器发起新的请求。

DNS解析

下一步首先需要获取的是输入的 URL 中的域名的 IP 地址,首先会判断本地是否有该域名的 IP 地址的缓存,如果有则使用,如果没有则向本地 DNS 服务器发起请求。本地 DNS 服务器也会先检查是否存在缓存,如果没有就会先向根域名服务器发起请求,获得负责的顶级域名服务器的地址后,再向顶级域名服务器请求,然后获得负责的权威域名服务器的地址后,再向权威域名服务器发起请求,最终获得域名的 IP 地址后,本地 DNS 服务器再将这个 IP 地址返回给请求的用户。用户向本地 DNS 服务器发起请求属于递归请求,本地 DNS 服务器向各级域名服务器发起请求属于迭代请求。

获取MAC地址

当浏览器得到 IP 地址后,数据传输还需要知道目的主机 MAC 地址,因为应用层下发数据给传输层,TCP 协议会指定源端口号和目的端口号,然后下发给网络层。网络层会将本机地址作为源地址,获取的 IP 地址作为目的地址。然后将下发给数据链路层,数据链路层的发送需要加入通信双方的 MAC 地址,本机的 MAC 地址作为源 MAC 地址,目的 MAC 地址需要分情况处理。通过将 IP 地址与本机的子网掩码相与,可以判断是否与请求主机在同一个子网里,如果在同一个子网里,可以使用 APR 协议获取到目的主机的 MAC 地址,如果不在一个子网里,那么请求应该转发给网关,由它代为转发,此时同样可以通过 ARP 协议来获取网关的 MAC 地址,此时目的主机的 MAC 地址应该为网关的地址。

TCP三次握手

下面是 TCP 建立连接的三次握手的过程,首先客户端向服务器发送一个 SYN 连接请求报文段和一个随机序号,服务端接收到请求后向服务器端发送一个 SYN ACK报文段,确认连接请求,并且也向客户端发送一个随机序号。客户端接收服务器的确认应答后,进入连接建立的状态,同时向服务器也发送一个ACK 确认报文段,服务器端接收到确认后,也进入连接建立状态,此时双方的连接就建立起来了。

HTTPS握手

如果使用的是 HTTPS 协议,在通信前还存在 TLS 的一个四次握手的过程。首先由客户端向服务器端发送使用的协议的版本号、一个随机数和可以使用的加密方法。服务器端收到后,确认加密的方法,也向客户端发送一个随机数和自己的数字证书。客户端收到后,首先检查数字证书是否有效,如果有效,则再生成一个随机数,并使用证书中的公钥对随机数加密,然后发送给服务器端,并且还会提供一个前面所有内容的 hash 值供服务器端检验。服务器端接收后,使用自己的私钥对数据解密,同时向客户端发送一个前面所有内容的 hash 值供客户端检验。这个时候双方都有了三个随机数,按照之前所约定的加密方法,使用这三个随机数生成一把秘钥,以后双方通信前,就使用这个秘钥对数据进行加密后再传输。

返回数据

当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应,浏览器接收到响应后,开始对 html 文件进行解析,开始页面的渲染过程。

页面渲染

浏览器首先会根据 html 文件构建 DOM 树,根据解析到的 css 文件构建 CSSOM 树,如果遇到 script 标签,则判端是否含有 defer 或者 async 属性,要不然 script 的加载和执行会造成页面的渲染的阻塞。当 DOM 树和 CSSOM 树建立好后,根据它们来构建渲染树。渲染树构建好后,会根据渲染树来进行布局。布局完成后,最后使用浏览器的 UI 接口对页面进行绘制。这个时候整个页面就显示出来了。

TCP四次挥手

最后一步是 TCP 断开连接的四次挥手过程。若客户端认为数据发送完成,则它需要向服务端发送连接释放请求。服务端收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明客户端到服务端的连接已经释放,不再接收客户端发的数据了。但是因为 TCP 连接是双向的,所以服务端仍旧可以发送数据给客户端。服务端如果此时还有没发完的数据会继续发送,完毕后会向客户端发送连接释放请求,然后服务端便进入 LAST-ACK 状态。客户端收到释放请求后,向服务端发送确认应答,此时客户端进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有服务端的重发请求的话,就进入 CLOSED 状态。当服务端收到确认应答后,也便进入 CLOSED 状态。

什么是HTTPS协议?

超文本传输安全协议(Hypertext Transfer Protocol Secure,简称:HTTPS)是一种通过计算机网络进行安全通信的传输协议。HTTPS经由HTTP进行通信,利用SSL/TLS来加密数据包。HTTPS的主要目的是提供对网站服务器的身份认证,保护交换数据的隐私与完整性。HTTP协议采用明文传输信息,存在信息窃听、信息篡改和信息劫持的风险,而协议TLS/SSL具有身份验证、信息加密和完整性校验的功能,可以避免此类问题发生。安全层的主要职责就是对发起的HTTP请求的数据进行加密操作 和 对接收到的HTTP的内容进行解密操作。

TLS/SSL的工作原理

TLS/SSL全称安全传输层协议(Transport Layer Security), 是介于TCP和HTTP之间的一层安全协议,不影响原有的TCP协议和HTTP协议,所以使用HTTPS基本上不需要对HTTP页面进行太多的改造。TLS/SSL的功能实现主要依赖三类基本算法

散列函数hash

基于散列函数验证信息的完整性

对称加密

对称加密算法采用协商的秘钥对数据加密

非对称加密

非对称加密实现身份认证和秘钥协商

HTTPS通信(握手)过程

1. 客户端向服务器发起请求,请求中包含使用的协议版本号、生成的一个随机数、以及客户端支持的加密方法。

2. 服务器端接收到请求后,确认双方使用的加密方法、并给出服务器的证书、以及一个服务器生成的随机数。

3. 客户端确认服务器证书有效后,生成一个新的随机数,并使用数字证书中的公钥,加密这个随机数,然后发给服 务器。并且还会提供一个前面所有内容的 hash 的值,用来供服务器检验。

4. 服务器使用自己的私钥,来解密客户端发送过来的随机数。并提供前面所有内容的 hash 值来供客户端检验。

5. 客户端和服务器端根据约定的加密方法使用前面的三个随机数,生成对话秘钥,以后的对话过程都使用这个秘钥来加密信息。

HTTPS的特点

HTTPS的优点

● 使用HTTPS协议可以认证用户和服务器,确保数据发送到正确的客户端和服务器;

● 使用HTTPS协议可以进行加密传输、身份认证,通信更加安全,防止数据在传输过程中被窃取、修改,确保数据安全性;

● HTTPS是现行架构下最安全的解决方案,虽然不是绝对的安全,但是大幅增加了中间人攻击的成本;

HTTPS的缺点

● HTTPS需要做服务器和客户端双方的加密个解密处理,耗费更多服务器资源,过程复杂;

● HTTPS协议握手阶段比较费时,增加页面的加载时间;

● SSL证书是收费的,功能越强大的证书费用越高;

● HTTPS连接服务器端资源占用高很多,支持访客稍多的网站需要投入更大的成本;

● SSL证书需要绑定IP,不能再同一个IP上绑定多个域名。

HTTPS是如何保证安全的?

对称加密与非对称加密

对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密虽然很简单性能也好,但是⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦截秘钥。

⾮对称加密

私钥 + 公钥= 密钥对

即⽤私钥加密的数据,只有对应的公钥才能解密,⽤公钥加密的数据,只有对应的私钥才能解密

因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对,通信之前双⽅会先把⾃⼰的公钥都先发给对⽅

然后对⽅再拿着这个公钥来加密数据响应给对⽅,等到到了对⽅那⾥,对⽅再⽤⾃⼰的私钥进⾏解密

HTTPS是如何保证安全的

结合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,然后发送出去,接收⽅使⽤私钥进⾏解密得到对称加密的密钥,然后双⽅可以使⽤对称加密来进⾏沟通。 此时⼜带来⼀个问题,中间⼈问题:如果此时在客户端和服务器之间存在⼀个中间⼈,这个中间⼈只需要把原本双⽅通信互发的公钥,换成⾃⼰的公钥,这样中间⼈就可以轻松解密通信双⽅所发送的所有数据。 所以这个时候需要⼀个安全的第三⽅颁发证书(CA),证明身份的身份,防⽌被中间⼈攻击。 证书中包括:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的HASH算法、证书到期时间等。但是问题来了,如果中间⼈篡改了证书,那么身份证明是不是就⽆效了?这个证明就⽩买了,这个时候需要⼀个新的技术,数字签名。 数字签名就是⽤CA⾃带的HASH算法对证书的内容进⾏HASH得到⼀个摘要,再⽤CA的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候,我再⽤同样的Hash算法,再次⽣成消息摘要,然后⽤CA的公钥对数字签名解密,得到CA创建的消息摘要,两者⼀⽐,就知道中间有没有被⼈篡改了。这个时候就能最⼤程度保证通信的安全了。

常见的状态码

1XX

100:(客户端继续发送请求,这是临时响应):这个临时响应是用来通知客户端它的部分请求已经被服务器接收,且仍未被拒绝。客户端应当继续发送请求的剩余部分,或者如果请求已经完成,忽略这个响应。服务器必须在请求完成后向客户端发送一个最终响应

101:服务器根据客户端的请求切换协议,主要用于websocket或http2升级

2XX

200(成功):请求已成功,请求所希望的响应头或数据体将随此响应返回

201(已创建):请求成功并且服务器创建了新的资源

202(已创建):服务器已经接收请求,但尚未处理

203(非授权信息):服务器已成功处理请求,但返回的信息可能来自另一来源

204(无内容):服务器成功处理请求,但没有返回任何内容

205(重置内容):服务器成功处理请求,但没有返回任何内容

206(部分内容):服务器成功处理了部分请求

3XX

300(多种选择):针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择

301(永久移动):请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置

302(临时移动): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

303(查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码

304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况

305 (使用代理): 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理

307 (临时重定向): 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求,临时重定向,和302含义类似,但是期望客户端保持请求方法不变向新的地址发出请求

4XX

400(错误请求): 服务器不理解请求的语法

401(未授权): 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。

403(禁止): 服务器拒绝请求

404(未找到): 服务器找不到请求的网页

405(方法禁用): 禁用请求中指定的方法

406(不接受): 无法使用请求的内容特性响应请求的网页

407(需要代理授权): 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理

408(请求超时): 服务器等候请求时发生超时

5XX

500(服务器内部错误):服务器遇到错误,无法完成请求

501(尚未实施):服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码

502(错误网关): 服务器作为网关或代理,从上游服务器收到无效响应

503(服务不可用): 服务器目前无法使用(由于超载或停机维护)

504(网关超时): 服务器作为网关或代理,但是没有及时从上游服务器收到请求

505(HTTP 版本不受支持): 服务器不支持请求中所用的 HTTP 协议版本

DNS完整的查询过程

首先搜索浏览器的 DNS 缓存,缓存中维护一张域名与 IP 地址的对应表

若没有命中,则继续搜索操作系统的 DNS 缓存

若仍然没有命中,则操作系统将域名发送至本地域名服务器,本地域名服务器采用递归查询自己的 DNS 缓存,查找成功则返回结果

若本地域名服务器的 DNS 缓存没有命中,则本地域名服务器向上级域名服务器进行迭代查询

首先本地域名服务器向根域名服务器发起请求,根域名服务器返回顶级域名服务器的地址给本地服务器

本地域名服务器拿到这个顶级域名服务器的地址后,就向其发起请求,获取权限域名服务器的地址

本地域名服务器根据权限域名服务器的地址向其发起请求,最终得到该域名对应的 IP 地址

本地域名服务器将得到的 IP 地址返回给操作系统,同时自己将 IP 地址缓存起来

操作系统将 IP 地址返回给浏览器,同时自己也将 IP 地址缓存起

至此,浏览器就得到了域名对应的 IP 地址,并将 IP 地址缓存起

TCP的三次握手和四次挥手

三次握手

三次握手作用

三次握手(Three-way Handshake)其实就是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。

三次握手过程

刚开始客户端处于 Closed 的状态,服务端处于 Listen 状态。

第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN,此时客户端处于 SYN_SEND 状态。

第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。

第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。

为什么不是两次握手

每次握手的作用

第一次握手:客户端发送网络包,服务端收到了 这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了 这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常

第三次握手:客户端发包,服务端收到了。 这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常

为什么不是两次握手原因

如果是两次握手,发送端可以确定自己发送的信息能对方能收到,也能确定对方发的包自己能收到,但接收端只能确定对方发的包自己能收到 无法确定自己发的包对方能收到

并且两次握手的话, 客户端有可能因为网络阻塞等原因会发送多个请求报文,延时到达的请求又会与服务器建立连接,浪费掉许多服务器的资源

四次挥手

四次挥手过程

刚开始双方都处于 ESTABLISHED 状态,假如是客户端先发起关闭请求。四次挥手的过程如下:

第一次挥手: 客户端会发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。

第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。

第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。

第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

四次挥手原因

因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送,故需要四次挥手;TCP 使用四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代 表不能再向对方发送数据,连接处于的是半释放的状态。最后一次挥手中,客户端会等待一段时间再关闭的原因,是为了防止发送给服务器的确认报文段丢失或者出错,从而导致服务器 端不能正常关闭。

对 WebSocket 的理解

概念

WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。

WebSocket原理

客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。

特点

● 支持双向通信,实时性更强,相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少

● 可以发送文本,也可以发送二进制数据‘’

● 建立在TCP协议之上,服务端的实现比较容易

● 数据格式比较轻量,性能开销小,通信高效

● 没有同源限制,客户端可以与任意服务器通信

● 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL

● 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

应用场景

弹幕

媒体聊天

协同编辑

基于位置的应用

体育实况更新

股票基金报价实时更新

浏览器原理

对浏览器的缓存机制的理解

● 浏览器第一次加载资源,服务器返回 200,浏览器从服务器下载资源文件,并缓存资源文件与 response header,以供下次加载时对比使用;

● 下一次加载资源时,由于强制缓存优先级较高,先比较当前时间与上一次返回 200 时的时间差,如果没有超过 cache-control 设置的 max-age,则没有过期,并命中强缓存,直接从本地读取资源。如果浏览器不支持HTTP1.1,则使用 expires 头判断是否过期;

● 如果资源已过期,则表明强制缓存没有被命中,则开始协商缓存,向服务器发送带有 If-None-Match 和 If-Modified-Since 的请求;

● 服务器收到请求后,优先根据 Etag 的值判断被请求的文件有没有做修改,Etag 值一致则没有修改,命中协商缓存,返回 304;如果不一致则有改动,直接返回新的资源文件带上新的 Etag 值并返回 200;

● 如果服务器收到的请求没有 Etag 值,则将 If-Modified-Since 和被请求文件的最后修改时间做比对,一致则命中协商缓存,返回 304;不一致则返回新的 last-modified 和文件并返回 200;

协商缓存和强缓存的区别

强缓存

使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。

协商缓存

如果命中强制缓存,我们无需发起新的请求,直接使用缓存内容,如果没有命中强制缓存,如果设置了协商缓存,这个时候协商缓存就会发挥作用了。命中协商缓存的条件有两个:max-age=xxx 过期了;值为no-cache。使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。如果资源发生了修改,则返回修改后的资源。

● no-cache 是指先要和服务器确认是否有资源更新,在进行判断。也就是说没有强缓存,但是会有协商缓存;

● no-store 是指不使用任何缓存,每次请求都直接从服务器获取资源。

总结

强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

浏览器的渲染过程

注意:这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

● 首先解析收到的文档,根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。

● 然后对 CSS 进行解析,生成 CSSOM 规则树。

● 根据 DOM 树和 CSSOM 规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和 DOM 元素相对应,但这种对应关系不是一对一的,不可见的 DOM 元素不会被插入渲染树。还有一些 DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。

● 当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。

● 布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的 paint 方法将它们的内容显示在屏幕上,绘制使用 UI 基础组件。

浏览器渲染优化

针对JavaScript

JavaScript既会阻塞HTML的解析,也会阻塞CSS的解析。因此我们可以对JavaScript的加载方式进行改变,来进行优化

尽量将JavaScript文件放在body的最后

<script>标签的引入资源方式有三种,有一种就是我们常用的直接引入,还有两种就是使用 async 属性和 defer 属性来异步引入,两者都是去异步加载外部的JS文件,不会阻塞DOM的解析(尽量使用异步加载)。三者的区别如下

● script 立即停止页面渲染去加载资源文件,当资源加载完毕后立即执行js代码,js代码执行完毕后继续渲染页面;

● async 是在下载完成之后,立即异步加载,加载好后立即执行,多个带async属性的标签,不能保证加载的顺序;

● defer 是在下载完成之后,立即异步加载。加载好后,如果 DOM 树还没构建好,则先等 DOM 树解析好再执行;如果DOM树已经准备好,则立即执行。多个带defer属性的标签,按照顺序执行。

针对CSS

使用CSS有三种方式:使用link、@import、内联样式,其中link和@import都是导入外部样式。它们之间的区别:

● link:浏览器会派发一个新的线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码

● @import:GUI渲染线程会暂时停止渲染,去服务器加载资源文件,资源文件没有返回之前不会继续渲染(阻碍浏览器渲染)

● style:GUI直接渲染

所以,在开发过程中,导入外部样式使用link,而不用@import。如果css少,尽可能采用内嵌样式,直接写在style标签中。

外部样式如果长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。所以CSS一般写在headr中,让浏览器尽快发送请求去获取css样式

针对DOM树、CSSOM树

● HTML文件的代码层级尽量不要太深

● 使用语义化的标签,来避免不标准语义化的特殊处理

● 减少CSS代码的层级,因为选择器是从右向左进行解析的

减少回流与重绘

CSS

避免设置多层内联样式。

如果需要设置动画效果,最好使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素。

避免使用CSS表达式(例如:calc())。

JS

避免频繁操作样式,最好将样式列表定义为class并一次性更改class属性。

避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。

可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。

浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批量处理。这样就会让多次的回流、重绘变成一次回流重绘。

浏览器本地存储方式及使用场景

Cookie

概念

Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie。

特性

● Cookie一旦创建成功,名称就无法修改

● Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie

● 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb

● 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的

● Cookie在请求一个新的页面的时候都会被发送过去

使用场景

● 最常见的使用场景就是Cookie和session结合使用,我们将sessionId存储到Cookie中,每次发请求都会携带这个sessionId,这样服务端就知道是谁发起的请求,从而响应相应的信息。(cookie与session都可用于身份认证)

LocalStorage

概念

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。永久存储,不会随着刷新页面或者关闭页面而消失

优点

● 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息

● LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在

● 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带

缺点

● 存在浏览器兼容问题,IE8以下版本的浏览器不支持

● 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage

● LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问

使用场景

● 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可

● 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中

SessionStorage

概念

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

SessionStorage与LocalStorage对比

● SessionStorage和LocalStorage都在本地进行数据存储;

● SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享;

● LocalStorage和SessionStorage都不能被爬虫爬取

应用场景

由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

Cookie、LocalStorage、SessionStorage区别

浏览器端常用的存储技术是 cookie 、localStorage 和 sessionStorage。

cookie:其实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享。

sessionStorage:html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享。

localStorage:html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享

上面三种方式都是存储少量数据的时候的存储方式,当需要在本地存储大量数据的时候,我们可以使用浏览器的 indexDB 这是浏览器提供的一种本地的数据库存储机制。它不是关系型数据库,它内部采用对象仓库的形式存储数据,它更接近 NoSQL 数据库。

Web Storage 和 cookie 的区别

● Web Storage是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽;

● cookie 需要指定作用域,不可以跨域调用;

● Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie;

● Cookie 也是不可以或缺的:Cookie 的作用是与服务器进行交互,作为 HTTP 规范的一部分而存在 ,而 Web Storage 仅仅是为了在本地“存储”数据而生。

什么是同源策略

跨域问题其实就是浏览器的同源策略造成的。同源策略限制了从同一个源加载的文档或脚本如何与另一个源的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的重要的安全机制。同源指的是:协议、端口号、域名必须一致。同源策略:protocol(协议)、domain(域名)、port(端口)三者必须一致。同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。同源政策主要限制了三个方面

● 当前域下的 js 脚本不能够访问其他域下的 cookie、LocalStorage、SessionStorage 和 indexDB。

● 当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。

● 当前域下 ajax 无法发送跨域请求。

如何解决跨越问题

CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain)上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域HTTP 请求。CORS需要浏览器和服务器同时支持,整个CORS过程都是浏览器完成的,无需用户参与。因此实现CORS的关键就是服务器,只要服务器实现了CORS请求,就可以跨源通信了。

JSONP

jsonp的原理就是利用<script>标签没有跨域限制,通过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

缺点

● 具有局限性, 仅支持get方法

● 不安全,可能会遭受XSS攻击

postMessage 跨域

解决问题

● 页面和其打开的新窗口的数据传递

● 多窗口之间消息传递

● 页面与嵌套的iframe消息传递

● 上面三个场景的跨域数据传递

用法

postMessage(data,origin)方法接受两个参数

● data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。

● origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

nginx代理跨域

nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。

跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。

nodejs 中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

document.domain + iframe跨域

此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

location.hash + iframe跨域

实现原理:a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

window.name + iframe跨域

WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。

对事件委托的理解

事件委托概念

事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件委托(事件代理)。使用事件委托可以不必要为每一个子元素都绑定一个监听事件,这样减少了内存上的消耗。并且使用事件代理还可以实现事件的动态绑定,比如说新增了一个子节点,并不需要单独地为它添加一个监听事件,它绑定的事件会交给父元素中的监听函数来处理。(e.target)

事件委托优点

减少内存消耗

如果有一个列表,列表之中有大量的列表项,需要在点击列表项的时候响应一个事件,如果给每个列表项一一都绑定一个函数,那对于内存消耗是非常大的,效率上需要消耗很多性能。因此,比较好的方法就是把这个点击事件绑定到他的父层,然后在执行事件时再去匹配判断目标元素,所以事件委托可以减少大量的内存消耗,节约效率。

动态绑定事件

给每个列表项都绑定事件,在很多时候,需要通过 AJAX 或者用户操作动态的增加或者去除列表项元素,那么在每一次改变的时候都需要重新给新增的元素绑定事件,给即将删去的元素解绑事件;如果用了事件委托就没有这种麻烦了,因为事件是绑定在父层的,和目标元素的增减是没有关系的,执行到目标元素是在真正响应执行事件函数的过程中去匹配的,所以使用事件在动态绑定事件的情况下是可以减少很多重复工作的。

事件委托缺点

focus、blur 之类的事件没有事件冒泡机制,所以无法实现事件委托

mousemove、mouseout 这样的事件,虽然有事件冒泡,但是只能不断通过位置去计算定位,对性能消耗高,因此也是不适合于事件委托的

对事件循环(Event Loop)的理解

因为 js 是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js 引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js 引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务。

顺序

● 首先执行同步代码,这属于宏任务

● 当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行

● 执行任务队列中所有微任务

● 当执行完所有微任务后,如有必要会渲染页面

● 执行任务队列中所有宏任务

宏任务和微任务分别有哪些

宏任务

script 脚本的执行(同步任务)

异步 Ajax 请求

定时器

setTimeout

setInterval

setImmediate

DOM事件

微任务

promise 的回调

node 中的 process.nextTick

对 Dom 变化监听的 MutationObserver

性能优化

性能优化方法

CDN

懒加载

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。在比较长的网页或应用中,如果图片很多,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。如果使用图片的懒加载就可以解决以上问题。在滚动屏幕之前,可视化区域之外的图片不会进行加载,在滚动屏幕时才加载。这样使得网页的加载速度更快,减少了服务器的负载。懒加载适用于图片较多,页面列表较长(长列表)的场景中。

节流与防抖

对节流与防抖的理解

防抖

概念

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。

应用场景

按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次

服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce

节流

概念

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

应用场景

拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动

缩放场景:监控浏览器resize

动画场景:避免短时间内多次触发动画引起性能问题

实现节流函数和防抖函数

函数防抖实现

函数节流实现

减少回流与重绘

回流与重绘的概念及触发条件

回流/重排

概念

当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。

触发条件

● 页面的首次渲染

● 浏览器的窗口大小发生变化

● 元素的内容发生变化

● 元素的尺寸或者位置发生变化

● 元素的字体大小发生变化

● 激活CSS伪类

● 查询某些属性或者调用某些方法

● 添加或者删除可见的DOM元素

重绘

概念

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。

触发条件

● color、background 相关属性:background-color、background-image 等

● outline 相关属性:outline-color、outline-width 、text-decoration

● border-radius、visibility、box-shadow

如何避免回流与重绘

CSS

避免设置多层内联样式。

如果需要设置动画效果,最好使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素。

避免使用CSS表达式(例如:calc())。

JS

避免频繁操作样式,最好将样式列表定义为class并一次性更改class属性。

避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。

可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。

浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列

浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批量处理。这样就会让多次的回流、重绘变成一次回流重绘。

documentFragment是什么?用它跟直接操作DOM的区别是什么?

概念

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

与直接操作 DOM 的区别

由于DocumentFragment不会出现在文档树中,将DocumentFragment插入文档树中,相当于把把他的子孙节点插入到文档树中,在频繁的DOM操作时,我们就可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作DOM相比,将DocumentFragment 节点插入DOM树时,仅会触发页面的一次重绘,这样就大大提高了页面的性能。

图片优化

webpack优化

如何用webpack优化前端性能?

通过webpack优化前端的手段

代码压缩

JS代码压缩

利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件

CSS代码压缩

利⽤ cssnano (css-loader?minimize)来压缩css

Html文件代码压缩

使用HtmlWebpackPlugin插件来生成HTML的模板时候,通过配置属性minify进行html优化

文件大小压缩

对文件的大小进行压缩,减少http传输过程中宽带的损耗

图片压缩

Tree Shaking

将代码中永远不会⾛到的⽚段删除掉(消除死代码)。可以通过在启动webpack时追加参数 --optimize-minimize 来实现;

代码分离

代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存

提取公共第三⽅库

SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

如何提高webpack构建速度?

优化webpack构建的方式有很多,主要可以从优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面入手

优化loader配置

在使用loader时,可以通过配置include、exclude、test属性来匹配文件,缩小文件的搜索范围,优化搜索时间

多⼊⼝情况下,使⽤ CommonsChunkPlugin 来提取公共代码

通过 externals 配置来提取常⽤库,脱离webpack打包,不被打⼊bundle中,从⽽减少打包时间

利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来,让⼀些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间

使⽤ Happypack 实现多线程加速编译

使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度

使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码

利⽤缓存提⾼rebuild效率

如何减少webpack打包时间?

优化 Loader

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。

优化 Loader 的文件搜索范围

对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。

还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

HappyPack

受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了

DllPlugin

DllPlugin 可以将特定的类库提前打包然后引入。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。

然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中

代码压缩

在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin 来并行运行 UglifyJS,从而提高效率。在 Webpack4 中,不需要以上这些操作了,只需要将 mode 设置为 production 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 console.log 这类代码的功能。

其他

resolve.extensions

用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面

resolve.alias

可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径

module.noParse

如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助

如何减少webpack打包体积?

按需加载

在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 loadash 这种大型类库同样可以使用这个功能。

Scope Hoisting

Scope Hoisting 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 optimization.concatenateModules 就可以了

Tree Shaking

Tree Shaking 可以实现删除项目中未被引用的代码(死代码)

git

git流程图

git基础命令

git分支

如何将一个分支上的代码转移到另一个分支上去

没有commit

1.先git add . 把修改的保存到暂存区

2.git stash 把暂存区的修改储存起来

3.git checkout dev 切换到正确的分支

4.git stash pop 将储存的修改取出来

commit

git reset HEAD^ 撤回到刚刚commit之前,继续2.3.4步

合并时冲突问题

冲突就是你pull下来代码后,将dev分支的代码合并到master发现你同事和你改了同一个地方,就会出现冲突

声明:只是我面试的公司问的超高频的面试题,若时间充裕还是建议看完整版的面试题,而且所有答案只适用于初级前端面试,若是中高级需要自己深入去了解!!!

相关思维导图模板

进阶面试题总结思维导图

树图思维导图提供 进阶面试题总结 在线思维导图免费制作,点击“编辑”按钮,可对 进阶面试题总结  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:fac4f2eaa7b9b4cfd44ba29e73117dbe

模拟面试题答案脑图思维导图

树图思维导图提供 模拟面试题答案脑图 在线思维导图免费制作,点击“编辑”按钮,可对 模拟面试题答案脑图  进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:2e4d4dffb03aa4d4f82ae540eb39e25a