一年前端面试:弄懂这些知识点很有必要!!!

278 阅读1小时+

哈喽~我是刘十一,"金三银四",又到了忙碌的日子了呢,你是否做好准备了呢?

长文! 长文! 长文!(重要的事说三遍)

目录结构:

  • HTML
  • CSS
  • JS
  • React
  • 浏览器与网络
  • webpack
  • TypeScript

HTML

1、cookie、localStorage、sessionStorage的区别

首先是在生命周期方面,cookie是可以设置过期时间的,没有设置的话浏览器关闭就会失效。而sessionStorage仅仅在当前网页会话下有效,关闭页面或浏览器就失效。localStorage除非手动清理否则是永久存在的。
其次是存放数据的体积,cookie一般只有4kb左右,而session与local则是约5M左右。
最后cookie每次都会携带在http请求头中,如果cookie过多保存数据的话给带来不必要的性能问题,而session与local不参与和服务器的通信,仅仅存在客户端。
在平时开发中可以将cookie通常用于判断两个请求是否来自于同一个浏览器,例如用户保持登录状态,其他情况还是使用session与local比较好。

2、HTML5 的新特性有哪些

用于绘画的元素:canvas与svg
用于播放视频与音频的元素:video与audio
用于本地离线存储:sessionStorage与localStorage
特殊的内容元素:nav,section,header,footer,footer等
用于拖放的元素:drag,Drop

3、HTML5的优缺点

HTML5提高了可用性与可维护性,改进了用户体验,新增的语义化标签有助于开发人员定义清晰的结构,利于SEO。但是只有大多数高版本的浏览器支持HTML5(Firefox、IE9及其更高版本、Chrome),仍有小部分低版本的浏览器是不支持的。

4、语义化的理解

语义化可以使HTML标签也具有语义的意义,可以传达本身关于标签所包含内容类型的一些信息。
为什么要语义化:首先从代码结构来说使用语义化的标签可以在没有使用CSS的情况下也可以很好的呈现内容结构,也更利于团队开发和维护;其次非常利于SEO, 爬虫依赖标签来确定关键的权重,因此使用语义化的标签可以和搜索引擎建立良好的沟通,帮助爬虫获取更多有效的信息;也可以方便其他设备解析,以有意义的方式来渲染网页。
比较常用的元素标签有header,nav,footer,article,section等\

5、meta 标签都包含哪些使用方法

元数据(metadata)是关于数据的信息。
标签提供关于 HTML 文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。
典型的情况是,meta 元素被用于规定页面的描述、关键词、文档的作者、最后修改时间以及其他元数据。
标签始终位于 head 元素中。
元数据可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。

meta有个必须的属性content用于表示需要设置项的值,两个非必须的属性http-equiv和name表示需要设置的项。
(1)、http-equiv:与http请求头有关的信息,设置的值会关联到http的头部。浏览器在请求服务器获取html的时候服务器会将html中设置的meta放在响应头中返回给浏览器。

  • content-type:用来声明文档类型,设置字符集。
  • expires:用于设置浏览器的过期时间。
  • refresh:自动刷新并跳转到指定的网页,如果不设置url则刷新本网页。
  • window-target:强制页面在当前窗口以独立的页面显示,可以防止别人在框架中调用自己的页面。
  • pragma:禁止浏览器从本地计算机的缓存中访问页面内容。 (2)、name:主要用于描述网页,主要是便于搜索引擎查找信息和分类信息用的。
  • author:网页作者。
  • description:用来告诉搜索引擎当前网页的主要内容,关于网站的描述信息。
  • keywords:设置网页的关键字。
  • generator:表示当前的html使用什么工具来编写,没有实际作用。
  • robots:告诉搜索引擎机器人抓取哪些页面。
<!-- 页面关键词 keywords -->
<meta name="keywords" content="HTML,ASP,PHP,SQL">
<!-- viewport主要是影响移动端页面布局的,例如: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6、script标签上defer与 async 的区别

html有三种script标签:
(1)script:浏览器在解析html的时候,如果遇到没有任何属性的script标签,就会暂停html的解析,先发送网络请求获取该js的代码内容,然后让js引擎执行该内容,当js引擎执行完毕后,再去解析html。script阻塞了浏览器对html的解析,如果js迟迟不响应,那么会导致白屏。
(2)async script:当浏览器遇到带有async属性的script时,请求该脚本的网络请求时异步的,不会阻塞浏览器解析html,一旦网络请求回来之后,如果此时html没有解析完,就先暂停解析,先让js引擎执行代码,js执行完毕之后在来解析html。但是async是不可控制的,执行时间不确定,多个async的执行顺序也是不确定的。
(3)defer script: 当浏览器遇到带有defer属性的script时,获取该脚本的网络请求也是异步的,不会阻塞浏览器解析HTML,一但网络请求回来以后,如果此时html还没解析完,浏览器不会暂停解析html。而是等html解析执行完毕之后再去执行js代码。如果存在多个defer,浏览器会保证它们按照html中出现的顺序执行,不破坏js脚本之间的依赖关系。

CSS

1、Flex 布局

弹性盒布局由父级容器与子级容器构成,通过设置主轴与交叉轴来控制元素的排列方式。可以简便、完整、响应式地实现各种页面布局,如果一个元素使用了弹性布局,则内部会形成bfc。
父级容器一般有以下属性:

  • flex-direction决定主轴的方向。
  • flex-warp可以设置子元素是否换行。
  • justify-content定义子元素在主轴上的对齐方式,align-items定义项目在交叉轴上的对齐方式。

子级容器一般由以下属性:

  • order定义自己容器的排列顺序,数值越小越靠前。
  • flex-grow定义放大比例。
  • flex-shrink定义项目缩小比列,默认为1。
  • flex-basis在分配多余空间之前,项目占据主轴的空间。
  • align-self允许单个项目有与其他项目不一样的对齐方式。 经常用于盒子的水平垂直居中,自适应布局,两栏布局等
2、BFC

BFC(块级格式化上下文),是指一个由块级元素形成的独立渲染区域,在这个区域里面,元素的布局由特定的约束规则决定,不受外界影响
BFC的创建:

  • 根元素、浮动的元素、绝对定位于固定定位的元素、表格的标题和单元格、行内块元素、overfolw不为hidden的元素、弹性元素、网格元素。 BFC的特性:
  • BFC内部的块级盒会在垂直方向上一个接一个的排列
  • 同一个BFC下的相邻块级元素可能发生外边距(margin)折叠,创建新的BFC可以避免
  • 每个元素的外边距盒的左边于包含块边框盒的左边相接触,即使浮动也这样。
  • 浮动盒的区域不会与BFC重叠
  • 计算bfc的高度时,浮动的元素也参与计算。 bfc的应用:自适应多栏布局、防止外边距折叠、清除浮动等。

解决了什么问题:

  • 解决浮动元素令父元素高度塌陷的问题

如,父元素div包含几个子div,这几个子元素都为浮动时,父元素高度坍塌,这是因为浮动的子元素脱离了文档流,漂浮于父元素之上,父元素检测不到子元素的存在,获取不到子元素高度,所以看起来父元素没有高度了。父元素后面的布局也就乱了,这时,可以给父元素添加属性overflow:hidden,当然,这只是一种BFC的做法,还可以display: table-cell;或position: fixed;或position: absolute; 触发了BFC的容器就是页面上的一个完全隔离开的容器,容器里的子元素绝对不会影响到外面的元素,为了保证这个规则,触发了BFC的父元素在计算高度时,不得不让浮动的子元素也参与进来,变相的实现了清楚内部浮动的目的。

  • 解决自适应布局的问题

PC端的网页,左右两栏布局很常见,一般左侧定宽,右侧主体页面宽度自适应变化,通常是用浮动来实现的;它利用了块级元素占满一行的特性,使得右边的元素可以随着页面宽度的变化而变化,又利用了浮动的特性,让左侧元素覆盖在右侧元素上方,同时还能挤开下方元素的内容,让页面看起来是两栏的效果,但随着右边元素的增加,超出了左边元素的高度后,文字就会环绕左侧元素,这显然不是我们想要的效果,因为右侧元素触发了BFC,触发BFC的容器就是页面上的一个完全隔离开的容器,容器中的子元素绝对不会影响到外面的元素,为了保证这个规则,触发了BFC的右侧元素为了将内部元素和左侧浮动元素隔离开,不得不形成这样左右完全隔离的两栏,同时,如果右侧元素依旧是块级元素,那么他尽可能占满一行的特性还在,这样就保证了右侧元素依旧是自适应的。

  • 解决外边距垂直方向重合问题

垂直方向上两个兄弟元素外边距会取最大值,而不是取和,那么我们可以通过触发BFC来防止他们之间相互影响。为其中一个元素的外边包裹一层父元素,并且触发父元素BFC,比如:overflow:hidden,这样打破原有格局,就不再会重叠啦! 另外我们可以用padding来代替marging,优点是简单易懂,缺点是如果根据设计本来就需要设置padding样式,那就没办法用了。

3、盒模型

在 CSS 中一个盒子由Content,padding,border,margin这四个部分组成。现在一共有标准盒模型和怪异盒模型,二者的区别主要是宽高的计算方式不同。标准盒模型的整体宽高就等于content的宽高,而怪异盒模型的宽高等于content+padding+border。两者盒模型可以通过box-sizing这个属性控制,content-box为标准和模型,border-box为怪异盒模型。

4、两栏布局

一般两栏布局指的是左边一栏宽度固定,右边一栏宽度自适应,两栏布局的具体实现。

  • 利用浮动:左侧固定宽度,并设置左浮动;右边元素的margin-left设置为左侧元素的宽度,宽度设置为auto(默认为auto,撑满整个父元素)。
  • 利用flex:将左边元素的放大和缩小比例设置为0,基础大小设置为200px (flex-shrink: 0; flex-grow: 0;flex-basis: 200px;)。将右边的元素的放大比例设置为1,缩小比例设置为1,基础大小设置为auto。
  • 利用绝对定位布局的方式:将父级元素设置相对定位。左边元素设置为absolute定位,并且宽度设置为200px。将右边元素的margin-left的值设置为200px。
5、三栏布局有哪些实现方式
  • 浮动方法:一左一右浮动,中间的margin-left为左边的宽度
  • 绝对定位:父元素相对定位,一左一右绝对定位,分别位于一左一右,中间使用margin空出左右元素所占据的空间。
  • flex:父元素为弹性盒,中间的元素flex:auto
6、水平垂直居中
  • Flex布局:父元素display:flex

  • 未知高宽:

    • 子绝父相,上下左右都为0,margin为auto。
    • 父 display: flex(grid);justify-content: center( align-items: center;);子align-self: center;
  • 知道高宽:子绝父相,margin负值盒子自身的一半。

  • 平移+定位:子绝父相,上左为50%,transform:translate(-50%)

  • 平移+margin:margin上左各50%,transform:translate(-50%)

  • absolute + calc:子绝父相,上左为 calc(50% - 子盒子自身的一半);

7、CSS3 的新特性有哪些

属性选择器

过度: transform: translate(120px, 50%):位移 transform: scale(2, 0.5):缩放 transform: rotate(0.5turn):旋转 transform: skew(30deg, 20deg):倾斜

渐变: linear-gradient:线性渐变 background-image: linear-gradient(direction, color-stop1, color-stop2, ...); radial-gradient:径向渐变linear-gradient(0deg, red, green);

圆角: border-radius::创建圆角边框; border-image:使用图片来绘制边框

阴影:box-shadow 设置元素阴影,设置属性如下: 水平阴影 垂直阴影 模糊距离(虚实) 阴影尺寸(影子大小) 阴影颜色 内/外阴影

8、CSS选择器及优先级

内联 > ID选择器 > 类选择器 > 标签选择器。

CSS选择器包括行内样式、id选择器、class选择器、标签选择器,优先级依次降低,!important可用于优先级提升,比行内样式优先级还要高,权重的计算依次为1000,100,10,1,!important的优先级为正无穷。

但实际上,1000,100,10,1不是十进制中的1000,100,10,1,而是进制数。
不是2进制,不是10进制,而是256进制,就是0到255后+1才是1,
比如通配符的权重为0,伪元素的权重为1,中间相差了255,依次类推。

基本选择器

  • * :通过元素选择器,匹配任何元素
  • E :标签选择器,匹配所使用E标签的元素
  • .info :class选择器,匹配所有class属性中包含info的元素
  • #footer :id选择器,匹配所有id属性等于footer的元素

多元素组合

  • E,F :多元素选择器,同时选择所有E元素或F元素,两者之间用“,”逗号分割
  • E F :后代选择器,匹配所有属于E元素后代的F元素,两者之间用“ ”空格分割
  • E>F :子元素选择器,匹配所有E元素的子元素F
  • E+F :毗邻元素选择器,匹配所有紧随E元素之后的同级元素F
  • E ~ F : 匹配任何在E元素之后的同级F元素(CSS3 的 同级元素 通用选择器)

属性选择器

  • E[att] :匹配所有具有att属性的E元素,不考虑其值(注:E可省略,比如“[cheacked]”,以下同。)
  • E[att=val] :匹配所有att属性等于"val"的E元素
  • E[att~=val] :匹配所有att属性具有多个空格分隔的值 、其中一个值等于"val"的E元素
  • E[att^="val"] :属性att的值以"val" 开头 的元素(CSS 3 属性选择器)
  • E[att$="val"] :属性att的值以"val" 结尾 的元素(CSS 3 属性选择器)
  • E[att*="val"] :属性att的值 包含"val"字符串 的元素(CSS 3 属性选择器)

伪元素

  • E:first-line :匹配E元素的第一行
  • E:first-letter :匹配E元素的第一个字母
  • E:before :在E元素 之前 插入生成的内容
  • E:after :在E元素 之后 插入生成的内容
a:link:after { 
content: " (" attr(href) ") "; 
}

伪类

  • E:first-child :匹配父元素的第一个子元素p:first-child { font-style:italic; }
  • E:link :匹配所有未被点击的链接
  • E:visited :匹配所有已被点击的链接
  • E:active :匹配鼠标已经其上按下、还没有释放的E元素
  • E:hover :匹配鼠标悬停其上的E元素
  • E:focus :匹配获得当前焦点的E元素input[type=text]:focus { color:#000; background:#ffe; }
  • E:lang(c) :匹配lang属性等于c的E元素

CSS 3 中 与用户界面有关的 伪类

  • E:enabled :匹配表单中激活的元素
  • E:disabled : 匹配表单中禁用的元素input[type="text"]:disabled { background:#ddd; }
  • E:checked :匹配表单中被选中的radio(单选框)或checkbox(复选框)元素
  • E::selection : 匹配用户当前选中的元素

CSS 3中的结构性伪类

  • E:root :匹配文档的根元素,对于HTML文档,就是HTML元素
  • E:nth-child(n) :配其父元素的第n个子元素,第一个编号为1
  • E:nth-of-type(n) :与:nth-child()作用类似,但是仅匹配使用同种标签的元素
  • E:first-of-type :匹配父元素下使用同种标签的第一个子元素,等同于:nth-of-type(1)
  • E:nth-last-child(n) :匹配其父元素的倒数第n个子元素,第一个编号为1
  • E:last-child :匹配父元素的最后一个子元素,等同于:nth-last-child(1)
  • E:nth-last-of-type(n) :与:nth-last-child() 作用类似,但是仅匹配使用同种标签的元素
  • E:last-of-type:匹配父元素下使用同种标签的最后一个子元素,等同于:nth-last-of-type(1)
  • E:only-child :匹配父元素下仅有的一个子元素,等同于:first-child:last-child或 :nth-child(1):nth-last-child(1)
  • E:empty:匹配一个不包含任何子元素的元素,注意,文本节点也被看作子元素
p:nth-child(odd) { color:#f00; }
p:nth-child(even) { color:#f00; }
p:nth-child(3n+0) { color:#f00; }
  • E:not(s) :匹配不符合当前选择器的任何元素(CSS 3 的 反选伪类):not(p) { border:1px solid #ccc;
  • E:target :匹配文档中特定"id"点击后的效果(CSS 3中的 :target 伪类)
9、scss\less\sass\css

less, sass, scss都是css预处理语言(也是对应的文件后缀名),它们的语法功能比css更强大。
预处理语言使用是:开发时用预处理语言,在打包上线时,用webpack再配合loader工具给转成css(Cascading Style Sheets)给浏览器使用。

scss 的基本语法:

(1)可以使用$来标识变量(可以将常用的样式标记成变量,后续复用即可,方便维护)

$highlight-color: #f40;
$basic-border: 1px solid black;
#app {
    background-color: $highlight-color;
    border: $basic-border;
}  
......

(2)嵌套语法(和less语法相同)

(3)& 父选择器(跟less语法相同)

......
  a{
    color:blue;
    &:hover{
      color: red;
    }
  }
......

(4)模块化(可以将需要的变量定义在一个新的js文件中,需要使用的样式文件直接引入即可)

@import './base.scss';
$highlight-color: #f90;
$basic-border: 1px solid black;
......
10、1px 问题

这个问题首先要知道在css中长度单位分为绝对单位与相对单位,我们熟知的px是一个相对单位,相对的是设备像素。
通过 设备像素/css像素 可以得到设备像素比。不同的设备可能会有不同的dpr(像素比),才导致了1px展示的宽度不一样。比较常见的方法有 通过其他属性模拟出来与跳过设置1px的问题。例如通过border-shadow来处理模拟,伪元素+缩放等。

11、重绘与重排

重绘:就是更新了元素的绘制属性,没有改变布局,重新把元素外观绘制出来的过程。外观属性一般有界面的box-shadow,opacity,visibilty等,文字的大小等。
重排:更新了元素的几何属性,那么浏览器就需要重新计算元素的几何属性,将元素进行重新排列。再日常开发中添加或删除可见的元素,改变元素的尺寸,浏览器窗口尺寸改变,offset等布局信息都会引起重排。

每次重排都会造成额外的计算消耗,因为大多数浏览器都是通过 队列化修改 并 批量执行 优化重排过程。浏览器会将修改操作保存起来,过一段时间或者操作到了一定的值才会清空队列。但是如果你获取布局信息操作,例如scrolltop,clientHeight等就会强制清空队列,立即重排+重绘。

针对重绘与重排我们可以进行一些性能优化:例如visibility代替display,none。避免使用table布局,对于一些复杂的动画效果可以使其脱离正常的文档流。还可以动态去改变类而不是样式。

JS

1、事件循环

js是单线程的,主要是用来再浏览器上验证表单操作dom元素的一门脚本语言;如果js是多线程的,那么两个线程同时对一个dom进行相互冲突的操作,那么浏览器解析是无法执行的。
故, js实现异步任务的执行机制就是事件循环。并且执行的任务被分为宏任务(I/O 操作、setInterval、script、setTimeOut,ajax请求等)微任务(promise,process.nextTick(会优先于微任务)),两者都是按照先入先出的顺序执行,每个宏任务都会有一个对应的微任务队列,宏任务在执行过程中会先去执行同步代码再执行微任务队列。

  • 首先判断js是同步的还是异步的,同步就进入主线程运行,异步就进入任务栈,
  • 异步任务在任务栈中注册事件,当满足触发条件以后,事件的回调被推入任务队列。
  • 主线程在执行完同步任务后,会去检查微任务队列,然后清空微任务队列。
  • 到任务队列获取新的任务,开始下一个循环。

async await的执行顺序是先执行async中的同步代码,然后到await时会阻塞,跳出整个函数,去执行外面的所有同步代码(不包括微任务),然后再回到函数执行await后面的代码,并把async生成的微任务推入微任务队列。(await下面的代码才会阻塞,await后面的还是会顺序执行的)。

来个综合题,试着练一练

console.log('1');

setTimeout(function() {
  console.log('2');
  process.nextTick(function() {
    console.log('3');
  });
  new Promise(function(resolve) {
    console.log('4');
    resolve();
  }).then(function() {
    console.log('5');
  });
}); 

process.nextTick(function() {
  console.log('6');
});

new Promise(function(resolve) {
  console.log('7');
  resolve();
}).then(function() {
  console.log('8');
});

setTimeout(function() {
  console.log('9');
  process.nextTick(function() {
    console.log('10');
  }) 
  new Promise(function(resolve) {
    console.log('11');
    resolve();
  }).then(function() {
    console.log('12')
  });
})

答案:1、7、6、8、2、4、9、11、3、10、5、12

2、async/await

ES6的时候,新增了promise函数区以同步的书写方式 实现异步回调,解决了回调地狱的问题,但是promise通过then的链式调用也会造成promise回调地狱。acync/await在底层转换成了peomise和then的回调函数,也即是说async实质上是promise的语法糖,可以解决回调问题。

3、Promise

promise:是异步编程的一种解决方案,简单来说promise是一个对象可以获取异步操作的消息,将异步操作以同步操作的流程表达出来
特点:
对象的状态不受外界影响,有三种状态 pending,fulfilled,rejected。只有异步操作的结果可以决定当前是哪个状态; 一旦状态改变就不会再变,任何时候都可以得到这个结果。只能从pedding到fulfilled或者从peding到rejected。
缺点:
无法取消promise;如果不设置回调函数,rpomise内部抛出的错误,不会反应到外部;当处于peding状态时,无法得知目前进展到哪个阶段;
原型方法:
then:为peomise实列添加状态改变时的回调函数。参数为resolev和rejected状态函数回调都是可选的。会返回一个新的promise实列
catch:用于指定发生错误时的回调函数。
finally:用于指定不管peomise对象最后状态如何,都会执行操作。该方法不接受任何参数,所以也无法得知promise的状态是什么。
promise方法:
all:将多个promise实列包装成一个新的peomise实列。可以接受数组参数。这个新的promise的状态由内部的多个promise实列决定。只有内部的所有实列的状态都是成功时,新promise的状态才为成功,将多个实列的返回值组成一个数组,转递给新的promise。只要参数中有一个reject了,新的promise的状态就为reject,第一个被reject的返回值 会给到新promise实列。
race:同样将多个promise实列包装为一个新的promise。但是只要参数中的promise有一个最先状态改变,那么新promose的状态就跟着改变,最先改变的promise的返回值会给到新建的promise实列。
any:同上,但是只要参数实列有一个状态变为fulfilled的话新的promise状态就为成功。只有所有参数实列都是rejected时,才会变为rejected。

4、原型与原型链

在js中,每定义一个对象,这个对象一定会带有 __proto__ 属性并且关联另一个对象,这个对象也就是原型也就是prototype对象。使用原型对象的好处就是所有对象实例可以共享它的属性和方法。

当在一个对象obj上访问某个属性时,如果不存在于obj上,那么便会去obj的原型也就是 obj.__proto__ 上去找这个属性,如果有则返回这属性,如果没有则去对象obj的原型的原型也就是 obj.__proto__.__proto__ 上去找,重复上述步骤一直到访问Object.prototype,没有的话继续沿着 __proto__ 找,也就是null,有则直接返回,无则返回undefined。

原型污染一般是指攻击者通过某种手段修改js对象的原型。原型污染可能会导致性能问题,例如每次访问对象 自身不存在的属性时 都要访问一下原型上被污染的属性,增加遍历次数。 可以通过Object.create(null)创建没有原型的对象,这样一来即便设置 __proto__ 也没有用,因为原型一开始就是null。 第二种是Object.freeze(obj),这样一来被冻结的对象不能修改属性,成为不可扩展对象。

5、JS继承

第一种是原型链继承将父类的实例作为子类的原型。这中继承方式虽然继承了父类的模板以及父类的原型对象。但是来自原型对象的所有属性被所有实例所共享,当有引用类型的属性被更改时,所有实列都受影响。此外还没有办法向父类构造函数传递参数。
第二种是构造继承在子类型的构造函数内部(通过call)调用父类型的构造函数。这种继承方式解决了原型链继承中的子类实例共享父类引用类型数据的问题,创建子类实列时,也可以向父类传递参数。但是这种继承方式并不涉及原型,因此父类原型上的属性与方法不会被继承。
第三种是组合继承使用原型链继承实现对父类原型上的属性与方法的继承,使用构造函数来实现对实列属性的继承。这种继承方式通过调用父类构造,继承父类的属性并保留了传参的优点,然后通过将父类作为子类的原型实现函数复用。但是需要调用两次父类构造函数,还必须手动的去修复子类的constructor才可以。
第四种是寄生组合继承:借用构造函数继承属性,通过原型链来继承方法,通过寄生方式,砍掉父类的实列属性,这样就不会初始化两次实例方法与属性。主要使用Object.create(父类.prototype)。这种继承方式只调用一次父类构造函数。只创建一份父类属性,原型链保持不变。
第五种是原型继承:将子类的原型设置为父类的原型。主要是借用Object.create方法接受一个作为新创建对象的原型的对象。用构造函数继承实例属性与方法,通过Object.create继承原型属性与方法。修改子类的原型对象的constructor指向。
第六种是ES6只用extends实现继承。实现方法寄生组合继承是一样的。

6、new到底做了什么

创建一个空对象,该对象的 隐式原型 指向该函数的 原型,这个新对象会绑定到函数调用的this,如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个对象。

function myNew(con,...args){//con为构造函数,剩下的是参数
    let obj={}  //创建一个空对象
    Object.setPrototypeOf(obj,con.prototype) //将对象的与构造函数通过原型链接起来 等同于obj.__proto__ = con.prototype
    let result = con.apply(obj,args) //将obj绑定到构造函数上并传入剩下参数
    return result instanceof  Object ? result : obj//判断构造函数的返回值是不是对象,是的话就只用构造函数的返回值,不是的话就使用obj
}
7、作用域与作用域链

简单理解的话作用域就是变量与函数的可访问范围,控制着变量与函数的可见性与生命周期,用于隔离变量,不同作用域下同名变量不会有冲突。作用域有词法作用域与动态作用域之分。所谓的词法作用域就是在定义变量或函数的时候就决定了作用域,JS就是采用这一方法。而动态作用域则相反,作用域在函数执行时才决定。并且,作用域又有全局作用,函数作用域,块级作用域。
一般拥全局作用域有以下三种情况:最外层函数和最外层函数外面定义的变量;所有未定义直接赋值的变量;所有的window对象属性。
函数内部就是函数作用域,内层作用域可以访问到外层作用域,而外层不可以访问内层。
块级作用域通过let 和const声明,在一个函数内部或者一个代码块中都可以形成块级作用域,声明后的变量在指定块级作用域外无法访问。块级作用域的特点是声明的变量不会被提升到块顶部,禁止重复声明。
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到就会从父级执行还是那个下文的变量对象找,一直查找到全局上下文的变量对象,这样有多个执行上下文的变量对象构成的 链表 就被成为作用域链

8、执行上下文

JS引擎要执行一段代码之前,JS解析器会创建一个 执行环境,会将代码运行时所需要的变量,函数,this等数据准备好。这个执行环境就是执行上下文。
一共有全局执行上下文;函数执行上下文:每当一个函数被调用时,就会为该函数创建一个新的上下文;eval函数执行上下文。
JS创建了 执行上下文栈 来管理 执行上下文,遵循后进先去的原则。
创建执行上下文分为两个阶段。代码在执行之前会绑定this;创建作用域链;创建变量对象。变量对象会包括所有的形参,函数声明,变量声明,然后进入执行阶段。

9、闭包

闭包是指能够访问自由变量的函数,自由变量是指能够在函数中使用但既不是函数参数又不是函数内部变量的变量。当一个内部函数存在外部作用域的引用就会形成闭包。闭包中的变量是储存在堆内存。闭包可以保护函数的私有变量不受外部干扰,实现方法和属性的私有化。比较常见的例如 一个函数return一个函数,函数作为参数,回调函数,IIFE(自执行函数),防抖节流等都会使用到闭包。闭包容易导致内存泄漏。

10、垃圾回收机制

程序工作中会产生很多程序用不到的内存或者之前用过了,以后不再会用的内存空间,回收销毁这些数据释放内存就是垃圾回收。程序运行需要内存,只要程序提出要求,操作系统必须提供内存,对于持续运行的服务进程,必须及时释放内存,否则内存占用越来越高,可能会影响系统性能,或者导致进程崩溃。
(1)标记清除法:一共有两个阶段,标记阶段的时候会给所有活动对象做上标记,清除阶段就会把没有标记的非活动对象给销毁。

垃圾收集器再运行时会给内存中的所有变量都加一个标记,假设内存中的所有对象都是垃圾,全部标记为0;
然后从根对象开始遍历,把不是垃圾的标记为1;
清理所有标记为0的对象,销毁所占内存;
最后把所有内存中的对象标记就改为0,等待下一个轮回。

标记清除法实现很简单,但是再清除之后,剩余对象的内存位置是不变得,会导致空闲内存空间是不连续的,也就是内存碎片化,这样再分配内存时速度很慢,内存过大的对象还很有可能找不到合适的内存块。

(2)引用计数法: 如果一个对象没有其他对象引用到它,那么该对象就会被垃圾回收机制回收。

当声明一个变量并且将一个引用类型赋值给变量的时候这个值的引用次数就为1;
如果同一个值又被赋值给另一个对象,那么引用次数在加1;
如果该变量的值被其他的覆盖了,则次数减1;
当这个值得引用次数为0得时候,说明没有变量再使用,这个值就会被回收;

引用计数法比标记清除法清晰,再清除对象得时候只需要关注引用时计数就可以。但是这个计数器需要占很大的位置,而且也没法解决循环引用无法回收得问题

11、内存泄漏

引擎中有垃圾回收机制,主要针对一些程序中不再使用到的对象,对其清理回收释放掉内存。但是如果一个对象不在用到,却没有被及时回收时就称之为内存泄漏。

常见的内存泄漏
不正当的闭包:用完以后赋值为null。  
隐式全局变量:垃圾回收机制很难判断全局变量不在被需要,所以全局变量很少被回收。用完以后赋值为null。  
游离DOM引用:进行dom操作时会使用变量缓存这个dom,但是移除节点的时候,应该同步释放缓存的引用,否则无法释放。  
定时器。  
事件监听器。  
未清理的console,浏览器会保存我们输出对象的信息数据引用。

前端关于内存方面主要有三个问题:

  • 内存泄漏:对象已经不在使用但是没有被回收,内存没有被释放
  • 内存膨胀:短时间内内存占用急速上升到一个峰值
  • 频繁GC:一般出现再频繁使用大的零食变量导致新生代空间被装满的速度极快,而每次新生代装满时就会触发GC。频繁GC同样会导致页面卡顿。不要使用太多的临时变量。
12、var、let、const 的区别

首先是var和let的区别,第一个区别是作用域不同,var是函数作用域,let是块级作用域。比如再for循环中用var定义的变量可以再for外部访问,而用let定义的变量再for循环外部访问不到。 第二个不同点是变量提升。第三个不同点是let不可以重复定义,会报错;而var可以重复定义。
const和let的特点差不多,但也有区别。const不可以重新赋值。let可以重新赋值,但也只是在string,number,boolean这种基本类型。当const定义的变量是对象时,可以对 对象的键值新增或赋值,当变量是数组时,也可以进行push,pop等操作。为了让变量不变也可以用object.freeze(变量名),但也只能单层冻结,对里层嵌套的对象或数组失效。

13、深浅拷贝

首先需要知道js的数据类型分为基本类型(string,number,boolean,null,undefined,symbol)和引用类型(Object,Array,Date,Function等)。基本数据直接存储在栈中,而引用类型的数据在栈中保存一个地址,该地址会指向堆中保存的真实数据。
浅拷贝在进行复制的时候如果是基本类型就把数据给新变量,如果是引用数据类型,给新变量的值是存在栈中的地址,如果改变新变量中的数据,那么也会影响到原数据。
Object.assign():是es6中新增的方法,可以用于多个对象合并,也可以用于对象的浅拷贝,该方法第一个参数是拷贝的目标对象,后面的参数是拷贝的数据来源,可以是多个对象。但是需要注意,该方法不会拷贝对象的继承属性,不拷贝不可枚举属性,可以拷贝Symbol类型的数据; 扩展运算符,slice和concat
深拷贝会在堆中重新开辟内存空间,将原对象完全复制过来,这两个对象相互独立,互不影响。
JSON.stringify():把对象序列化为json的字符串,并将对象内容转为字符串,再利用parse转为对象。但是该方法会忽略undefined,symbol,不可枚举对象,对象的原型链等。第二种方法为递归复制。还有函数库 lodash

14、实现深浅拷贝

Object.assign : 拷贝所有的属性值到新的对象中,如果属性值是对象的话,拷贝的是地址,所以并不是深拷贝

let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

通过展开运算符 ... 来实现 浅拷贝

let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1

原型链实现 深拷贝

// 深拷贝
let obj1 = {
    name : '牛小六',
    arr : [1,[2,3],4],
};
let obj4=deepClone(obj1)
obj4.name = "小六";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 实现深拷贝的方法
function deepClone(obj) {
    if (obj === null) return obj; 
    if (obj instanceof Date) return new Date(obj);
    if (obj instanceof RegExp) return new RegExp(obj);
    if (typeof obj !== "object") return obj;
    let cloneObj = new obj.constructor();
    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {//方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性
        // 实现一个递归拷贝
        cloneObj[key] = deepClone(obj[key]);
      }
    }
    return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '牛小六', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '小六', arr: [ 1, [ 5, 6, 7 ], 4 ] }
15、防抖节流

防抖(debounce)的基本概念 就是在 一段时间只执行一次。
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
setTimeout和clearTimeout其实就是防抖的主要要素了。
在我们的日常开发中其实也经常用到,比如我们调整页面的大小,验证表单某个字段是否重复时发生请求次数控制,防止表单多次提交等。

节流(throttle)的最基本的概念,就是间隔一段时间执行一次。
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
在我们的日常开发中其实也经常用到,比如我们滚动鼠标滚轮监听滚动条位置,防止按钮多次点击等。

16、call、appply、bind区别

(1)三者都是用来改变函数的this指向
(2)三者的第一个参数都是this指向的对象
(3)bind是返回一个绑定函数 可稍后执行,call、apply是 立即调用
(4)三者都可以给定参数传递
(5)call给定参数需要将参数全部列出,apply给定参数数组,bind创建一个函数,使这个函数不论怎么调用都有同样的this值

17、this

this 是 执行上下文中的 一个属性,它 指向 最后一次调用 这个方法 的 对象。
this的指向,是在 函数 被调用的时候 确定的,也就是 执行上下文被创建时 确定的。
在函数执行过程中,this一旦被确定,就不可更改了。
如果函数 作为一个对象的 方法 调用时,this 指向这个对象.(obj.fn)
如果函数独立调用,那么该函数内部的this,则指向undefined。但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。(var test = foo.getA; console.log(test());//独立调用 )

18、ES6 新特性

let const和var
解构,快速提取数组/对象中的元素
模板字符串
参数默认值,可以使用...reset形式设置剩余形参,支持无限参数
展开数组
箭头函数
promise:解决回调地狱
Map + Set + Weakmap + Weakset Object.assign flat:扁平化数组(ES10)

ECMAScript版本发布时间新增特性
ECMAScript 2009(ES5)2009年11月扩展了Object、Array、Function的功能等
ECMAScript 2015(ES6)2015年6月类,模块化,箭头函数,函数参数默认值等
ECMAScript 2016(ES7)2016年3月includes,指数操作符(**)
ECMAScript 2017(ES8)2017年6月async/await,Object.values(),Object.entries(),String padding等
ECMAScript 2018(ES9)2018年6月Promise.prototype.finally等
ECMAScript 2019(ES10)2019年6月Array.flat(),Array.flatMap()等
ECMAScript 2010(ES11)2020年6月可选链操作符( ?. )等
19、箭头函数与普通函数的区别

箭头函数没有自己的this,只能通过作用域链来向上查找离自己最近的那个函数的this。
箭头函数不能作为constructor,因此不可以通过new来调用
箭头函数没有argument属性,通过rest可以获取
箭头函数不能直接使用call,apply来改变this
箭头函数不能使用yield
箭头函数是匿名函数。

20、数据类型的判断

JS种一共有8种数据类型的数据,7种基本类型,string number boolean null undefined bigInt symbol以及一种引用类型。
基本数据类型:按值访问,可操作保存在变量中的实际值。值被保存在栈种; 引用类型:在栈中保存地址,实际是保存在堆中。

typeof: 基本类型除了null返回object之外,引用类型除了function其余全是object
instanceof:只能用来判断变量得原型链是否有构造函数的prototype属性(两个对象是不是属于原型链关系),不适合判断基础类型。
constructor:每一个实例对象都可以通过constructor来访问它的构造函数。但是 nullundefined是无效的对象,因此不能判断。
Object.prototype.toString.call():方法返回对象的类型字符串。因为实列对象有可能会自定义toString方法会覆盖掉本身的object.prototype上的toString,所以在使用的时候,最好使用call改变this指向。

判断数组的五种方法:
instanceof: instanceof arr === Array
Array.isArray() Array.isArray(arr)
Object.prototype.isPrototypeOf Object.prototype.isPrototypeOf(arr,Array.prototype)判断两个对象的原型是不是一样
Object.getPrototypeOf bject.getPrototypeOf(arr) === Array.prototype
Object.prototype.toString.call Object.prototype.toString.call(arr) === '[object Array]'

21、数组去重

首先是双层for循环:先定义一个包含原始数组第一个元素的数组,然后遍历原始数组,将原始数组中的每个元素与新数组中的每个元素进行对比,如果不重复则添加到新数组中,然后返回新数组。但是如果数组长度很大的话效率会很低。
Array.filter() 加 indexOf:利用indexof检测元素在数组中第一次出现的位置是否和元素现在得位置相等,如果不等则说明该元素是重复元素,然后理由filter过滤。
Array.sort()加一行遍历冒泡(相邻元素去重):使用数组得sort排序方法,然后根据排序后得结果进行遍历及相邻元素比对,如果相等则跳过该元素,直到遍历结束。
ES6中得Set去重:ES6提供新的数据结构Set,Set结构得一个特性就是成员值都是唯一得,没有重复的值。

fucntion uniqu (){
    retuern Array.form(new Set(array))
}

//简写
fucntion uniqu (){
    retuern [...new Set(array)]
}

//在简写
let uniqu = (a) => [...new Set(a)]

reduce实现对象去重:值得注意的是初始值得是一个空数组,不然没法push。

var resources = [ 
{ name: "张三", age: "18" }, 
{ name: "张三", age: "19" }, 
{ name: "张三", age: "20" }, 
{ name: "李四", age: "19" }, 
{ name: "王五", age: "20" }, 
{ name: "赵六", age: "21" } ] 
var temp = {};
resources = resources.reduce((prev, curv) => { 
    // 如果临时对象中有这个名字,什么都不做 
    if (temp[curv.name]) {
    } // 如果临时对象没有就把这个名字加进去,同时把当前的这个对象加入到prev中
    else { 
        temp[curv.name] = true; prev.push(curv);
     }
     return prev
}, []); 
console.log("结果", resources);

以上写法利用了object的建重复会覆盖的特性。最终这个temp会变为
{ 张三: true,: true,五: true,赵六: true}

22、冒泡与捕获,事件委托

(1)DOM事件流
事件流描述的是从页面中接收事件的顺序,当一个元素产生事件的时候,该事件会在dom树元素节点之间按照特定的顺序去传播,传播路劲的每个节点都会收到这个事件。包括三个阶段:

  • 事件捕获:事件从最不精准的对象(document)开始触发,然后到最精准的节点。
  • 事件目标:当到达目标元素后,执行目标元素该事件相应的处理函数。
  • 事件冒泡:事件按照从最特定的事件目标到最外层的document对象的顺序触发,当一个元素接收到事件的时候会把他接收到的事件传给自己的父级,一直到window。 (2)事件委托
    不给每个子节点单独设置事件监听器,而是设置在其父节点上,然后利用冒泡原理设置每个子节点。例如给ul注册点击事件,然后利用事件对象的target来找到当前点击的li,然后事件冒泡到ul上。
    好处:在js中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的操作dom,那么引起浏览器重绘和回流的可能会越多。每个事件处理函数都需要占用内存,数量越多内存占用越大。运用事件委托就可以将所有的操作当到js程序里面,只对父级这一个对象进行操作,与dom操作就只需要交互一次,这样就可以减少与dom交互的次数,提高性能。
    (3)DOM0级与DOM2级
    DOM0级:元素.on事件类型=处理函数;只能绑定一个事件处理函数,只能绑定到冒泡阶段;
    DOM2级:元素.addEventListener(事件类型,处理函数,dom流阶段);可以绑定多个事件处理函数,可以绑定到捕获阶段。

Raect

1、react事件机制

首先我们需要知道React并不是将事件绑定到了真实的dom上,而是将事件绑定在应用顶部容器的container上,将事件统一管理交由真正的处理函数运行。这样做的好处就是抹平了不同浏览器之间对事件存在的不同兼容性,减少了内存的消耗,还可以在组件挂载和销毁时同一订阅和移出事件
react模拟了一套:事件捕获-> 事件源->事件冒泡。当开发者正常给React绑定事件比如onClick,onChange等都默认在模拟冒泡阶段执行。想要在捕获阶段执行可以将事件后面添加Capture后缀。可以通过e.stopPropagation()来阻止冒泡。但是阻止默认行为却与原生有一些区别。原生事件使用e.perverntDefault() return false来阻止默认行为。但是react由于给元素的事件并不是真正的事件处理函数,所以导致return fasle方法在react中完全失去作用。可以使用e.perventDefault()阻止。

合成事件:react中,元素绑定的事件并不是原生事件,而是react合成的事件,例如onClick是由click合成,onChange是由blur,change,focus等多个事件合成。这些事件会被保存在对应的dom元素类型fiber对象的memoizedProps属性上。
2、对React-Fiber的理解,它解决了什么问题?

fiber诞生于React16版本,在react中是最小粒度的执行单元,在遍历更新每个节点的时候都是采用虚拟dom,所以可以理解为fiber就是react的虚拟dom。在15以前的版本中,react对于虚拟dom是采用递归方式遍历更新的,一次更新就会从应用根部递归更新,递归一旦开始就无法中止,随着项目越来越复杂,层级嵌套很深,导致更新事件变长,给前端交互上的体验就是页面卡顿,为了解决这个问题,所以引入了fiber。
更新fiber的过程被称作调和,每个fiber都可以作为一个执行单元来处理,所以每个fiber可以根据自身的过期时间来判断是否还有空间时间执行更新,如果没有时间更新,就会把主动权交给浏览器去做渲染,做重排,重绘等,这样能给用户感觉上不卡。等浏览器有空余时间,通过调度,再次恢复执行单元上来,这样就中断了渲染,提高用户体验。

3、React中的受控组件与非受控组件

受控:在HTML中,表单元素的标签、、等的值改变通常是根据用户输入进行更新。 在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 进行更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为受控组件。 比如,给表单元素input绑定一个onChange事件,当input状态发生变化时就会触发onChange事件,从而更新组件的state。

非受控:非受控组件指的是,表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值。 在非受控组件中,可以使用一个ref来从DOM获得表单值,而不是为每个状态编写一个事件处理。

4、React中的refs的作用是什么?有哪些引用场景?

refs提供了一种方式用于访问render方法中创建的react元素或者dom节点。一般使用react.createRef或者react.useRef来创建ref对象。类组件可以将ref对象绑定到类组件实例上,而函数组件每次更新所有变量都会重新声明,所以hook和函数组件对应的fiber建立起关联,将useRef产生的ref对象挂到函数组件对应的fiber,函数组件每次执行,只要组件不销毁,函数组件对应的fiber对象就一直存在,所以ref对象就可以被保存下来,类组件获取ref可以有ref字符串(this.refs.myRef),ref函数(this.myRef),ref对象(this.myRef.current)等三种方式。可以用来实现组件通信,函数组件缓存数据等。

5、setState触发之后发生了什么?(setState的更新流程)

首先setState会产生当前更新的优先级,接下来react会从fiber Root根部fiber向下调和子节点,再调和阶段将对比发生更新的地方,更新对比过期时间,找到发生更新的组件,合并state触发render函数得到新的ui视图层,染成render阶段。然后进入commit阶段,替换真实DOM。最后还是在commit阶段会执行setState中的回调函数。

6、setState调用原理(setState是同步的还是异步的)

setState本身代码的执行肯定是同步的,这里的异步是指多个state汇合成到一起进行批量更新。同步还是异步取决于它被调用的环境

  1. 如果setState是在react能够精致的范围被调用那么就是异步的。比如合成事件处理函数、生命周期函数,此时会进行批量更新
  2. 如果setState在原生js控制的范围被调用那就是同步的,比如原生事件处理函数,定时器回调函数,Ajax回调函数。
7、react的生命周期
  1. 初始化阶段
    consturctor执行:初始化state,绑定this。
    getDerivedStateFromProps执行:直接从ctor类上直接绑定静态方法,传入props,state
    componentWillMount执行(废弃)
    render函数执行
    componentDidMount执行:会在组件挂载后立即调用,可以操作dom,订阅,添加监听等。
  2. 更新阶段 componentWillReceiveProps(废弃)
    getDerivedStateFromProps
    shouldComponentUpdate
    componentWillUpdate(废弃)
    render
    getSnapShotBeforeUpdate
    componentDidUpdate
  3. 卸载阶段
    componentWillUnmount:清除定时器,取消订阅等
8、为什么要废弃三个生命周期,原因是什么

在react16中核心算法由diff改变为了fiber,而在fiber框架中掉和过程会多次执行这三个will周期,如果在从周期中去setState或者操作dom的话,会触发很多次重绘,影响性能还有可能会导致数据错乱。

9、对Redux的理解

Redux是一个用来管理数据状态和ui状态的js应用工具,在React中ui以组件的形式来搭建,组件之间可以相互嵌套,但是react却是单向数据流,数据只能自上而下。当项目变得庞大之后管理数据以及回调函数等变得越来越多,状态的管理变得很困难。Redux提供了一个store仓库,组件通过dispatch将state直接传入store,不用通过其他组件。所有组件都可以从store中回去到所需要的state,这使得状态管理更清晰。redux是一个数据仓库把数据统一保存起来再隔离数据与ui的同时,负责处理他们的绑定关系

10、Redux的工作流程

react组件可以通过订阅来获取数据,渲染ui组件等。由于react中的数据流动是单向的。react组件不可以直接修改store中的数据。所以组件必须通过dispatch发送指令到action creator去生成一个action。当action到达store之后会先经过数据的预处理比如对数据的异步处理。处理完成以后store会将旧的state与action一起发送给reducer。reducer根据action中的指令去更新state数据,更新结束以后会将新的state返回给store,store会立马将新的数据推送订阅自己的组件,然后组件更新渲染ui。

11、Redux的三大原则

单一数据源:所有的数据都保存在单一的store中
state只读:不能直接对store进行修改,只能用新的store替换旧的store
使用纯函数来执行修改

12、Redux和mobx的区别
  1. redux将数据保存到单一的store中,而mobx将数据保存到分散的多个store中
  2. redux需要手动处理变化后的操作,mobx使用observable保存数据,数据变化后可以自动处理响应操作。
  3. redux使用不可变状态,操作时只读的使用纯函数。mobx的状态是可以变得,可以直接进行修改。
  4. mobx中有很多抽象与封装调式较为困难,而redux提供能够进行时间回溯的开发工具,调式更简单。
13、为什么会有hooks

在hooks出现之前类组件提供了完整的状态管理和生命周期控制,承担复杂的业务逻辑,而函数组件则是纯粹的从数据到视图的映射。但是随着项目的体积增加,功能增多类组件则出现了以下缺点:

  • this管理,容易引入难以追踪的bug
  • 生命周期的划分并不符合“内聚性”原则,例如定义定时器和清理定时器不在同一个生命周期里
  • 组件之间复用状态难 增加hooks后函数组件可以像类组件一样可以进行状态管理,能获取ref做数据缓存,解决逻辑复用难得问题,使用函数式编程。
14、多个react-hooks用什么记录每个hook的顺序?为什么不能在条件语句中声明hooks?hooks的声明为什么要在顶部?

函数组件fiber使用memoizedState保存hooks信息,react通过赋予current不同的hooks对象达到监控hooks是否在函数组件内部调用。
多个react-hooks使用 单向链表 的形式将保存了hook信息的hook对象串联起来,并用memoizedState将hooks链表存放起来。 如果一旦在条件语句中声明hooks,在下次函数组件更新的时候,hooks链表结构将会被破坏,current树的momoizedState缓存的hooks信息与当前workInProgress不一致,导致发生异常。

15、useState更改state流程
  1. 用户每次调用dispatchAction都会创建一个update,然后把它放入待更新pending队列中。
  2. 然后判断如果当前fiber正在更新,那么久不需要更新了
  3. 反之说明当前fiber没有更任务,那么就会拿上次state和本次state进行浅对比,如果相同直接退出更新。不同就更新调度任务。
16、useEffect与useLayoutEffect的区别

useEffect与useLayoutEffect都是用来处理副作用,例如改变dom、设置订阅、操作定时器等。但是useEffct是被异步调用,不会阻塞浏览器;而useLayoutEffect会在所有的dom变更之后同步调用,主要用于处理dom,调正样式,避免页面闪烁等问题。但是由于是同步调用会阻塞浏览器。

17、虚拟Dom,做了什么,为什么要用虚拟dom

从本质上来说虚拟DOM是一个javaScript对象,通过对象的方式来表示DOM结构。将页面的状态抽象为JS对象的形式,配合不同的渲染工具,是跨平台渲染成为可能。通过事务处理机制将多次DOM修改的结果一次性的更新到页面上,从而减少修改DOM的重绘重排次数,提高渲染性能。
第一点虚拟dom可以保证性能的下限,在不进行手动优化的情况下,提供过得去的性能。相比真实操作dom需要重建所有的dom元素,虚拟dom会将真实的dom结构以js对象的形式描述,通过diff算法对比新旧虚拟dom找到需要更新的dom。第二点就是跨平台,因为虚拟dom的本质是js对象可以很方便的进行跨平台操作。

18、React Diff算法

diff算法就是虚拟dom树发生变化后,生成dom树更新补丁的方式。通过对比新旧两个虚拟dom树的变更差异,将更新补丁作用于真实dom,以最小成本完成视图更新。
流程:真实dom映射为虚拟dom,虚拟dom发生变化后根据差异计算生成patch,这个patch是一个结构化数据包含增加、更新、移除等信息。根据patch更新真实dom,反馈到用户的界面上。
策略:1.忽略节点跨层级操作场景,提升对比效率(同层级)。2.组件的class一致则默认为相似的树结构(同组件)。3.同一层级的子节点可以通过标记key的方式 进行列表对比(同key值比较)

19、React key

作用:key是react用于追踪哪些列表中元素被修改、添加或者移除的辅助标记。在react diff算法中react会借助元素的key值来判断该元素是新创建的还是被移动而来的,从而减少不必要的元素重渲染。在开发过程中应该保证某个元素的key在同级元素中具有唯一性。
注意:key值一定要和具体元素一一对应,尽量不要使用index去作为可以,不要在render的是用随机数或者其他操作给元素添加上不稳定的key,这样造成的性能开销比不加key的情况更糟糕。

20、React与vue的异同

相似之处: 使用虚拟dom提高重绘性能; 都有props概念允许组件之间的数据传递; 都鼓励组件化,将应用拆分为一个个功能明确的模块,提高复用性。

不同点: vue支持双向数据绑定,react提倡单向数据流; vue在使用虚拟dom的时候会跟踪每个组件的依赖关系,不需要重新渲染整个组件树。每当应用的状态改变react会全部将子组件重新渲染,但是也提供了shouldComponentUpdate方法进行控制; vue的模板很接近html元素,react使用JSX语法; vue通过getter和setter以及函数劫持 可以精确知道数据变化,react默认通过比较引用(diff)的方式进行,可能会导致大量不必要的dom重新渲染; 框架本质不同,Vue本质是MVVM框架,由MVC发展而来;React是前端组件化框架,由后端组件化发展而来。

21、React与vue的优缺点

1、React的优缺点

优点:

  • 速度快:在UI渲染过程中,React通过在虚拟DOM中的微操作来实现对实际DOM的局部更新
  • 跨浏览器兼容:虚拟DOM帮助我们解决了跨浏览器问题,它为我们提供了标准化的API,甚至在IE8中都是没问题的。
  • 模块化:为你程序编写独立的模块化UI组件,这样当某个或某些组件出现问题时,可以方便地进行隔离。
  • 单向数据流:Flux是一个用于在JavaScript应用中创建单向数据层的架构,它随着React视图库的开发而被Facebook概念化。
  • 同构、纯粹的javascript(利SEO):因为搜索引擎的爬虫程序依赖的是服务端响应而不是JavaScript的执行,预渲染你的应用有助于搜索引擎优化。
  • 兼容性好:比如使用RequireJS来加载和打包,而Browserify和Webpack适用于构建大型应用。它们使得那些艰难的任务不再让人望而生畏。

缺点:

  • React本身只是 MVC 中的V(视图)而已,并不是一个完整的框架,所以如果是大型项目想要一套完整的框架的话,基本都需要加上ReactRouter和Flux才能写大型应用。
  • 陡峭的学习曲线:由于复杂的设置过程,属性,功能和结构,它需要深入的知识来构建应用程序。 React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设Instagram 的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

2、Vue.js的优缺点

优点:

  • 简单:官方文档很清晰,比 Angular 简单易
  • 快速:异步批处理方式更新 DOM
  • 组合:用解耦的、可复用的组件组合你的应用程
  • 紧凑:~18kb min+gzip,且无依赖。
  • 强大:表达式 & 无需声明依赖的可推导属性 (computed properties)。
  • 对模块友好:可以通过 NPM、Bower 或 Duo 安装,不强迫你所有的代码都遵循 Angular 的各种规定,使用场景更加灵活。

缺点:

  • 新生儿:Vue.js是一个新的项目,没有angular那么成熟
  • 影响度不是很大:google了一下,有关于Vue.js多样性或者说丰富性少于其他一些有名的。
  • 不支持IE8
22、react是什么?

ReactJS是一套JavaScript Web库,由Facebook打造而成且主要用于构建高性能及响应式用户界面。React负责解决其它javascript框架所面对的一大常见难题,即对大规模数据集的处理。能够使用虚拟DOM并在发生变更时利用补丁安装机制只对DOM中的dirty部分进行重新渲染,React得以实现远超其它框架的速度表现。

23、React 特点
  • 声明式设计:React采用声明范式,可以轻松描述应用。
  • 高效:React通过对DOM的模拟,最大限度地减少与DOM的交互
  • 灵活:React可以与已知的库或框架很好地配合。
  • JSX:JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
  • 组件:通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。-
  • 单向响应的数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。
  • 掌握 React 不仅可以帮你应对前端应用开发,而且它的编程思想还可以应用到 React Native 原生 App 开发和服务器端渲染的后端开发。
24、函数组件与类组件的区别

传参方式:函数组件本质是一个js函数因此传参方式与普通函数一致,类组件的传参需要从props解构出来 处理state:类组件有自己的state可以定义状态,但是函数组件没有 生命周期:类组件有生命周期,函数组件没有 渲染jsx的方法:类组件需要在render方法中return,函数组件直接return

25、HOC
  • 是 React 中用于复用组件逻辑的一种高级技巧
  • 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式(一种组件的设计模式)
  • 接受一个组件和额外的参数(如果需要),返回一个新的组件
  • 纯函数,没有副作用

优点∶ 逻辑复用、不影响被包裹组件的内部逻辑。

缺点∶ HOC传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖

使用场景

  • 代码复用,逻辑抽象
  • 渲染劫持
  • state 抽象和更改
  • props 更改
26、Flux

Flux是Facebook用户建立客户端Web应用的前端架构, 它通过利用一个单向的数据流补充了React的组合视图组件,这更是一种模式而非正式框架,你能够无需许多新代码情况下立即开始使用Flux。Flux应用有三个主要部分:Dispatcher调度 、存储Store和视图View(React 组件)。

27、常用HOOK API
  • const [state, setState] = useState(initialState);

setState 返回一个 state,以及更新 state 的函数。 在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。 setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。

  • useEffect(didUpdate);

React.useEffect(() => {...... }, []) 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。 依赖项

  • const value = useContext(MyContext);

const ThemeContext = React.createContext(themes.light);

  • const [state, dispatch] = useReducer(reducer, initialArg, init);

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

  • useCallback()

const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b],); 返回一个 memoized 回调函数。 把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。 当你把回调函数传递给 经过优化的 并使用引用相等性 去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

  • useMemo()

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); 返回一个 memoized 值。 把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。 传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。 如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。 我们可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。

  • useRef()

const refContainer = useRef(initialValue); useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。 返回的 ref 对象在组件的整个生命周期内保持不变。 useRef() 和自建一个 {current: ...} 对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象

  • useLayoutEffect

useLayoutEffect 的函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。 可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。 尽可能使用标准的 useEffect 以避免阻塞视觉更新。

浏览器与网络

1、HTTP缓存

HTTP缓存根据失效时间划分为强缓存与协商缓存;
强缓存主要由Expires和cache-control两种。Expires给出了缓存过期的绝对时间,再过期之前浏览器可以直接从缓存中读取数据,但是由于是一个绝对时间,会受到客户端时间的影响而变得不精准。cache-control用于控制缓存的行为,常用的有max-age,s-maxage,public/private、no cache/no store等。
max-age给出了缓存过期的相对时间,单位为秒数。
s-maxage只适用于公共缓存服务器,当使用s-maxage后公共缓存服务器会直接忽略expires和max-age。
public表示该资源可以被任务节点缓存(包括客户端和代理服务器)。private表示该资源只提供给客户端缓存,代理服务器不会缓存。
no cache再请求首部中使用时表示告知代理服务器不直接使用缓存,要求向源服务器发起请求。再响应首部中被使用时表示客户端可以缓存资源吗,但是每次使用缓存资源之前都必须向服务器确认资源的有效性。
no store再响应首部中被使用时才是真正的不进行任何缓存。
协商缓存有两组,last-Modified和if-Modified-Since:last-Modified表示资源的最后修改时间,当浏览器第一次接受到服务器返回资源的last-Modified值后将其存储起来,并在下次访问该资源时通过携带if-Modified-Since请求首部发送给服务器验证该资源有没有过期。Etag和if-None-Match:Etag用于代表资源的唯一性标识,服务器会按照指定的规则生成资源的标识,当资源发生变化时,Etag的表示也会更新。同上所述,浏览器再第一次接收到Etag后会将其值存储起来,并在下次访问该资源的时候携带if-none-match请求首部发送给服务器验证该资源有没有过期。

2、同源策略与跨域

一般来说浏览器安全可以分为:web页面安全、浏览器网络安全、浏览器系统安全。而web页面安全主要就是同源策略限制。协议、域名、端口号一直才叫同源。浏览器默认同源之前的站点是可以相互访问资源与操作dom的,不同源之间想要操作dom与访问资源则需要添加一些安全策略的限制,也就是常说的同源策略。同源策略主要限制:Dom层面、数据层面、网络层面的限制。不可以相互访问与操作dom,不可以获取cookie、web Storage、indexDB等数据,不能发送请求。由于同源策略的存在造成了跨域问题。
跨域的解决方案

  • CORS(跨域资源共享) cors通信需要浏览器和服务端都支持,只要服务器支持就可以跨域通信。cors请求默认不是包含cookie和http认证信息的。分为简单请求和非简单请求。
    简单请求的请求方法为head、get、post三者之一,服务器需要设置access-control-allow-origin字段。
    非简单请求方法又put、delete请求,或者content-type为application/json。非简单请求会在正是请求之前发送一次OPTIONS类型的查询请求,成为预检请求,询问服务器是否支持网页所在域名的请求,以及可以使用哪些头信息字段,只有收到肯定的答复才会发起正式的请求。多次预检请求会损耗性能,可以再响应头中添加access-control-max-age表示预检请求的返回结果可以被缓存起来。
  • Nginx代理 配置一个代理服务器向服务器请求,再将数据返回给客户端,实质与cors跨域原理是一样的,需要配置请求响应头access-control-allow-origin字段。
  • node + express代理
  • JSONP 通过添加一个script标签,向服务器请求JSON数据。不过只支持get请求而且不安全,可能会遇到XSS攻击。
什么是跨域:跨域是浏览器为了请求安全而引入的基础同源策略的安全特性。当页面和请求的协议、主机名、端口三者有一个不同即为跨域。跨域是浏览器端的限制,不会影响服务端。
为什么会有跨域:同源策略是浏览器一个非常重要的安全策略,分别从dom层面、数据层面、网络层面限定不可以相互访问与操作dom、不可以获取cookie、web Storage、indexDB等数据、不可以进行网络请求,从而减少了页面被攻击的可能性。

解决跨域:
cors后端设置access-control-allow-origin即可。cors分为简单请求与预检请求,当请求类型为get、post、head,请求头为accept、accept-luange、content-type、accept-content的即为简单请求。当请求方式为delete,put,content-type为application/json的即为预检请求,预检请求会先发送一个OPTIONS请求,询问服务器是否支持网页所在域名请求。只有得到ok的相应后才会发送正式的请求。
nginx代理:分为正向代理与反向代理。代理客户端与服务器进行资料交流的被称之为正向代理,代理服务端与客户端进行资料交流的被称之为反向代理。
jsonp:主要是利用了script标签不受同源策略限制的特性实现跨域。通过添加一个script标签向服务器获取json数据,但是只支持get请求,而且容易受到xss攻击
3、浏览器安全与攻击
  • XSS攻击:代码注入攻击,通过恶意注入脚本在浏览器运行,然后盗取用户信息。网站没有过滤恶意代码,与正常的代码混在一起执行,可能会造成页面数据或用户信息被盗取,修改dom,监听用户行为,例如监听键盘事件窃取账号密码,流量被劫持到其他网站等情况。
    防范:对输入框的内容进行过滤或者使用转义符进行转码;可以通过meta标签或者设置http头部开启白名单告诉浏览器哪些外部资源可以加载执行;在cookie信息中添加httpOnly,告诉浏览器在保存cookie切不对客户端脚本开放访问权限;使用验证码,避免脚本伪装用户执行操作。
  • SCRF攻击:跨站请求伪造攻击,主要是利用用户的登录状态发起跨站请求。发起csrf攻击有三个条件,一是目标网站有一定的csrf漏洞,二是用户登录过目标网站,并且浏览器保存了登录状态。三是用户主动打开第三方网站。 防范:在cookie信息中添加sameSite属性;验证请求来源,服务器根据http请求中的origin或者referer属性判断是否为允许访问的站点。token验证;双重验证cookie;
  • 中间人攻击:在http传输过程中容易被中间人窃取、伪造、串改这种攻击方式就是中间人攻击。 防范:利用https安全层对数据进行加解密操作,以保证数据安全。
4、状态码

2开头 (请求成功)表示成功处理了请求的状态代码。

  • 200 (成功) 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。
  • 201 (已创建) 请求成功并且服务器创建了新的资源。
  • 202 (已接受) 服务器已接受请求,但尚未处理。
  • 203 (非授权信息) 服务器已成功处理了请求,但返回的信息可能来自另一来源。
  • 204 (无内容) 服务器成功处理了请求,但没有返回任何内容。
  • 205 (重置内容) 服务器成功处理了请求,但没有返回任何内容。
  • 206 (部分内容) 服务器成功处理了部分 GET 请求。

3开头 (请求被重定向)表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。

  • 300 (多种选择) 针对请求,服务器可执行多种操作。 服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。
  • 301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。
  • 302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。
  • 303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。
  • 304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此响应时,不会返回网页内容。
  • 305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返回此响应,还表示请求者应使用代理。
  • 307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。

4开头 (请求错误)这些状态代码表示请求可能出错,妨碍了服务器的处理。

  • 400 (错误请求) 服务器不理解请求的语法。
  • 401 (未授权) 请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。
  • 403 (禁止) 服务器拒绝请求。
  • 404 (未找到) 服务器找不到请求的网页。
  • 405 (方法禁用) 禁用请求中指定的方法。
  • 406 (不接受) 无法使用请求的内容特性响应请求的网页。
  • 407 (需要代理授权) 此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。
  • 408 (请求超时) 服务器等候请求时发生超时。
  • 409 (冲突) 服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。
  • 410 (已删除) 如果请求的资源已永久删除,服务器就会返回此响应。
  • 411 (需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
  • 412 (未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
  • 413 (请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
  • 414 (请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
  • 415 (不支持的媒体类型) 请求的格式不受请求页面的支持。
  • 416 (请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态代码。
  • 417 (未满足期望值) 服务器未满足"期望"请求标头字段的要求。

5开头(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

  • 500 (服务器内部错误) 服务器遇到错误,无法完成请求。
  • 501 (尚未实施) 服务器不具备完成请求的功能。例如,服务器无法识别请求方法时可能会返回此代码。
  • 502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。
  • 503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。
  • 504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
  • 505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。
5、从输入url到页面展示发生了什么

浏览器是多线程的,浏览器主进程主要控制页面创建、销毁、网络资源管理等;此外还有第三方插件进程;GPU进程;浏览器渲染进程。如果输入的是非url结构字符串的话,浏览器就会使用默认的搜索引擎去搜索该字符串

(1)DNS域名解析出协议、主机、端口等信息并构建一个http请求

(2)tcp三次握手建立链接

第三次握手的必要性:三次握手其实由tcp自身【可靠传输】的特点决定的,客户端与服务端要进行可靠传输,那么就必须保证双方的接收与发送能力。第一次握手可以确认客户端的发送能力,第二次握手可以确认服务端的接收与发送能力,第三次握手确认客户端的接收能力,不然容易出现丢包的现象。  
第三次握手是可以携带数据的,因为经过前两次握手可以知道服务端的接受与发送能力,链接已经建立了。所以i可以携带数据,第一次不可以是因为如果有人恶意在Syn中添加大量的数据,不管服务端的接受和发送能力,会花费服务端内存与时间。

(3)浏览器向服务器发送http请求

(4)服务器处理请求并返回http报文

(5)浏览器解析渲染页面

根据HTML解析出DOM树  
根据CSS解析出Css规则树  
Dom树与CSS树合并形成渲染树  
根据渲染树计算每个节点的信息(layout)引起重排  
根据计算好的信息绘制整个页面(paint)引起重绘

(6) tcp四次挥手断开链接

webpack

1、常见的loader和plugin,以及loader和plugin的区别
  • source-map-loader、image-loader、babel-loader、style-loader、css-loader、url-loader、ts-loader。
  • html-webpack-plugin、clean-webpack-plugin、ModuleConcateenationPlugin(开始scope hosting)、webpack-bundle-analyzer(可视化webpack输出文件体积)

区别:
loader本质是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为webpack只识别js,所以loader承担的是对其他类型的资源进行转译的与处理工作;在modules.rules中配置,作为模块的解析规则类型为数组。
Plugin就是插件,基于事件流框架tapable,插件可以扩展webpack的功能。在plugins中单独配置,类型为数组每一项是一个plugin实例,参数都通过构造函数传入。

2、webpack的构建流程

webpack是一个串行的过程,从启动到结束会依次执行以下流程:

  • 初始化参数:从配置文件个shell语句中读取与合并参数,得到最终参数
  • 开始编译:利用最终参数初始化Compiler对象,加载所有配置的插件,执行对象的run方法开始编译
  • 确定入口:根据配置中的entry找出所有的入口文件
  • 编译模块: 从入口文件出发,调用所有配置的loader对模块进行翻译,在找出该模块依赖的模块,在递归本步骤知道所有入口依赖的文件都处理完成
  • 完成模块编译:经过loader翻译完所有模块后,得到每个模块被翻译后的内容以及依赖关系
  • 输出资源: 根据入口和模块之间的依赖关系,组装为包含多个模块的chunk,再把每个chunk转换为一个单独的文件加入到输出列表,这不是修改输出内容的最后 机会
  • 输出完成:确定好输出内容后根据配置的输出的路劲和文件名,把内容写入到文件系统。
3、source map是什么?生产环境怎么用?

source map是将编译、打包、压缩后的代码映射回源代码的过程。map文件只要不打开开发者工具浏览器是不会加载的。
生产环境一般有三种处理方案:
hiddne-source-map:借助第三方错误监控平台Sentry使用
noSources-source-map:只会显示具体行数以及查看源代码的错误栈
sourcemap:通过nginx设置将.map文件只对白名单开放

4、模块打包的运行原理

webpack的构建流程,模块打包在webpack源码中主要依赖compilercompilation两个核心对象实现。

compiler对象是一个全局单列,负责整个webpack打包的构建流程。compilation对象是每一次构建的上下文对象,它包含了当次构建所需要的所有信息,每次热更新和重新构建,compiler都会重新生成一个新的compilation对象,负责此次更新的构建过程。

而每个模块之间的依赖关系则依赖于AST语法树。每个模块文件在通过loader解析完成以后会通过acorn库胜场模块的代码的AST语法树,通过语法树就可以分析这个模块是否还有依赖的模块,进而继续循环执行下一个模块的编译解析。

5、webpack热更新原理

webpack的热更新又称为热替换,这个机制可以做到不用刷新浏览器而将变更后的模块替换掉旧的模块。
热更新的核心就是客户端从服务端拉取更新后的chunk,WDS与浏览器之间维护了一个websocket,当本地资源发生改变时,WDS会向浏览器及时推送更新并带上构建时的hash,让客户端与上一次资源进行对比。客户端对比出差异以后会向WDS发起AJAX请求来获取更改内容(文件列表,hash),这样客户端就可以在借助这些信息继续向WDS发起jsonp请求获取该chunk的增量更新。拿到增量更新后如何处理由HotModulePlugin完成。

5、在实际工作中,如何保证各个loader按照预想方式工作

可以使用enforce强制执行loader的作用顺序,pre代表所有正常loader之前执行,post是所有loader之后执行。

6、如何提高webpack打包速度
  1. 优化loader 针对loader影响打包效率的肯定是babel,babel会将代码转为字符串生成AST,然后对AST进行转换生成新的代码,项目越大转换代码越多,效率就会越低。因此可以优化loader的文件搜索范围,将babel编译过的文件缓存起来
  2. happyPack webpack在打包过程中是单线程的,如果需要长时间编译的任务很多就会导致影响效率,因此可以使用HappyPack将loader的同步执行转换为并行的
  3. DLLPlugin DllPlugin可以将特定的类库提前打包然后引入,这种方法可以极大的见到打包类库的次数,只有类库发生更新的时候才会打包。
  4. 代码压缩 webpack4中只需要将mode设置为production就可以默认开启压缩代码
  5. 图片压缩 配置image-webpack-loader
7、减少打包体积
  1. 按需加载:将每个路由页面单独打包为一个文件。
  2. scope Goisting:会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
  3. tree Shaking:可以实现删除项目中未被引用的代码。
8、loader与plugin的编写思路
  1. loader loader是支持以数组的形式配置多个的,因此当webpack在转换该文件类型的时候,会按顺序链式调用每个loader,前一个loader返回的内容会作为下一个loader的入参,因此loader的开发需要遵循例如 返回值必须是标准的js代码字符串,以保证下一个loader能够正常工作,同时在开发上需要严格遵循单一职责,只关心loader的输出以及对应的输出。loader函数中的this上下文由webpack提供,可以通过this对象提供的相关属性,获取当前loader需要的各种信息数据。
  2. plugin 遵循规范和原则: 插件必须是一个函数或者一个包含apply方法的对象,这样才能访问compiler实例;传给每个插件的compiler和compilation对象都是同一个引用,若在一个插件中修改了他们身上的属性,会影响后面的插件。异步的时间需要在插件处理完成任务时调用回调函数通知webpack进入下个流程不会卡住。

TypeScript

1、ts相对于js的优势是什么
  • 静态类型检查:可以在开发人员编写脚本时检测错误,可以使代码质量更好,更清晰。
  • 类型可以在一定程度上充当文档
  • 对js打包内置支持
2、TypeScript 中 const 和 readonly 的区别?枚举和常量枚举的区别?接口和类型别名的区别?

const可以防止变量的值被修改,readyonly可以防止变量的属性被修改;常量枚举只能使用常量枚举的表达式,在编译阶段会被删除 ,常量枚举成员在使用的地方会被内联进来。接口和类型别名都可以用来描述对象或函数的类型,与接口不同,类型别名还可以用于其他类型,如基本类型、联合类型、元组。

ts中的any类型的作用是什么

为编程阶段还不清楚类型的变量指定一个类型,变量的值可能是来自于动态的内容,例如用户输入、第三方库等。使用any可以直接通过类型检查器进入编译阶段。

3、ts中的this和js中的this差异

ts中必须声明this的类型才能在函数或者对象中使用this,箭头函数的this与es6中的箭头函数this是一致的。

4、ts中type和interface区别

type与interface都可以描述对象或函数,都允许拓展。但是type可以声明基本类型、联合类型、元组;type可以使用typeof获取实例的类型进行赋值。多个相同的interface声明可以自动合并;使用interface描述数据结构,使用type描述类型关系

5、ts中的 ?. ?? ! !. _ ** 等符号的含义
  • ?. 可选链,遇到null和undefined可以立即停止表达式的运行
  • ?? 空值合并运算符,当左侧操作数为null或undefined时,其返回右侧的操作数,否则返回左侧的操作数
  • ! 非空断言运算符 ,x!将从x值域中排除null和undefined
  • !. 在变量名后添加,可以断言排除undefined和null类型
  • _ 数字分隔符,分隔符不会改变数值字面量的值,使数字更易懂
  • ** 求幂
6、ts模块的加载机制

假设有个导入语句 import {a} from 'moduleA' 首先编译器会舱室定位需要导入的模块文件,通过绝对或者相对的路劲查找方式;如果上面的解析失败了,没有查找到对应的模块,编译器会舱室定位一个外部模块声明(d.ts),最后如果编译器还是不能解析这个模块就会抛出错误。