前端面试题整理

Css

1. ★ 父元素和子元素宽高不固定,如何实现水平垂直居中
- 弹性盒模型
  -父元素设置:display:flex; justify-content:center; align-items:center
  -额外的骚操作:父元素设置弹性盒display:flex; 子元素可以设置margin: auto; 实现垂直水平居中
- 定位属性(position)配合位移属性(transform)
  - 父元素设置:position:relative,
  - 子元素设置:position:absolute; top:50%; left:50%; transform:translate(-50%,-50%)
2. ★★ 分别实现骰子中的'一点' 和 '三点' 的布局。

链接:

/*一点布局:原理就是将单个子元素垂直水平居中,利用弹性盒模型(display:flex)即可*/
父元素设置:display:flex; justify-content:center; align-items: center

/*三点布局:同样是使用弹性盒模型(display:flex),只不过这次需要用到其他属性*/

父元素设置:display: flex; justify-content: space-between;
子元素设置: .child:nth-child(2){
              align-self: center;
            }
            .child:nth-child(3){
              align-self: flex-end;
            }

三点布局需要三个子元素,在这里的第一个子元素不需要设置排列方式,默认为 align-self: flex-start 
justify-content: space-between; 的作用是使子元素能够在水平方向上两边产生间隔并平均分布空间
align-self: flex-start(默认)/center/flex-end; 该属性作用在父元素设置了display:flex的子元素上,可以调整子元素自身的位置
3. ★★ 简述选择器~和+的区别。
~ 选择器的作用:
    1.选择紧跟着当前符合条件元素后面的同级元素
    2.可以匹配多个
+ 选择器的作用:
    1.选择紧跟在当前符合条件元素后面的同级元素
    2.只能匹配一个

例: <div class="box"></div>
    <p class="one"></p>
    <span class="two"></span>
    <p class="three"></p>
    <span class="four"></span>

    .box ~ p  :可以选中box下的所有p元素,既是 one 和 three
    .box + span :则匹配选中box相邻下符合条件的第一个元素,既是 two
4. ★★ 简述box-sizing的有效值以及所对应的盒模型规则。
/*box-sizing值*/
    box-sizing: content-box/border-box/inherit
 box-sizing属性用于更改用于计算元素的宽度和高度默认的CSS盒子模型,可以使用此属性来模拟不正确支持CSS盒子模型规范的游览器行为。
**content-box:
    1.box-sizing的默认属性
    2.是CSS2.1中规定的宽度高度的显示行为
    3.在CSS中定义的宽度和高度就对应到元素的内容框(即元素容器本身
    4.在CSS中定义的宽度和高度之外绘制元素的内边距和边框(即在元素容器本身上增加内边距和边框,容器宽高需要自增计算
    容器占据空间大小计算方式:content(容器宽高)+ margin(外边距) + border(边框)
**border-box:
    1.在CSS中微元素设定的宽度和高度就决定了元素的边框值
    2.元素在设置内边距和边框是在已经设定好的宽度和高度之内进行绘制
    3.CSS中设定的宽度和高度减去边框和内间距才能得到元素内容所占的实际宽度和高度
    容器占据空间大小计算方式:content(容器宽高)+padding(内边距)+border(边框)
**inherit:
    1.规定元素是从父容器继承box-sizing的属性值
5. ★★★ html中元素的margin是否会叠加(合并)?如何解决?
/*  会叠加   */
问题详解1: flex布局对子元素的影响
    1.子元素的float、clear和vertical-align属性将会失效
    2.解决了margin传递、重叠(叠加)问题

问题详解2:flex布局的margin传递叠加问题主要有以下两种
    1.父子间的margin,会由子级传递到父级
    —— 解决方法: margin传递的产生的原因是父级的高度没有被自动撑开,所以在父级父级增加属性:overflow: auto 即可解决
    2.兄弟间的margin值会重复叠加
    —— 解决方法: 浏览器为了保证列表的整齐,上下margin产生了叠加,不能直接解决。只能通过减少一个margin的方式。如只定义margin-top:100px;  margin-bottom:0px。的方式解决。
6. ★★ 简述align-items和align-content的区别。
align-items:可以应用于所有的flex容器,它的作用是设置flex子项在每个flex行的交叉轴上的默认对齐方式(相对Y轴

align-content:只适用于多行的flex容器,在使用前需在flex容器设置flex-wrap:wrap;表示子元素超出换行;align-content 它的作用是当flex容器在交叉轴上有多余的空间时,将子项作为一个整体进行对齐。
7. ★★ 简述data-*属性的用法(如何设置,如何获取),有何优势?
data-*定义:
    1.是用于储存页面或应用程序的私有自定义数据
    2.赋予我们在所有html元素上嵌入自定义data属性的能力

data-*用法:
    1.属性名不应该包含任何大写字母,并且在前缀 "data-" 之后必须有至少一个字符
    2.属性值可以是任意字符串
    3.一个元素可以拥有任意数量的data属性
    4.data属性无法储存对象,如需储存,可通过对象序列化

data-*如何设置、获取:
    1.如何设置
        通过JavaScript内置的setAttribute('data属性名','新内容')即可设置
        通过该数据类型的(dataset) API设置data值,IE10以上才支持;
            var button = document.queryselector('button')
            button.dataset.data属性名 = '新内容' ; 这里的data属性名是指data-后面的名字

    2.如何获取
        通过JavaScript内置的getAttribute('data属性名') 即可获取
        通过该数据类型的(dataset) API设置data值,IE10以上才支持;
            var button = document.queryselector('button')
            data = button.dataset.data属性名 ; 这里的data属性名是指data-后面的名字

data-*优势:
    1.其储存的自定义数据能够被页面的JavaScript利用,可以创建更好的用户体验
    2.可以通过JavaScript来构造数据、填充数据
    3.代码体积小、较为灵活
    4.解决网站的外观和实用性之间产生的冲突
8. ★ 简述title与h1的区别,b与strong的区别,i与em的区别。
《title与h1的区别》:
    1. 从网站角度看,title 更重于网站信息。title可以直接告诉搜索引擎和用户这个网站是关于什么主题和内容的。
    2. 从文章角度看,h1则是用于概括文章主题。
    3. 一个网站可以有多个title,最好一个单页用一个title,以便突出网站页面主体信息,从seo看,title权重比h1高,适用性比h1广。
    4. 标记了h1的文字页面给予的权重会比页面内其他权重高很多。一个好的网站是h1和title并存,既突出h1文章主题,又突出网站主题和关键字。达到双重优化网站的效果。

《b与strong 的区别》:
    1. b 是只是对文本的简单加粗, strong 是一个语义化标签,对相关文本具有强调作用
    2. b 标签只是侧重于字体加粗, strong标签加强字体的语气都是通过粗体来实现的,相比之下,搜索引擎更喜欢侧重于strong标签
    3. strong标签更注重于内容上的应用,在html中,对关键词的标明,然而还有一些网站上,也有使用strong标签登对小标题进行强调,但是在页面中,如果出现过多的strong标签,可能会对排名不利。

《i 与 em 的区别》:
    1. i(italic)是实体标签,用来使字符倾斜,em(emphasis)是逻辑标签,作用是强调文本内容 
    2. i标签只是斜体的样式,没有实际含义,常用来表达无强调或着重意味的斜体,比如生物学名、术语、外来语;
    3. em表示标签内字符重要,用以强调,其默认格式是斜体,但是可以通过CSS添加样式。
    建议:为了符合CSS3的规范,i 标签应尽量少用而应改用 em
9. ★ 什么是标准文档流
*** 标准文档流指的是元素排版布局过程中,元素会默认自动从左往右,从上往下的流式排列方式。当前面内容发生了变化,后面的内容位置也会随着发生变化。

*** HTML就是一种标准文档流文件。

简单的来说就是各种布局属性在html中所显示的效果,如display(行内元素与块级元素*非常重要*)、float、position
10. ★ z-index是什么?在position的值什么时候可以触发?
z-index : 指的是一个元素在当前文档页面定位时重叠层显示的层级等级,默认为0 ,数值不限,越大显示层级越高

触发机制:当position的值设置为absolute、relative和fixed时才能触发
11. ★★ CSS3 如何实现圆角?
border-radius属性
    1. 四个值: 第一个值为左上角,第二个值为右上角,第三个值为右下角,第四个值为左下角。
    2. 三个值: 第一个值为左上角, 第二个值为右上角和左下角,第三个值为右下角
    3. 两个值: 第一个值为左上角与右下角,第二个值为右上角与左下角
    4. 一个值: 四个圆角值相同
只需要使用border-radius将四个角设置为相适应的尺寸即可实现圆角
12. ★★ HTML5有哪些缓存方式?
1、localstorege缓存,将数据储存在本地客户端,只有用户手动清除才能清除缓存
    API:1.localstorege.setItem(key,value),键值对的形式缓存
        2.localstorege.getItem(key),根据键名来缓存值
        3.localstorege.length ,获取总缓存数量

2、sessionStorege 会话缓存,会话机制是指从打开浏览器开始访问页面的时候,到关闭这个页面的过程成为一个会话,sessionStorege储存的数据会随着页面关闭而销毁
    API: 1. sessionStorage.setItem(key,val),localStorage是以键值对的形式创建的;
         2. sessionStorage.getItem(key),根据键名来获取缓存的值;
         3. sessionStorage.length;获取总共缓存值得数量, localStoarge返回的是个对象;

3、离线缓存机制(Application Cache)
    1. 配置manifest文件,manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)
    2. manifest 文件可分为三个部分:
        1、CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
        2、NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
        3、FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)
    3. API: 0(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存
            1(IDLE) : 闲置,即应用缓存未得到更新
            2 (CHECKING) : 检查中,即正在下载描述文件并检查更新
            3 (DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源
            4 (UPDATEREADY) : 更新完成,所有资源都已下载完毕
            5 (IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存
4、web SQL
    1. 关系数据库,通过SQL语句访问
    2. Web SQL 数据库API并不是HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用SQL操作客户端数据库的APIs
    3. 支持情况:Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。
    4. API:1. openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
            2. transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
            3. executeSql:这个方法用于执行实际的 SQL 查询。

5、 IndexDB
    1.索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。
13. ★★ CSS3新增伪类有那些?
常用的伪类:
    1. :link 选择所有未访问的链接
    2. :visited 选择所有访问过的链接
    3. :active 选择正在活动的链接(或理解为鼠标点击瞬间效果)
    4. :hover 鼠标放到链接后的状态
    5. :focus 选择元素输入后具有焦点
    6. :before 在元素之前插入内容
    7. :after 在元素之后插入内容
14. ★ 简述一下src与href的区别,title和alt的区别
href:href表示超文本引用,用来建立当前元素和文档之间的链接,常用在link和a等元素上。
    注:当浏览器解析到这一句时会识别该文档为css文件,会下载并不会停止对当前文档的处理,所以建议使用link方式而不是@import加载css。

src:src表示引用资源,替换当前元素,是页面内容不可缺少的一部分,常用在img,script,iframe上。src指向外部资源的位置,指向的内部会迁入到文档中当前标签所在的位置;请求src资源时会将其指向的资源下载并应用到当前文档中,例如js脚本、img图片等。src链接内的地址不会有跨域问题
     注:当浏览器解析到这一句时会暂停其他资源的下载和处理,直至将该资源加载、编译、执行完毕。这也是js脚本放在底部而不是头部的问题

title:
    1. title属性是为元素提供额外的注释信息,当鼠标放在元素上时会有title文字显示,以达到补充说明或提示。
       2. title属性更倾向于用户体验的考虑。
    3. title既可以是元素的属性也可以是标签,作为属性可以用在除base,basefont,head,html,meta,param,script和title之外的任何标签上(title常与form以及a标签一同使用,以提供关于输入格式和链接目标的信息),title与style、id、class等一起作为HTML中许多标签共用的标准属性。

alt:
    1. alt属性是在你的图片无法显示时的替代文本,它会直接输出在原本加载图片的地方。
    2. alt属性有利于SEO,是搜索引擎搜录时判断图片与文字是否相关的重要依据。
    3. alt只能是元素的属性,只能用在img、area和input标签中(img,area中alt必须指定)。     

注:当a标签内嵌套img标签时,起作用的是img的title属性。
15. ★ 什么是CSS hack?
CSS hack:CSS hack是通过在CSS样式中加入一些特殊的符号,让不同的浏览器识别不同的符号(什么样的浏览器识别什么样的符号是有标准的,CSS hack就是让你记住这个标准),以达到应用不同的CSS样式的目的。

注:CSS属性Hack、CSS选择符Hack以及IE条件注释Hack, Hack主要针对IE浏览器。

例:margin属性在ie6中显示的距离会比其他浏览器中显示的距离宽2倍,也就是说margin-left:20px;在ie6中距左侧对象的实际显示距离是40px,而在非ie6中显示的距左侧对象的距离是设置的值20px;所以要想设置一个对象距离左侧对象的距离在所有浏览器中都显示是20px的宽度的样式应为:.kwstu{margin-left:20px;_margin-left:20px;}

CSS hack常见的三种形式:
    1. 属性级Hack:比如IE6能识别下划线“_”和星号“*”,IE7能识别星号“*”,但不能识别下划线”_ ”,而firefox两个都不能认识。
    2. 选择符级Hack:比如IE6能识别*html .class{},IE7能识别*+html .class{}或者*:first-child+html .class{}。
    3. IE条件注释Hack:IE条件注释是微软IE5开始就提供的一种非标准逻辑语句。比如针对所有IE:&lt;!-[if IE]&gt;&lt;!-您的代码-&gt;&lt;![endif]&gt;,针对IE6及以下版本:&lt;!-[if it IE 7]&gt;&lt;!-您的代码-&gt;&lt;![endif]-&gt;,这类Hack不仅对CSS生效,对写在判断语句里面的所有代码都会生效。
    PS:条件注释只有在IE浏览器下才能执行,这个代码在非IE浏览下被当做注释视而不见。可以通过IE条件注释载入不同的CSS、JS、HTML和服务器代码等。
16. ★★ 什么叫做优雅降级和渐进增强?
渐进增强 progressive enhancement: 
    1. 针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
    2. 渐进增强观点则认为应关注于内容本身。内容是我们建立网站的诱因。有的网站展示它,有的则收集它,有的寻求,有的操作,还有的网站甚至会包含以上的种种,但相同点是它们全都涉及到内容。这使得“渐进增强”成为一种更为合理的设计范例。这也是它立即被 Yahoo! 所采纳并用以构建其“分级式浏览器支持 (Graded Browser Support)”策略的原因所在。

优雅降级 graceful degradation:
    1. 一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。
    2. 优雅降级观点认为应该针对那些最高级、最完善的浏览器来设计网站。而将那些被认为“过时”或有功能缺失的浏览器下的测试工作安排在开发周期的最后阶段,并把测试对象限定为主流浏览器(如 IE、Mozilla 等)的前一个版本。

区别:
    1. 优雅降级是从复杂的现状开始,并试图减少用户体验的供给
    2. 渐进增强则是从一个非常基础的,能够起作用的版本开始,并不断扩充,以适应未来环境的需要
    3. 降级(功能衰减)意味着往回看;而渐进增强则意味着朝前看,同时保证其根基处于安全地带
17. ★★★ 移动端适配怎么做?
方法一:@media 媒体查询,通过查询设备的宽度来执行不同的 css 代码,最终达到界面的配置。

方法二:Flex弹性布局

方法三:rem + viewport 缩放,屏幕宽度设定 rem 值,需要适配的元素都使用 rem 为单位,不需要适配的元素还是使用 px 为单位。
18. ★★ 请问苹果原生浏览器中默认样式如何清除,例如button,input默认样式?
清除苹果默认样式: css样式中加入 input,textarea,button { -webkit-appearance: none; border-radius:0px; border:none;}

input、button默认样式: input[type="button"], input[type="submit"], input[type="reset"] {-webkit-appearance: none;}
19. ★ CSS清除浮动的方法。
1. 在标签尾部添加空块级标签,设置样式属性为:clear:both;缺点:如果页面浮动布局多,就要增加很多空div,不利于页面的优化。
2. 父级定义伪类after和zoom,.box:after{display:block; clear:both; content:""; visibility:hidden; height:0;}  .box{ zoom: 1 }
3. 简单粗暴,父级设置overflow:hidden,缺点是不能和position配合使用
4. 直接给父元素单独设置高度(height);缺点:只适合高度固定的布局,要给出精确的高度,如果高度和父级div不一样时,会产生问题。对于响应式布局会有很大影响。
20. ★★ PC端常用的布局方法。
1、利用float+overflow实现
    左右定宽度,分布向两边浮动,中间如果没设置溢出处理默认宽度是100%,设置后就会截取两边的宽度从而实现中间自适应
2、flex布局
    通过flex相关的属性进行适配性的调整布局
3、Table表格布局
4、float+margin实现三列布局
5、定位absolute实现中间自适应
6、Grid网格布局
7、圣杯布局
8、双飞翼布局
9、等高布局
21. ★★ 布局左边20%,中间自适应,右边200px,不能用定位。
布局: <div class="box">
        <div class="left">left</div>
        <div class="right">right</div>
        <div class="content">content</div>
      </div>

样式:*,html,body{
          margin: 0;
          padding: 0;
        }
        .box{
          width: 1500px;
          height: 500px;
          background-color: rgb(215, 221, 221);
          margin: 0 auto;
        }
        .left{
          width: 20%;
          height: 200px;
          background-color: chocolate;
          float: left;
        }
        .right{
          width: 200px;
          height: 200px;
          float: right;
          background-color: cornflowerblue;
        }
        .content{
          overflow: hidden;
          height: 400px;
          background-color: darkblue;
        }
22. ★★ 行内元素和块级元素?img算什么?行内元素怎么转化为块元素?
行内元素:1.无法设置宽高;
        2. 对margin仅设置左右有效,上下无效;
        3. padding上下左右有效;不会自动换行
块级元素:1.可以设置宽高
        2. margin和padding的上下左右均对其有效
        3. 超出当前行会自动换行
        4. 多个块状元素标签写在一起,默认排列方式为从上至下
img:属于行内块元素(inline-block),即有行内元素的属性也有块级元素的属性

元素之间的转化可以通过设置样式:display:block/inline/inline-block来改变自身的元素属性
23. ★★ 将多个元素设置为同一行? 清除浮动的几种方式?
将多个元素设置为同一行:1. 浮动(float) 2.行内元素/行内块元素  
清除浮动:详见 19
24. ★ 什么是CSS3 transform? animation? 区别是什么?
transform:1. transform 属性向元素应用 2D 或 3D 转换。该属性允许我们对元素进行旋转、缩放、移动或倾斜。
          2. transform属性是静态属性,需要配合transition和animation才能展现出动画效果。你可以把它看成是跟left、top等属性一样,只是一个静态样式而已。

animation:一个css3的动画属性,需要配合@keyframes 使用

区别: 1. transition是css中检测指定属性变化进行自动补间动画的属性。
      2. animate是先指定好动画过程中的关键帧属性,进行动画的属性。
25. ★★ nth-of-type和nth-child的区别是什么?
nth-of-type:该css伪类是针对具有一组兄弟节点的标签, 用 n 来筛选出在一组兄弟节点的位置。

nth-child:该css伪类首先是找到所有当前元素的兄弟元素,, 用 n 来筛选出在当前元素的兄弟元素节点的位置。

总的来说就是,nth-of-type 它是当前元素的兄弟元素的第n个,而nth-child 是当前元素的兄弟节点的第n个当前元素。

例:
      <p>第一个</p>
      <p>第二个</p>
      <h1>第三个</h1>
      <p>第四个</p>
      <p>第五个</p>

     p:nth-child(4){
      color: red;     //选中的是 ‘第四个’ 内容的 p 标签
    }

    p:nth-of-type(4){
      color: yellow;  //选中的是 ‘第五个’ 内容的 p 标签
    }
26. ★★ :before 和 ::before区别是什么?
区别:
    1. 叫法不同:一个是伪类,一个是伪元素
    2. 版本不同:作用都是一样,但单冒号伪类写法是旧版本css2写法, 双冒号伪元素是新版本css3写法
    3. 兼容性差异:单冒号伪类写法 兼容性比 双冒号要好。 :before > ::before
27. ★★ 简述 viewport 所有属性
width:控制 viewport 的大小,可以指定的一个值,如 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。

height:和 width 相对应,指定高度。

initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例(调整页面缩放比例)。

maximum-scale:允许用户缩放到的最大比例。

minimum-scale:允许用户缩放到的最小比例。

user-scalable:用户是否可以手动缩放
28. ★ 如何理解HTML结构语义化?
为什么要语义化:
    a. 为了在没有CSS的情况下,页面也能呈现出很好地内容结构、代码结构:为了裸奔时好看;

    b. 用户体验:例如title、alt用于解释名词或解释图片信息的标签尽量填写有含义的词语、label标签的活用;

    c. 有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重;

    d. 方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备)以有意义的方式来渲染网页;

    e. 便于团队开发和维护,语义化更具可读性,遵循W3C标准的团队都遵循这个标准,可以减少差异化。

写代码时要注意什么:
    1.  尽可能少的使用无语义的标签div和span;

    2.  在语义不明显时,既可以使用div或者p时,尽量用p, 因为p在默认情况下有上下间距,对兼容特殊终端有利;

    3.  不要使用纯样式标签,如:b、font、u等,改用css设置。

    4.  需要强调的文本,可以包含在strong或em标签中,strong默认样式是加粗(不要用b),em是斜体(不要用i);

    5.  使用表格时,标题要用caption,表头用thead,主体部分用tbody包围,尾部用tfoot包围。表头和一般单元格要区分开,表头用th,单元格用td;

    6.表单域要用fieldset标签包起来,并用legend标签说明表单的用途;demo

    7.每个input标签对应的说明文本都需要使用label标签,并且通过为input设置id属性,在lable标签中设置for=someld来让说明文本和相对应的input关联起来。

    8.补充一点:不仅写html结构时,要用语义化标签,给元素写css类名时,也要遵循语义化原则,不要,随便起个名字就用,那样等以后,再重构时,非常难读。最忌讳的是不会英文,用汉语拼音代替。别那么LOW 。
29. ★★ 伪类选择器和伪元素?CSS3中引入的伪元素有什么?
1. 伪类选择器是css2版本中的旧写法,相对于css3中伪元素的的新写法兼容性会更好。

2. 伪元素只能在一个选择器中出现一次,且需要配合content属性一起使用

3. 伪元素不会出现在DOM中,所以不能通过js来进行操作,仅仅是在渲染层加入而已

css3引入的伪元素:
    1、 ::after  //在xxx之后插入内容
    2、 ::before     // 在xxx之前插入内容
    3、 ::first-letter  //选择xxx元素的首字母
    4、 ::first-line       //选择xxx元素的首行
    5、 ::selection     //选择用户选择的元素部分
30. ★★ HTML5有哪些新特性,移除了哪些元素?如何处理HTML5新标签兼容问题?如何区分HTML和HTML5?
HTML5 新特性:
    一、语义标签

    二、增强型表单

    三、视频和音频

    四、Canvas绘图

    五、SVG绘图

    六、地理定位

    七、拖放API

    八、WebWorker

    九、WebStorage

    十、WebSocket

移除的元素:
    1、纯表现的元素:basefont,big,center,font,s,strike,tt,u。

    2、对可用性产生负面影响的元素:frame,frameset,noframes。

处理HTML5新标签兼容问题: 使用html5shiv
    原理:用document.createElement()创建html5标签

    用法:1、在页面引入html5shiv.js
         2、在样式中编写 article,aside,dialog,footer,header,section,footer,nav,figure,menu
{display:block}

如何区分HTML和HTML5:
    HTML:  1)标识文本(eg: 定义标题文本、段落文本、列表文本、预定义文本);
            2)建立超链接,便于页面链接的跳转;
            3)创建列表,把信息有序地组织在一起,方便浏览;
            4)在网页中显示“图像、声音、视频、动画”等多媒体信息,使网页设计更具冲击力;
            5)可制作表格,以便显示大量数据;
            6)可制作表单,允许在网页内输入文本信息,执行其他用户操作,方便信息互动;
            7)没有体现结构语义化的标签(常用命名方式如下,eg: <div id=“header”></div>, 该语句用来表示网站的头部)。

    HTML5: 1)用于绘画的canvas元素;
            2)用于媒介回放的video和audio元素;
            3)对本地离线存储有更好的支持;
            4)新的特殊内容元素(eg: article、footer、header、nav、section等);
            5)新的表单控件(eg: calendar、date、time、email、url、search等);
            6)有语义优势,提供了一些新标签,(eg: <header> <article> <footer> 提供了语义化标签),可以更好地支持搜索引擎的读取,便于SEO蜘蛛的爬行。
31. ★★ 常见浏览器兼容性问题?
1、不同浏览器下的padding和margin不同
    解决方法:使用通配符(*)将padding和margin设置为0

2、块属性标签float之后,又有横向的margin值,在IE6中显示会比设置的大(IE6双边距bug)
    解决方法:在float标签样式控制中加入display:inline;

3、设置较小的高度标签(一般小于10px),在IE6,IE7,遨游中超出自己设置的高度
    解决方法:给超出高度的标签设置overflow:hidden;或者设置行高line-height小于你设置的高度。

4、行内标签设置display:block;后又采用float布局,再设置横向margin值时,在IE6中显示会比设置的大(IE6双边距bug)
    解决方法:在display:block;后面加上display:inline;display:table;

5、……更多请看链接
32. ★ 块级元素?行内元素?空元素?
块级元素:1. 可以设置宽高值,元素大小超出html文档会自动换行
        2. 外边距和内边距都可控制
        3. 可以容纳行内元素和其他块元素

行内元素:1. 不可以设置宽高值,元素紧跟在前一个元素后面,不会换行
        2. 宽度就是它的文字或图片的宽度,不可改变;
        3. 内联元素只能容纳文本或者其他内联元素;

空元素: 1. 没有闭合标签的标签被称作为空标签。
        2.在我们使用的标签中,有的具有闭合标签。例如<td>标签,它有闭合标签</td>。但是也有一部分标签没有闭合标签,例如<br />标签,这一类标签我们称之为空标签。
33. ★★ media属性?screen? All? max-width? min-width?
media: 媒体查询

screen :计算机屏幕

All :默认,适合所有设备

max(min)-width :规定目标显示区域的宽度

css合并写法: @media screen and (min-width:xxxpx) {}
34. ★ meta标签的name属性值?
解释:meta标签的 name 属性是用来定义一个 HTML 文档的描述、关键词,规定了元数据的名称,规定了content属性的信息/值的名称
属性值: 1. application-name  //规定页面所代表的Web应用程序的名称
        2. author     //规定页面文档的作者的名字
            实例: <meta name="author" content="作者名称">

        3. description    //规定页面的描述。搜索引擎会把这个描述显示在搜索结果中
            实例: <meta name="description" content="页面描述">

        4. gennerator    //规定用于生成文档的一个软件包(不用于手写页面)。
            实例: <meta name="generator" content="FrontPage 4.0">

        5. keywords    //规定一个逗号分隔的关键词列表 - 相关的网页(告诉搜索引擎页面是与什么相关的)。
            实例: <meta name="keywords" content="HTML, meta tag, tag reference"
            提示:总是规定关键词(对于搜索引擎进行页面分类是必要的)。
35. ★★ 一般做手机页面切图的几种方式
1、DPR:2    -----  切两倍图,(即设计原图大小,因为设计图是按原来的手机尺寸放大两倍之后的)  一般保存为xxx@2x

2、DPR:3 -----   切三倍图(即设计原图大小的1.5倍,因为设计图是按原来的手机尺寸放大两倍之后的)     一般保存为xxx@3x

切图的注意事项:
    1、 尺寸:一定是要偶数
    2、 命名:命名需要符合功能板块
    3、 ……更多详情看链接
36. ★★ px/em/rem有什么区别?为什么通常给font-size设置的字体为62.5%
px(像素):页面默认的尺寸计算单位,绝对长度,它是相对于显示器屏幕分辨率而言的

    特点:1. IE无法调整那些使用px作为单位的字体大小
         2. 国外的大部分网站能够调整的原因在于其使用了em或rem作为字体单位;
         3. Firefox能够调整px和em,rem,但是96%以上的中国网民使用IE浏览器(或内核)。


em:相对长度,相对于应用在当前元素的字体尺寸;一般浏览器默认字体大小为16px,则 1em = 16px

    特点:1. em的值并不是固定的;
         2. em会继承父级元素的字体大小。


rem(root em):相对单位,相对于html根元素字体大小的单位,当html的font-size:16px时,1rem = 16px

    特点:1. 这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。
         2. 除了IE8及更早版本外,所有浏览器均已支持rem。


为什么给font-size设置为62.5%: 方便换算!
    1. 因为绝大多数浏览器的默认字体大小为 16px ,而将font-size设置为 62.5% 则等价于字体大小的font-size:10px;
    2. 随之在其他的换算单位,如 rem 的字体换算时,则可以表示为 1rem = 10px, 整数值对于单位的换算会比较方便

    3. 但是在Chrome(谷歌浏览器)中,支持最小字体的大小为 12px ,解决办法就是 将html根字体设置为 font-size: 625%; 意:1rem = 100px ,以此单位换算
37. ★★ sass和scss有什么区别? sass一般怎么样编译的
区别:1、sass 书写时不带大括号, scss 带大括号
         例: sass-  .box
                       color:red

             scss-   .box{
                color:red;
               }
     2、 sass 没有 ; 号 , scss有

     3、 scss 写法和css 简直一样

sass编译:
    1. sass 是基于Ruby语言开发而成的,所以在使用 sass语言之前得先行安装Ruby编译器。
    2. Sass语言有两种后缀名,分别是 .sass 和 .scss ,两者只是写法有些许不同。
    3. 编译方式:
        1、命令行编译
            详情看链接↑
        2、插件保存编译
            详情看链接↑
38. ★★ 如果对css进行优化如何处理?
优化原则:减少css样式的渲染加载时间,通过削减css样式的代码体积等相关操作

实践型优化:
    1、内联首屏关键CSS(Critical CSS):内联CSS能够使浏览器开始页面渲染的时间提前
        * 性能优化中有一个重要的指标——首次有效绘制(First Meaningful Paint,简称FMP)即指页面的首要内容(primary content)出现在屏幕上的时间
        ** 这一指标影响用户看到页面前所需等待的时间,而内联首屏关键CSS(即Critical CSS,可以称之为首屏关键CSS)能减少这一时间。

    注:内联css并不是不加以限制的,它的初始拥堵窗口3存在限制(TCP相关概念,通常是 14.6kb, 压缩后的大小),如果内联CSS后的文件超出了这一限制,系统就需要在服务器和浏览器之间进行更多次的往返,这样并不能提前页面渲染时间。

    2、异步加载CSS
        * CSS会阻塞渲染,在CSS文件请求、下载、解析完成之前,浏览器将不会渲染任何已处理的内容。
        ** 有时,这种阻塞是必须的,因为我们并不希望在所需的CSS加载之前,浏览器就开始渲染页面。
        *** 那么将首屏关键CSS内联后,剩余的CSS内容的阻塞渲染就不是必需的了,可以使用外部CSS,并且异步加载

        方式一、 使用JavaScript动态创建样式表link元素,并插入到DOM中。

        方式二、 将link元素的media属性设置为用户浏览器不匹配的媒体类型(或媒体查询),如media="print",甚至可以是完全不存在的类型media="noexist"。对浏览器来说,如果样式表不适用于当前媒体类型,其优先级会被放低,会在不阻塞页面渲染的情况下再进行下载。


    3、文件压缩
        * 通过相关的构建工具对css样式进行打包压缩,去除多余的空格和换行。如 webpack、rollup、grunt/gulp.js 等    


    4、去除无用CSS
        1. 筛选去除相关重复的css样式
        2. 去除在页面中无法生效或不生效的css样式


建议型优化:
    1、有选择地使用选择器

    2、减少使用昂贵的属性
        1. 在浏览器绘制屏幕时,所有需要浏览器进行操作或计算的属性相对而言都需要花费更大的代价。

        2. 当页面发生重绘时,它们会降低浏览器的渲染性能。所以在编写CSS时,我们应该尽量减少使用昂贵属性
        * 昂贵属性: 如box-shadow/border-radius/filter/透明度/伪类:nth-child()等


    3、优化重排与重绘
        * 减少重排
            1. 重排会导致浏览器重新计算整个文档,重新构建渲染树,这一过程会降低浏览器的渲染速度。有很多操作会触发重排,我们应该避免频繁触发这些操作。

        ** 避免不必要的重绘
            1. 当元素的外观(如color,background,visibility等属性)发生改变时,会触发重绘。
            2. 在网站的使用过程中,重绘是无法避免的。不过,浏览器对此做了优化,它会将多次的重排、重绘操作合并为一次执行。
            3. 不过我们仍需要避免不必要的重绘,如页面滚动时触发的hover事件,可以在滚动的时候禁用hover事件,这样页面在滚动时会更加流畅。


    4、不要使用@import
        *** 不建议使用@import主要有以下两点原因。

        * 首先,使用@import引入CSS会影响浏览器的并行下载。使用@import引用的CSS文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。这就导致浏览器无法并行下载所需的样式文件。

        ** 其次,多个@import会导致下载顺序紊乱。在IE中,@import会引发资源文件的下载顺序被打乱,即排列在@import后面的js文件先于@import下载,并且打乱甚至破坏@import自身的并行下载
39. ★★ 如何对css文件进行压缩合并? gulp如何实现?
如何压缩合并:
    ** 通过相关的构建工具对css样式进行打包压缩,去除多余的空格和换行。如 webpack、rollup、grunt/gulp.js 等

gulp 如何实现css压缩合并
    * 看链接
40. ★ 如何实现图片和文字在同一行显示?

1. 给img标签添加 “vertical-align:middle”属性

2. 如果是背景图,则通过background的 定位属性来设置位置

3. 分别把图片和文字放入不同的div中,设置“vertical-align:middle”属性
a 标签的设置顺序: 
    1. link , 链接平常的状态
    2. hover ,鼠标放置在链接上显示的样式
    3. active ,链接被按下的样式
    4. visited , 链接被访问过后的状态
42. ★★★ 手机端上图片长时间点击会选中图片,如何处理?
img{ pointer-events:none },禁止事件,但会把整个标签的事件都禁用掉,不建议使用

img{ -webkit-user-select:none },用户选中状态

推荐:
    img{
            -webkit-touch-callout: none; //触摸
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }
43. ★ 简述video标签的几个属性和方法
属性:
    src :视频的属性
    poster:视频封面,没有播放时显示的图片
    preload:预加载
    autoplay:自动播放
    loop:循环播放
    controls:浏览器自带的控制条
    width:视频宽度
    height:视频高度
方法: 通过video id获取当前元素
    Media.paused; //是否暂停

    Media.defaultPlaybackRate = value;//默认的回放速度,可以设置

    Media.playbackRate = value;//当前播放速度,设置后马上改变

    Media.played; //返回已经播放的区域,TimeRanges,关于此对象见下文

    Media.seekable; //返回可以seek的区域 TimeRanges

    Media.ended; //是否结束

    Media.autoPlay; //是否自动播放

    Media.loop; //是否循环播放

    Media.play(); //播放

    Media.pause(); //暂停
    //视频控制

    Media.controls;//是否有默认控制条

    Media.volume = value; //音量

    Media.muted = value; //静音
44. ★★ 常见的视频编码格式有几种?视频格式有几种?
常见的视频编码格式,H264 , VP8, AVS, RMVB,WMV,QuickTime(mov)

视频格式有MPEG、AVI、nAVI、ASF、MOV、3GP、WMV、DivX、XviD、RM、RMVB、FLV/F4V。
45. ★★ canvas在标签上设置宽高和在style中设置宽高有什么区别?
在canvas标签上设置宽高, canvas画布发生的变化不会影响到画布内容,即画布内容不会发生改变

相反的,在style样式中设置宽高则会影响到画布内容的形状

js原生面试题

1.js里的基本类型与引用数据类型有哪些,引用类型和基本类型的区别**

基本类型:Undefined、Null、Boolean、Number、String、Symbol

引用类型:统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。

1)、内存的角度:

基本类型在内存中,占据一块空间,空间里存储的就是数据,获取数据是直接获取

引用类型在内存中,占据两块空间,第一块存储的是地址,第二块存储的是数据,获取数据是属间接取值

2)、赋值的角度

基本类型赋的就是值,引用类型赋的就是地址

3)、函数传参

基本类型传的是值,引用类型传的是地址

2 ,null和undefined区别

undefined:定义变量后,没有赋值

null:对象没有引用的值,即就是引用类型里没有存储地址

3,js中的==和===区是什么

三等是数据和类型都相等, ==会尝试进行类型转换

Let ,const.var的区别[越详细越好) 扩展;它们的存储位置

相同点:都是用来定义变量的

不同点:

1)、var定义的变量会声明提升,let和const不存在声明提升

2)、var定义的变量作用域是全局作用域和函数作用域,let和const是块级作用域

3)、var定义的全局变量是window对象的属性,let和const定义的全局变量不是window的属性

4)、var可以定义多个同名的变量,let和const不行

5)、let有暂时性死区

6)、let在循环里,可以暂存循环便变量

7)、const定义的变量是常量,必须赋初始值而且不可修改

const变量,说绑定一个函数或者数组,那他可以变吗?为什么什么原理,const 定义一个数组,改变下标0的值,会报错吗

不可以,因为const修饰的变量是只读的

改变下标的值不会报错,因为存储数据的 地址 没变

for循环里let换成var会发生什么

①如果换成var,for 循环定义的迭代变量会渗透到循环体外部,改成let之后,在循环体外是访问不到的,原因let是块级作用域

②如果在for循环里有异步,异步里向外输出定义的迭代变量,如果是let定义的变量,会向外输出每一次迭代的便变量,如果换成var,向外输出的是已经循环结束的变量

this的指向,箭头函数的this

①当this所在函数是事件处理函数时,this代表事件源

②当this所在函数是构造函数时,this代表new出来的对象

③当this所在函数是类的方法时,this代表调用方法的对象。

④当this所在函数没有所属对象,this代表window对象。

箭头函数根本没有自己的this,它内部的this就是外层代码块的this

call, apply,bind的区别[说一说基本原理]

相同点:三个函数都会改变this的指向(调用这三个函数的函数内部的this)

不同点:

1)、bind会产生新的函数,(把对象和函数绑定死后,产生新的函数)

2)、call和apply不会产生新的函数,只是在调用时,绑定一下而已。

3)、call和apply的区别,第一个参数都是要绑定的this,apply第二个参数是数组(是函数的所有参数),call把apply的第二个参数单列出来。

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

1.写法不同

2.不能作为构造函数使用

3.箭头函数中this的指向不同

4.箭头函数不具有prototype原型对象

5.箭头函数不具有super

箭头函数的作用(跟普通函数的区别) 简要描述下ES6中的箭头函数以及其使用场景

①.箭头函数使表达更加简洁

②.最主要的目的就是解决this指针的问题

使用场景:

1.简单的函数表达式,内部没有this引用,没有递归,事件绑定,解绑定

2.需要调用this,且this应与外层函数一致时(保证指向vue实例)

原型和原型链

原型:任何对象都有一个原型对象,这个原型对象由对象的内置属性proto指向它的构造函数的prototyoe指向的对象,即任何对象都是由一个构造函数创建的,被创建的对象都可以获得构造函数的prototype属性,这个构造函数就是原型

原型链:当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的proto隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的proto中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。

作用域和作用域链

作用域,就是变量起作用的区域(范围)。或者说,js代码执行时,查找变量的范围。

作用域链是指:当js编译器在寻找变量时,先在最近的作用域(花括号)里找,如果找不到,则抄上一级作用域(花括号)里找,依次类推,直到找到或者找不到为止。这就是作用域链。

深浅拷贝 区别以及如何进行深拷贝

区别:浅拷贝只能拷一层,更深层次对象级别拷贝的是地址;深拷贝拷贝多层,每一级别的数据都会拷贝

相同点:都是出现在引用类型里的说法

浅拷贝示例:

var arr1 = new Array(12,23,34)

Var arr2 = arr1;//这就是一个最简单的浅拷贝

如果要改变arr2所引用的数据:arr2[0]=100时,那么arr1[0]的值也是100。

可以使用递归实现深拷贝:

// 深拷贝封装成函数;

// 参数:源对象

// 返回值:复制出来的对象

promise promise都有哪几种状态 其执行状态

概述:Promise是异步编程的一种解决方案,从语法上讲,Promise是一个对象,可以获取异步操作的消息 作用: (1)、避免回调地狱的问题(2)、Promise对象提供了简洁的API,使得控制异步操作更加容易 Promise有三种状态:pendding //正在请求,rejected //失败,resolved //成功 基础用法:new Promise(function(resolve,reject){ })

数组去重的方法

①利用数组indexof方法

var arr = [1,1,2,2,3,3,3,4,5,6,6,6,7];

var arr1 = [];

for(var i=0; i<arr.length; i++){

if(arr1.indexOf(arr[i]) == -1){

arr1.push(arr[i]);

}

}

console.log(arr1);//1,2,3,4,5,6,7

②ES6的Set

function unique(arr) {

return new Set(arr)

}

const res = unique([1, 1, 2, 3, 3, 2])

console.log(res)

js如何创建数组,js数组都有哪些方法?

有两种创建方式:①字面量创建var arr=[];②构造函数类创建var arr1=new Array();

push 尾增 返回新长度

pop 尾删 删除的项目

unshift 头增 返回新长度

shift 头删 返回删除的项目

concat 数组拼接 返回新数组

join 数组转字符串 返回字符串

reverse 逆序

sort 按字符串UniCode码排序

map 对数组的每个元素做某个处理,参数是回调函数,并且有返回值

slice 复制

indexOf 查找数组中的元素,找到返回该元素下标, 没找到返回-1

splice 截取

filter 过滤

Array.of 填充单个值

Array.from 把类数组类型转换为数组

Array.keys 获取键名

Array.values 键值

find 返回符合的第一个成员

findIndex 返回符合的第一个成员的缩影

every 对数组中的每一项进行判断,若都符合则返回true,否则返回false

some 对数组中的每一项进行判断,若都不符合则返回false,否则返回true

reduce:将数组所有数值进行叠加返回

forEach 对数组的每个元素做某个处理,参数是回调函数

js字符串方法

concat 拼接两个字符串,返回新的字符串

indexOf 找不到返回-1 找到了返回下表

includes查找字符串中是否有包含子字符串,

listIndexOf 从后往前找

match 找到一个或者多个正则表达式的匹配

repeat 复制字符串指定次数

reolace查到匹配的字符串子串并替换

slice substr substring 从小标x开始 截取几个 返回新字符串 substring取前不取后

split 把字符串转换数组

startswith 查找是否已指定字符串开头 返回布尔值

toString 返回一个字符串

valueOf返回字符串对象的原始值

trim 去除空格

tolowerCase 字符串变成小写

toUpperCase 字符串变成大写

同一个数组,同样的限制条件,map和filter find返回值有什么区别

相同点:都不会改变原数组

不同点:

map返回值是一个新的数组,新数组中的元素为原始数组中的元素调用函数处理后的值。

find返回值:返回数组中符合条件的第一个元素的值!返回值不是数组!

filter返回值:返回一个新数组 是原数组中符合条件的所有元素。

延时器(宏任务微任务)

按照设定的时间,延迟程序的执行,并且只会执行一次

语法: setTimeout( 函数 , 延迟的时间 );

第一个参数,也是函数形式,来定义要执行的程序内容

第二个参数,定义延迟时间,单位是毫秒

宏任务微任务:这一次,彻底弄懂 JavaScript 执行机制 - 掘金

闭包 什么情况下会使用闭包

闭包:定义在一个函数内部的函数,并且这个内部函数能够访问到外层函数中定义的变量

使用场景:防抖函数

闭包的作用:

①让外部访问函数内部变量成为可能

②局部变量会常驻在内存中

③可以避免使用全局便变量,防止全局变量污染

缺点:会造成内存泄露(内存空间长期被占用,而不被释放)

函数节流和事件防抖

函数节流的意思是,不要调用太频繁;如每隔多少毫秒就调用一次

使用场景: 比如页面无数次的滚动时就会触发无数次的onscroll,但是我希望调用函数不要那么频繁

解决思路:只要页面一滚动,就启动一个定时器,在很多次滚动过程中,如果计时器还没有停,则不调用函数,如果计时器停了,那就函数就已经调用一次完毕,然后再重新启动一个定时器

let i = 0;
let myTimer = null;
​
window.onscroll = function(){   
    //无数次的滚动过程中,
    //1、如果计时器还没有停(myTimer!=null),则不执行下面的代码(即:直接return)。
    //2、如果计时器停了(myTimer==null),说明一次执行完毕,则重新再启动定时器。
    if(myTimer!=null){
        return;
    }
    //启动定时器,时间间隔是200ms。
    myTimer = setTimeout(()=>{
        console.log(i++);
        myTimer =null;
    },200);
}

事件防抖: 概念:有一些事件的触发比较频繁,但是,我们只希望这无数次的事件触发中,有部分事件是有效的(如:用户有短暂的停止时才调用函数)。特别是在触发一次,就发一次请求,会有无数次的抖动。

如: 键盘事件:onkeydown,onkeyup,onkeypress,oninput,都是按一次键,触发一次.触发非常频繁。

 使用场景:

搜索框(百度搜索框,淘宝,京东等等),每次用户输入内容都需要发一次请求,从后端拿到关键字对应的内容.

特别是输入汉字时:我们希望用户输入完成汉字时再触发(发送请求),但是,实际情况是:当用户输入一个字母时,就会触发一次事件(发送一次请求)

记住:用户的输入习惯:当输入完一个汉字后,或者若干个汉字后,会有短暂的停顿。而在连续输入字母的过程中,不会有停顿。事件防抖的思路就是利用这个短暂停。

解决思路:我们可以设置一个定时器,在每次输入时,先清除上一次的定时器,如果本次输入和上次输入之间的时间间隔大于我们设置的毫秒数时,上次输入的内容就会发送请求。
​
<!DOCTYPE html>

<html> <head> <meta charset="utf-8"> <title></title> <style type="text/css"> *{ margin: 0; padding: 0; } ul{ list-style: none; } </style> </head> <body> <input type="text" id="querystr"> <input type="submit" value="百度一下"> <ul id="search-list"> <li>运动鞋</li> <li>拖鞋</li> </ul> </body> </html> <script type="text/javascript">

let myTimer = null;
​
$("querystr").oninput = function(){
    //在每次输入时,先清除上一次的定时器,清除了定时器后,就不会发送请求了。
    //即本次输入和上次输入的间隔非常短(小于100ms)时,上次定时器的代码就不会执行,也就不会发请求了。
    //如果本次输入和上次输入之间的时间间隔大于100ms时,上次输入的内容就会发送请求。
    if(myTimer!=null){
        window.clearTimeout(myTimer);
    }
    myTimer = setTimeout(()=>{      
        let scriptDom = document.createElement("script");
    scriptDom.src = 'https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=' + this.value + '&json=1&p=3&sid=1438_24869_21080_18560_17001_25177_22160&req=2&bs=1%2B&pbs=1%2B&csor=2&pwd=1%3D&cb=f&_=1511334117083';
        document.body.appendChild(scriptDom);
    scriptDom.remove();
    },100);
}
​
function f(data){
    console.log(data);
    let htmlStr = "";
    data.s.forEach((item)=>{
        htmlStr+=`<li>${item}</li>`;
    });
    $("search-list").innerHTML = htmlStr;

​ ​

跨域的解决方案

可以使用jsonp来完成跨域,本质上是利用HTML标签的 src属性可以跨域的特性

js运行机制

从上到下开始执行,同步代码一直往下走,异步代码放到队列当中,然后继续走同步,同步走完之后,然后是事件循环,走事件队列,执行满足条件的回调,并根据情况决定执行完成之后是否注销回调

事件流,讲讲事件冒泡与事件捕获(事件委托是事件冒泡还是事件捕获,为什么)

当某个事件执行时,从子元素向父元素触发 或 从父元素向子元素触发 称为事件流

事件流分为三个阶段:捕获阶段,事件源,冒泡阶段

事件流的两种模式:

事件冒泡:从子元素向父元素触发 -->当某个事件触发时,同样的事件会向父元素触发。

事件捕获:从父元素向子元素触发

事件委托是利用事件的冒泡原理来实现的

原理:当我们想给很多个子标签添加同一个事件的时候,可以给它的父级元素添加对应的事件,当触发任意子元素时,会冒泡到父级元素里,这时绑定在父级元素的事件就会被触发,这就是事件代理(委托),委托他们的父级代为执行事件。

作用:代码简洁;减少浏览器内存的使用

cookie存在即合理,为什么存在这个?这个是请求服务端 还问为什么location能永久性存储 还问为什么location能永久性存储

cookie是在HTML4中使用的给客户端保存数据的,也可以和session配合实现跟踪浏览器用户身份, location 永久性存储是因为它是在硬盘种存储的

cookie,localStorage,sessionStorage 的区别**

(答案我自己删减过)相同点:

都是在客户端保存数据的,存储数据的类型都是字符串

不同点:

1、生命周期:

1)、cookie如果不设置有效期,那么就是临时存储(存储在内存中),是会话级别的,会话结束后,cookie也就失效了,如果设置了有效期,那么cookie存储在硬盘里,有效期到了,就自动消失了。

2)、localStorage的生命周期是永久的,除非主动删除数据,否则数据永远不会消失。

3)、sessionStorage,可以理解成没有设置有效期的cookie,也是会话级别的,只要浏览器窗口不关闭,数据就会一直存在

2、网络流量:cookie的数据每次都会发给服务器端,webstorage不会与服务器端通信,所以,webstorage更加节约网络流量

3、大小限制:cookie大小限制在4KB,非常小;webstorage在5M

4、安全性:WebStorage不会随着HTTP header发送到服务器端,所以安全性相对于cookie来说比较高一些,不会担心截获。

5、使用方便性上:WebStorage提供了一些方法,数据操作比cookie方便;

什么是发布者,订阅者模式

发布者-订阅者模式又称观察者模式:当一个对象(发布者)状态发生改变时,所有依赖它的对象(订阅者)都会跟着变化

vue中的双向数据绑定就是数据劫持结合发布者-订阅者模式实现的

map、set是什么

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值,可以简单的理解为无重复值的数组。可以存储不同类型的任何数据,经常被用来数组去重

Map结构提供了“键—值”的对应,是一种更完善的Hash(哈希)结构(键值对的集合)实现,Map结构中的键是唯一的;

相同点:都是属于能够保存很多数据的一个集合

讲讲Map 与 Set扩展:WeekMap与weekSet

WeakSet和Set结构类似,也是不重复的值的集合,但WeakSet的成员只能是对象。

es6面向对象 面向对象编程思想(说一下一对一和一对多)

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了;面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

面向对象就是把实物抽象化、面向过程就是从上向下的编程

es5和es6的继承 es5和es6的区别

ES5的继承时通过原型或构造函数机制来实现。子类继承父类的时候,先创造子类的实例对象this,然后再将父类的方法或者属性添加到this上面,然后再去继承原型链

ES6通过class关键字定义类,他的继承机制完全和es5不一样,先调用super方法将父类实例对象的属性和方法加到this上面,然后再用子类的构造函数修改this。在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例!

区别

1.ES5先创建子类,在实例化父类并添加到子类this中

2.ES6先创建父类,在实例化子集中通过调用super方法访问父级后,在通过修改this实现继承

冒泡排序

// 冒泡排序的核心理念就是把数组由小到大排序或者由大到小排序

//冒泡排序:本质就是两两交换

//将元素进行两两比较,一趟比较之后,一定会将最大的元素放到 最后的位置就像是气泡一样升起来

// 剩下的元素还会继续两两比较,找到次大值,直至结束

//n个数排序 需要排n-1趟

//i代表循环的趟数 N-1趟 ((数组长度-1) 到最后一个数就不用排虚了

var arr = [6,7,5,8,4,9,3,0,2,1];

var t = 0;

for(var i=0; i<arr.length-1; i++){

//j代表每趟交换的次数 N-i-1次 i代表的是第几趟

for(var j=0; j<arr.length-i-1;j++){

if(arr[j]>arr[j+1]){

t = arr[j];

arr[j] = arr[j+1];

arr[j+1] = t;

}

}

}

console.log(arr);

获取dom元素

通过id名来获取

document.getElementById("ID名"):返回单个元素

通过标签批量获取元素对象

document.getElementsByTagName("标签名"):返回数组

通过类名批量获取元素对象

document.getElementsByClassName("类名"):返回数组

通过name批量获取元素对象

document.getElementsByName("name名"):返回数组

通过ID,类,标签选择器获得对象

注意事项:只能在页面只有一个元素的时候使用

document.querySelector(选择器):返回单个元素对象

批量返回类,标签选择器获得对象

document.querySelectorAll("选择器"):返回数组

如有两个字符串的版本号比如V2.12.0和V2.7.0,如何比对它两的大小

Vx(主).x(子).x(修正)

主版本: 变化了,1/3的API发生巨变 , 使用方式变化了

子版本: API没有删减,使用方式没变化,内部实现发生了变化

修正版: 什么都没变,处理一下bug

V6.8.0 稳定

V6.9.1 非稳定版

beta 测试

rc 、alpha测试稳定

请求方式get和post的区别

get请求的数据会暴露在地址栏中,而post请求则不会

post的安全性比get的高,post传输的数据比get大

getTime,parseInt,parFloat

getTime 返回自从时间戳1970-01-01到现在时间的毫秒数

parseInt 目的是把里面转换成整数

parFloat 浮点型转换

请解释—下JavaScript的同源策略

同源是指:协议、域名、端口号——完全相同。

同源策略是一种安全协议,指一段脚本只能读取同一来源的窗口和文档的属性。

协议:http/https协议

域名:ip地址或是ip地址对应的域名

端口号:80/443默认端口号

如何中断 ajax 请求

一种是设置超时时间让ajax自动断开,另一种为手动去停止ajax请求,其核心是调用XMLHttpRequest对象上的abort方法

http请求过程

一、浏览器根据域名解析IP地址

二、浏览器与WEB服务器建立一个TCP连接

三、浏览器给WEB服务器发送一个HTTP请求

四、服务器响应HTTP请求,浏览器得到HTML

五、浏览器解析HTML,并请求HTML代码中的资源

六、关闭TCP连接,浏览器对页面进行渲染呈现给用户

简单解释一下什么是CSRF攻击,并给出常用的防范措施

CSRF 攻击:中文名称:跨站请求伪造,可以理解为攻击者盗用了你的身份,以你的

名义发送恶意请求,比如:以你名义发送邮件、发消息、购买商品,虚拟货币转账

等。

防御手段:

o 验证请求来源地址;

o 关键操作添加验证码;

o 在请求地址添加 token 并验证

你所了解的CSS 预处理器都有哪些?它们与传统CSS相比的优点是什么?

预处理器的定义:CSS预处理器是基于CSS的一种语言,通过工具编译成CSS,添加了很多CSS不具备的特性,能提升CSS文件的组织方式。

常见的css预处理器有less和sass

优点:①、能够更好的组织CSS代码 ②、提高代码复用性 ③、提高代码可维护性

forEach和map的区别

相同点:
  • 都是循环遍历数组中的每一项

  • forEach和map方法里每次执行匿名函数都支持3个参数,参数分别是item(当前每一项)、index(索引值)、arr(原数组)

  • 匿名函数中的this都是指向window

  • 只能遍历数组

    不同点

forEach() 没有返回值。map() 有返回值,可以return 出来。

除了闭包还有什么可以 造成内存泄露

意外的全局变量,被遗忘的定时器,没有被清理的DOM元素引用,

内存泄漏:内存空间长期被占用,而不被释放

闭包:函数内部定义函数,并且这个内部函数能够访问到外层函数中定义的变量(准确的说是声明的参数和变量)

特点

1、让外部访问函数内部变量成为可能。

2、局部变量会常驻在内存中。

3、可以避免使用全局变量,防止全局变量污染。

4、会造成内存泄漏(内存空间长期被占用,而不被释放)

Bom

BOM 是浏览器对象模型,提供了独立于内容而与浏览器窗口进行交互的对象

BOM对象有哪些:

1、window对象 ,是JS的最顶层对象,其他的BOM对象都是window对象的属性;

2、document对象,文档对象;

3、location对象,浏览器当前URL信息;

4、navigator对象,浏览器本身信息;

5、screen对象,客户端屏幕信息;

6、history对象,浏览器访问历史信息;

事件委托

事件委托是利用事件的冒泡原理来实现的

当我们想给很多个子标签添加同一个事件的时候,可以委托他们的父级代为执行事件

作用:代码简洁;减少浏览器内存的使用

Dom创建,添加,复制,删除节点

创建):document.createElement(‘tagName’)

添加):在子节点后追加元素node.appendChild(child) node-父级 child-子级 在某一指定元素前添加元素node.insertBefore(child,指定元素) node-父级 child-子级

删除):node.removeChild(child) 返回删除的节点

复制(克隆)节点:node.cloneNode()

display:none visibility:hidder的区别

display: none隐藏后的元素不占据任何空间

而visibility: hidden隐藏后的元素空间依旧保留

Promise和 Callback有什么区别?

Callback:一个被当做参数的函数,就是回调函数

栗子:

function fn1(fn2) {
        setTimeout(function() {
            console.log(1);
            fn2 && typeof fn2 === 'function' && fn2();
        }, 1000);
    }
function fn2(){
     console.log(2);
}
​
fn1();
fn2()

Promise:Promise 是一种异步操作的解决方案,将写法复杂的回调函数和监听事件的异

步操作,用同步代码的形式表达出来,解决了回调地狱(一个调一个,一个调一个)

浏览器的内核分别是什么?(背诵主要的单词既可),经常遇到的浏览器的兼容性有哪些?

主流浏览器 IE,火狐,谷歌, 欧朋浏览器。

主流浏览器内核 IE内核:Trident;Window10发布后,IE将其内置浏览器命名为Edge(原名斯巴达),使用了新内核Edge引擎。 Firefox内核:GeckoSafari内核:Webkit;苹果公司自己的内核,也是苹果的Safari浏览器使用的内核。 Chrome内核:Blink;有Google和Opera Software开发的浏览器排版引擎,2013年4月发布,现在Chrome内核:Blink。Blink其实是webkit的分支,大部分国产浏览器最新版都采用Blink内核。 Opera内核是:13年之前采用Presto内核;13年之后采用Blink内核。

浏览器的兼容问题:(写了两个最常见的,还有其他的,有需要可自行研究)

①所有浏览器的字体不统一 解决:给body统一设置字体 body{font-family:微软雅黑;}

②浏览器默认的margin和padding不同 解决:*{margin:0;padding:0;}

简述同步和异步的区别?

同步:一般指的是在代码运行的过程中,从上到下逐步运行代码,每一部分代码运行完成之后,下面的代码才能开始运行

异步:就是当一个异步被调用,不用等到得到结果,就可以继续执行后续操作

前端开发中常见的异步一般常见的包括:setTimeoutsetIntervalajax

宏任务与微任务

宏任务:新程序或者子程序,直接被执行的,事件的回调函数,定时器等

微任务:promist.then(),object.observe等

执行完同步代码后, 首先执行宏任务,因为script打通,在调用栈为空后,执行微任务,微任务清空后,浏览器可能会重新渲染,重新渲染后,执行宏任务,如果宏任务其中一轮结束后,事件循环发现微任务,则继续执行微任务,清空后执行宏任务,这样循环,直到宏任务清空

axios和fecth的区别

axios 是基于promise 本质上是对原生xhr的封装 只不过它是promise实现版本,符合es的最新规范

fetch 不是ajax的进一步分装,而是原生js 没有使用xhr对象

ES6总结

1、es5和es6的区别,说一下你所知道的es6

ECMAScript5,即ES5,是ECMAScript的第五次修订,于2009年完成标准化ECMAScript6,即ES6,是ECMAScript的第六次修订,于2015年完成,相对于ES5更加简洁,提高了开发效率ES6新增的一些特性:

  • let声明变量和const声明常量,两个都有块级作用域ES5中是没有块级作用域的,并且var有变量提升,在let中,使用的变量一定要进行声明

  • 箭头函数ES6中的函数定义不再使用关键字function(),而是利用了()=>来进行定义

  • 模板字符串模板字符串是增强版的字符串,用反引号(`)标识,可以当作普通字符串使用,也可以用来定义多行字符串

  • 解构赋值ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值

  • for of循环for...of循环可以遍历数组、Set和Map结构、某些类似数组的对象、对象,以及字符串

  • import、export导入导出ES6标准中,Js原生支持模块(module)。将JS代码分割成不同功能的小块进行模块化,将不同功能的代码分别写在不同文件中,各模块只需导出公共接口部分,然后通过模块的导入的方式可以在其他地方使用

  • set数据结构Set数据结构,类似数组。所有的数据都是唯一的,没有重复的值。它本身是一个构造函数

  • ... 展开运算符可以将数组或对象里面的值展开;还可以将多个值收集为一个变量

  • class 类的继承ES6中不再像ES5一样使用原型链实现继承,而是引入Class这个概念

  • async、await使用 async/await, 简化promise操作,可以通过编写形似同步的代码来处理异步流程, 提高代码的简洁性和可读性async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

  • Promise是异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更合理、强大

2、var、let、const之间的区别

var声明变量可以重复声明,而let不可以重复声明 var是不受限于块级的,而let是受限于块级 var声明的变量会挂到window的属性上,而let不会 var可以在声明的上面访问变量,而let有暂存死区,在声明的上面访问变量会报错 const声明之后必须赋值,否则会报错 const定义不可变的量,改变了就会报错

3、使用箭头函数应注意什么?

(1)用了箭头函数,this就不是指向window,而是父级(指向是可变的) (2)不能够使用arguments对象 (3)不能用作构造函数,这就是说不能够使用new命令,否则会抛出一个错误

4、介绍下 Set、Map的区别?

应用场景Set用于数据重组,Map用于数据储存Set:  (1)成员不能重复 (2)只有键值没有键名,类似数组 (3)可以遍历,方法有add, delete,has

属性:size获取元素的长度
方法:add(ele) 向Set中添加元素
              delete(ele)  删除
              has(ele)  是否包含某个元素,返回布尔值
              clear(): 清空set集合

Map: (1)本质上是健值对的集合,类似集合 (2)可以遍历,可以跟各种数据格式转换

   map也是一种数据结构,类似于对象,
   Map属性:size
    设置键值对 set(key,value)
    获取       get(key)
    删除       delete(key)
    清空       clear()
    包含       has(key)    返回布尔值

5、ECMAScript 6 怎么写 class ,为何会出现 class?

ES6的class可以看作是一个语法糖,它的绝大部分功能ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程(OOP)的语法

//定义类
class Point { 
  constructor(x,y) { 
      //构造方法
       this.x = x; //this关键字代表实例对象
       this.y = y; 
  } toString() {
       return '(' + this.x + ',' + this.y + ')'; 
  }
}

6、Promise构造函数是同步执行还是异步执行,那么 then 方法呢?

promise构造函数是同步执行的,then方法是异步执行的

7、promise有几种状态,什么时候会进入catch?

三个状态:
pending、fulfilled、reject
两个过程:
padding -> fulfilled、padding -> rejected当pending为rejectd时,会进入catch

8、使用结构赋值,实现两个变量的值的交换

let a = 1;let b = 2;
[a,b] = [b,a];

9、下面Set结构,打印出的size值是多少

let s = newSet();
s.add([1]);s.add([1]);
console.log(s.size); //2
两个数组[1]并不是同一个值,它们分别定义的数组,在内存中分别对应着不同的存储地址,因此并不是相同的值都能存储到Set结构中,所以size为2

10、Promise 中reject 和 catch 处理上有什么区别

reject 是用来抛出异常,catch 是用来处理异常 reject 是 Promise 的方法,而 catch 是 Promise 实例的方法 reject后的东西,一定会进入then中的第二个回调,如果then中没有写第二个回调,则进入catch 网络异常(比如断网),会直接进入catch而不会进入then的第二个回调

11、如何使用Set去重

let arr = [12,43,23,43,68,12];
let item = [...new Set(arr)];
console.log(item);//[12, 43, 23, 68]

12、将下面for循环改成for of形式

let arr = [11,22,33,44,55];
let sum = 0;
for(let i=0;i<arr.length;i++){
    sum += arr[i];
}

答案:

let arr = [11,22,33,44,55];
let sum = 0;
for(value of arr){
    sum += value;
}

13、forEach、for in、for of三者区别

forEach更多的用来遍历数组 for in 一般常用来遍历对象或json for of数组对象都可以遍历,遍历对象需要通过和Object.keys() for in循环出的是key,for of循环出的是value

14、说说你对Promise的理解

Promise 是异步编程的一种解决方案。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。但是无法获取到pending状态,在promise中接受两个内置参数分别是resolve(成功)和reject(失败),Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。

then方法可以传递两个回调函数第一个是成功,第二个是失败,失败回调也可以使用promise的catch方法回调,promise还有一个强大的功能那就是all方法可以组合多个promise实例,包装成一个新的 Promise 实例。

15、ES6 中新增的数据类型有哪些

Set 和 Map 都是 ES6 中新增的数据结构,是对当前 JS 数组和对象这两种重要数据结构的扩展。由于是新增的数据结构

  1. Set 类似于数组,但数组可以允许元素重复,Set 不允许元素重复

  2. Map 类似于对象,但普通对象的 key

VUE面试题(带数字,带序号)

1、v-show 与 v-if 有什么区别?

相同点: v-show和 v-if都是 控制 dom元素 的 显示和隐藏 的。

不同点: 1、原理:

           v-show是通过控制元素的样式属性display的值,来完成显示和隐藏;
            v-if是通过对dom元素的添加和删除,完成显示和隐藏

  2、使用场景:由原理(做法)得出使用场景的区别

            v-show:使用在dom元素频繁切换的场景
            v-if:当dom元素的切换不频繁,可以使用。特别是,首次元素处于隐藏的情况下。

2、说说你对 SPA 单页面的理解,它的优缺点分别是什么?

1)、首先:

SPA的英文是single-page application ,也就是说整个项目中只有一个页面

    单页面应用的实现思路:  就是在 Web 页面初始化时加载所有的 HTML、JavaScript 和 CSS,页面的内容的变化,靠动态操作DOM。

2)、其次:

说说它的优点和缺点:

它的优点有点:

  第一点:**局部刷新。**用户体验好、快,内容的改变不需要重新加载整个页面。

  第二点:**服务器的压力小。**基于上面一点,SPA 对服务器的压力小;

  第三点:**前后端职责分离。**架构清晰,前端进行交互逻辑,后端负责数据处理;

它的缺点也有点:

  第一点:**初次加载耗时多。**为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;

  第二点:**前进后退路由管理问题。**由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理(这是vue-router做的);

  第三点:**SEO 难度较大。**由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

3、怎样理解 Vue 的单向数据流

首先

  单项数据流是发生在**父组件朝子组件传值的时候**,所有的 prop 使得其父子 prop 之间形成了一个单向下行绑定。
   也就是说:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态。

而且

   每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。所以,在一个子组件内部改变 prop,Vue 会在浏览器的控制台中发出警告。

4、computed 和 watch 的区别和运用的场景?

首先(相同点):

computed 和 watch都可以实现数据的监听。

其次(区别):

第一(本质): computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,当依赖的属性值发生改变时,才会重新计算 computed 的值,它可以设置getter和setter. watch: 更多的是观察的作用,每当监听的数据变化时都会执行回调进行后续操作,它只能设置getter.

第二(运用场景): computed:当我们需要进行数值计算,并且依赖于其它数据时,使用 computed。 watch:当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch。

5、直接用下标(索引)给一个数组项赋值,Vue 能检测到变化吗?

不能

因为,vue再做数据劫持时,只对数组做了劫持,没有对数组的每一项进行劫持。所以,用下标的方式修改数组项时,vue不能检测到数据的变化。

为了解决他们,Vue 也提供了操作方法: Vue.set Vue.set(数组名,下标,新值)

6、谈谈你对 Vue 生命周期的理解?

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

2)、各个生命周期阶段及其钩子函数

vue的生命周期核心经历了四个阶段,在四个阶段的前后分别有两个钩子函数。

第一阶段:数据挂载阶段:把配置项data中的属性,赋给了vue对象本身,并做了数据劫持。

                 该阶段前后的两个钩子函数:beforeCreate和created

第二阶段:模板渲染阶段:把vue对象的数据渲染到模板上。

                 该阶段前后的两个钩子函数:beforeMount和mounted

第三阶段:组件更新阶段:当数据发送变化时,组件会进行重新渲染,所以,准确的说是,组件重新渲染阶段。

                 该阶段前后的两个钩子函数:beforeUpdate和updated

第四阶段:组件销毁阶段:组件销毁。

                 该阶段前后的两个钩子函数:beforeDestroy和destroyed

可以补充:

当使用keep-alive包裹组件时,会有组件激活和停用之说,这两个钩子函数分别是:activited和deactivated

还可以补充:

项目场景,如:发送请求,如:父组件修改子组件的props引起子组件的更新等等。

详情请参考: 彻底理解vue的钩子函数,vue的生命周期理解,什么是vue的生命周期,钩子函数_vue生命周期-CSDN博客

7、Vue 的父组件和子组件生命周期钩子函数执行顺序?

Vue 的父子组件钩子函数的执行顺序可以归类为4个 部分:

第一部分:首次加载渲染:

   父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

第二部分:父组件修改子组件的props值时: 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

第三部分:父组件修改的数据跟子组件没有关系时:

不会影响子组件   父 beforeUpdate -> 父 updated

第四部分:销毁过程:

父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

8、在哪个生命周期内调用异步请求?

答:大部分时候,会在created发送请求。 因为, 1)、此时data已经挂载到vue实例了,放在beforeCreate里太早,vue实例没有数据,放在mounted里太晚。 2)、放在 created 中有助于一致性,因为ssr不支持 beforeMount 、mounted 钩子函数。 Created的使用场景:如果组件的初始数据来自于后端,那就在created里发送请求

9、组件中 data 为什么是一个函数?

如果不用函数,即:data是引用各类型,那么所有组件对象的data会指向同一块内存区域,导致数据之间互相影响。

使用函数时,每次实例化组件时(<标签名>),调用(data)函数,return一个新(new)的data对象。这样每个组件实例的data所指向的内存空间是独立的,data的 属性值不会互相影响。

10、v-model 的原理?

v-model 指令主要用在表单元素上实现数据双向绑定的。 例如:input、textarea、select 等创建双向数据绑定,本质上不过是语法糖,其实v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:

  • text 和 textarea 元素使用 value 属性和 input 事件;

  • checkbox 和 radio 使用 checked 属性和 change 事件;

  • select 字段将 value 作为 prop 并将 change 作为事件。

11、Vue 实现数据绑定的原理

vue数据绑定是通过 数据劫持观察者模式 的方式来实现的。

 1、数据劫持:使用Object.defineProperty();

   目的是:**感知属性的变化**。当给属性赋值时,程序是能够感知的(知道的)。如果知道的话,就可以控制属性值的有效范围,也可以改变其它属性的值等。

  当你把一个普通的 JavaScript 对象(json)传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

Object.defineProperty()函数:JavaScript中的Object.defineProperty()函数-CSDN博客

 2、观察者模式(发布订阅模式):
     目的是:**当属性发生变化时,使用该数据地方(模板,计算属性,watch等等)跟着变化**

12、Vue 组件间通信有哪几种方式?

    Vue 组件间通信主要指以下 3 类通信:父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式:

(1)props / $emit : 父子组件通信

(2)ref: 适用 父子组件通信 (3)EventBus ($emit / $on): 适用于 父子、隔代、兄弟组件通信

(4)$root:集中管理,适用于所有场景

(5)Vuex 适用于所有场景

13、谈谈你对 keep-alive 的了解?

keep-alive 可以缓存其它组件及其的组件的状态,避免了组件的频繁创建和销毁。

它有个特性:

1)、用于缓存组件,一般结合路由和动态组件一起使用。

2)、提供 include 和 exclude 属性。两者都支持字符串或正则表达式, include 表示只有名称匹配的组件会被缓存,exclude 表示任何名称匹配的组件都不会被缓存 ,其中 exclude 的优先级比 include 高;

3)、对应两个钩子函数 activated 和 deactivated ,当组件被激活时,触发钩子函数 activated,当组件被移除时,触发钩子函数 deactivated。

14、Vue 中的 key 有什么作用?

首先

     key 是Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。

再说一下,diff算法的执行过程:

1)、oldCh 和 newCh 各有两个头尾的变量 oldStartIndex、oldEndIndex 和 newStartIndex、newEndIndex, 2)、在新节点和旧节点进行两两对比时,共有4种比较方式: a.newStartIndex 和oldStartIndex 、 b.newEndIndex 和 oldEndIndex 、 c.newStartIndex 和 oldEndIndex 、 d.newEndIndex 和 oldStartIndex, 如果以上 4 种比较都没匹配,如果设置了key,就会用 key 再进行比较。

15、你对 Vue 项目进行哪些优化?

第一个方面:代码层面的优化 v-if 和 v-show 区分使用场景 computed 和 watch 区分使用场景 v-for 遍历必须为 item 添加 key,且避免同时使用 v-if 长列表性能优化 事件的销毁 图片资源懒加载 路由懒加载 第三方插件的按需引入 优化无限列表性能 服务端渲染 SSR or 预渲染 第二个方面:Webpack 层面的优化 Webpack 对图片进行压缩 减少 ES6 转为 ES5 的冗余代码 提取公共代码 模板预编译 提取组件的 CSS 优化 SourceMap 构建结果输出分析 Vue 项目的编译优化 第三个方面:基础的 Web 技术的优化 开启 gzip 压缩 浏览器缓存 CDN 的使用 使用 Chrome Performance 查找性能瓶颈

16、动态路由传参2种方式params与query

1)、params

声明式:
<router-link :to="{ name: 'user', params: { id: 123 }}">User</router-link>
编程式:
$router.push({ name: 'user', params: { id: '123' }})
 //模板里的写法:
 $route.params.参数名
 
 //脚本里的写法:
 this.$route.params.参数名

动态路由匹配也行。

1)、路由配置:{ path: '/user/:id', component: User }

2)、传参:

//声明式 <router-link to="/user/01001">用户01001的信息</router-link> //编程式 $router.push("/user/01001");

3)、接值:

//模板里的写法: $route.params.参数名

//脚本里的写法: this.$route.params.参数名

2)、query

传:

// 带查询参数,变成 /register?plan=private
$router.push({ path: '/register', query: { plan: 'private' }})
注意:如果提供了 path,那么params 会被忽略

接:

 //模板里的写法:
 $route.query.参数名 
 //脚本里的写法:
 this.$route.query.参数名

17、vue实例和vue组件写法的区别

1、 data是个函数(面试题) 一个组件的 data 选项必须是一个函数,且要有返回object,只有这样,每个实例(vue组件对象)就可以维护一份被返回对象的独立的拷贝,否则组件复用时,数据相互影响,也就是说,组件的作用域是独立的。 2、组件模板(html代码)只能有一个根标签 3、组件名不可和html官方的标签名同名 4、组件没有el选项,只有根实例存在el 5、书写:组件名如果驼峰,那么使用时,用短横线(羊肉串的写法)

18、谈谈你对vueX的理解

1、vueX是干什么的

	   vuex能够**保存全局数据**,供整个应用使用,可以在组件之间传递数据。

       vuex保存的数据是**响应式的**	

       vuex保存的数据可以**跟踪状态的变化**

2、vueX的核心概念

     state   :      数据仓库 ,存储所有的  **共享数据**  ,相当于vue组件里的data
	getter : 在state的基础上  **派生的数据**, 相当于vue组件里 computed
	mutation:修改state的数据时,用mutation,这与**跟踪状态** 有关系
	 action:解决mutation里只能有同步代码的问题,action里可以有**异步代码**

3、vueX的数据流

      组件里 dispatch(派发)vueX中的 action,action里commit(提交)mutation,mutation里修改state。state被修改后,会响应式到组件上。

     ![1599989118212](C:\Users\31759\AppData\Roaming\Typora\typora-user-images\1599989118212.png)

19、vue路由懒加载

vue的SPA(Single Page Application)应用里,当(webpack)打包构建时,会把所有的js打在一起,JavaScript 包会变得非常大,并在第一次请求时全部下载完毕,影响页面加载(性能)。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

目前有三种方式实现路由组件的懒加载,分别是:

  • vue异步组件

  • es 的 import()

  • webpack的require.ensure()

1)、 vue异步组件

把路由配置,进行修改

{

    path: '/shopcar',

    name: 'shopcar',

    component: resolve => require(['@/pages/ShopCar'],resolve)

},

1)、运行是打开chrome的network,就会看到进入路由 /shopcar 时,会多出另外 一个js文件。一般是0.js或者1.js

2)、用npm run build打包时,wepback就会多打了一个 js文件(如:0.b5a82d6947b2e30edcc8.js),这个js文件就是把ShopCar文件进行了单独打包。同样的在network里,就会看到进入路由 /shopcar 时,多出一个单独的js文件的请求

注:这种方式,webpack会把每个异步组件单独打成一个js文件。

2)、es的import()

主要是把原来的引入方式进行修改 ,路由配置就不用改了:

1、不指定webpackChunkName,每个组件单独打一个js文件

原来是:import ShopCar from '@/pages/ShopCar'

修改后:const ShopCar = () => import('@/pages/ShopCar');

修改后的做法是定义了一个函数,由于函数不调用不执行,所有,一开始时,并不会引入该组件,只有路由跳转时才会调用该函数。

2、指定webpackChunkName,相同的webpackChunkName就会打在同一个js文件里

1)、以下两个组件的webpackChunkName相同,所以,打在一个js文件里

const ShopCar = () => import(/* webpackChunkName: 'demot' */ '@/pages/ShopCar');

const GoodsDetailPage = () => import(/* webpackChunkName: 'demot' */ '@/pages/GoodsDetailPage');

2)、下面这个组件的webpackChunkName和上面两个不一样,所以,单独打在一个js文件里

const Login = () => import(/* webpackChunkName: 'demoty' */ '@/pages/Login');

3)、webpack的require.ensure()

这种方式,只改路由配置即可。

如:

{

    path: '/GoodsDetailPage',

    name: 'GoodsDetailPage',

    component: r => require.ensure([], () => r(require('@/pages/GoodsDetailPage')), 'demot')

},

{

    path: '/Login',

    name: 'Login',

    component: r => require.ensure([], () => r(require('@/pages/Login')), 'demot')

},

{

    path: '/shopcar',

    name: 'shopcar',
    
    component: r => require.ensure([], () => r(require('@/pages/ShopCar')), 'demoty')

},

以上代码中,我把Login和GoodsDetailPage使用了相同的chunkName

20、MV*(MVC,MVP,MVVM)

答:

  这是项目的架构模式。优点:耦合度低、重用性高、生命周期成本低、部署快、可维护性高、有利软件工程化管理。

**1、MVC是从后端演变后的项目架构模式。**    

    M:model,模型,主要完成业务功能,在数据库相关的项目中,数据库的增删改查属于模型(重点)。
    V:view,视图,主要负责数据的显示
    C:controller,控制器,主要负责每个业务的核心流程,在项目中体现在路由以及中间件上。

2、MVP

     MVP是由MVC演变过来的。

     P:Presenter  代替里C。

    在MVP中View并不直接使用Model,而在MVC中View可以绕过Controller从直接Model中读取数据。

3、MVVM

   MVVM是Model-View-ViewModel的缩写,MVVM模式把Presenter改名为ViewModel,基本与MVP模式相似。 唯一区别是:**MVVM采用数据双向绑定的方式**

   在做vue的开发时,程序员写的代码和vue框架本身合起来是属于MVVM模式。

21、你了解Vue.js吗?

这种题,一般是比较难以回答的,问得很模糊,

 如果不想多说,那就直接回答:了解并做过五个项目。

 如果想回答详细的话,参考思路如下:

 1)、vueJS是基于MVVM的JS框架

 2)、有(常见的)13个指令:

 3)、有(常见的)8个配置项:el,data,computed,watch,components,filter,directives,mixins

 4)、vue实例生命周期分为四个阶段,八个生命周期函数

 5)、vue做项目时会用到的全家桶技术:vueJS框架,vueX,vue-router,aixos,vant组件库等等

 6)、我用vue框架一共做过五个项目。

………………………………

22、vue-router的两种路由模式的区别

路由模式分为两种:hash和history;通过设置vueRouter对象的mode属性来完成修改。

区别:

1)、外观上

hash模式时,路径上有#。

history模式时,路径上没有#。

2)、原理上

   **hash模式通过修改location.href来完成**

   使用锚点连接的思路,使用hash模式不会给后端发请求。当然,在hash值变化时,会同时触发window对象的onhashchange事件,并可以通过事件对象的oldURL属性和newURL属性 得到新旧URL。
<body>
    <div>
        <a href="#p01" >p01</a><br/>
        <a href="#p02" >p02</a><br/>
        <a href="#p03" >p03</a>
    </div>
    <br/><br/><br/><br/><br/><br/>
    <a name="p01">我是第一个p</a>
    <br/><br/><br/><br/><br/><br/>
    <a name="p02">我是第二个p</a>
    <br/><br/><br/><br/><br/><br/>
    <a name="p03">我是第三个p</a>
    <br/><br/><br/><br/><br/><br/>
</body>
</html>
<script>
window.onhashchange = function(event){
    console.log("旧url",event.oldURL);
    console.log("新的url",event.newURL);
}
</script>

2)、**通过修改history.pushState来完成**

如:

window.history.pushState(null,null,"p01.html");
window.location.reload();//想测试的话,尽量加上这句话,要不然,地址变了,但是页面并没有出现。
   history模式会给后端发请求(如果刷新当前页面的话),一旦,后端和前端提供了同样的路径,那么,浏览器的请求结果就会有问题,到底是后端的资源还是前端的资源(不同的后端处理思路不停),还好,我们一般在后端apiserver的请求路径的前面习惯性的有个 /api。

     所以,由于不同服务端的处理思路不同。所以,需要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 `index.html` 页面(单页面),这个页面就是你 app 依赖的页面。否则,就会返回404。

    你可以改成history的模式,测试一下,如果刷新当前页面,那么,浏览器会朝后端发送请求(给当前路径)。

23、Vue路由守卫的三种方式,及其钩子函数和参数

1)、全局守卫
全局守卫有前置守卫和后置守卫,是vueRouter对象的两个钩子函数,分别是 beforeEach和afterEach。

**前置守卫:**
router.beforeEach((to, from, next) => {
 	//	to: 目标路由
 	//	from: 当前路由

 	// next() 跳转  一定要调用
  	next(false);//不让走
	next(true);//继续前行
 	next('/login')//走哪
	next({path:'/detail/2',params:{},query:{}})//带点货
 }

后置守卫:

router.afterEach((to,from)=>{
   //全局后置守卫业务
})

如果能够回答上过程,肯定有加分:

//过程: 1、请求一个路径:如:/Index 2、经历前置守卫 决定了能去哪个路径 3、根据去的路径,找对应component(路由配置) 4、经过后置守卫 5、创建组件

2)、路由独享守卫

写在路由配置里。钩子函数名:beforeEnter,只有前置守卫

如:

// src/router/index.js
{
  path: '/user',
  component: User,
  beforeEnter: (to,from,next)=>{ //路由独享守卫 前置 
    console.log('路由独享守卫');
    if(Math.random()<.5){
      next()
    }else{
      next('/login')
    }
   }
 }
3)、组件内部守卫

写在组件对象里。分别有前置守卫,后置守卫,路由改变守卫(当前组件被复用的情况,不是路径改变)三个钩子函数。

export default{
    data(){return {}}
  ……………………  

    //组件内部钩子
    beforeRouteEnter (to, from, next) {//前置
      // 不!能!获取组件实例 `this`
      // 因为当守卫执行前,组件实例还没被创建
    },
    beforeRouteUpdate (to, from, next) {
      // 在当前路由改变,但是该组件被复用时调用
      // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
      // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
      // 可以访问组件实例 `this`
    },
    beforeRouteLeave (to, from, next) {//后置
      // 导航离开该组件的对应路由时调用
      // 可以访问组件实例 `this`
    }
}

24、为何vue采用异步渲染?

1、vue采用异步队列渲染是为了提高性能,在异步队里会去掉重复的无效的渲染。

    当vue中的数据发生改变后,vue框架会把该数据引起的dom更新放入异步队列( 缓冲在同一事件循环中发生的所有数据变更 ),进行排队。 如果同一个 watcher 被多次触发,只会被推入到队列中一次 。 这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的 。

2、如果不采用 异步渲染,而是在数据发生改变后,立即更新dom,如果有重复无效的渲染,那么,就会造成性能的浪费。

25、nextTick实现原理?

1)、为什么用Vue.nextTick()

 首先,JS是单线程的,那么,它如何处理异步操作。
  • 所有同步任务都在主线程上执行,形成一个执行栈

  • 主线程之外,会存在一个任务队列,只要异步任务有了结果,就在任务队列中放置一个事件(所以,也叫事件队列),进行排队(处于等待状态)。

  • 当执行栈中的所有同步任务执行完后,就会读取任务队列(事件队列)中的任务(事件)。即:任务队列中的任务就结束了等待状态,进入执行栈。

  • 主线程不断重复第三步。直到任务队列和执行栈里的代码执行完毕。

    了解一个事件循环: javascript的事件循环(event loop)_javascript的事件循环(event loop)_jiang7701037的博客-csdn博客-CSDN博客

    其次,vue更新DOM的思路。使用的就是异步更新队列,所以,就使用了事件循环。目的是提高性能,避免无效的重复的DOM更新。即:vue中更新数据后,并不会立即更新DOM,而是把数据引起的DOM更新放入到异步更新队列里。等待下次事件循环(tick),并在两个tick之间进行UI渲染。这样程序员就不能在更改数据后,立即获取更新后的DOM,也不知道什么时候DOM能够更新。基于此,vue提供了nextTick函数。让程序员操作更新后DOM的代码放入到nextTick的回调函数里。由nextTick内部,在更新完DOM后,调用回调函数。

示例代码:

this.msg = "hello"
this.$nextTick(()=>{
     操作更新后DOM的代码。
});

2)、什么是Vue.nextTick()

Vue.nextTick的代码思路示意

function nextTick(cb){
	//DOM 更新
	
	cb();
}

那么,vue是如何知道DOM更新了?

  • MutationObserver:这是HTML5新增的API。用于监视DOM变动的接口,它可以监听一个DOM对象上发生的子节点删除、属性修改、文本内容修改等

  • 另外,考虑到,微任务比宏任务耗时少,浏览器的兼容性。所以,vue中延迟调用优先级如下: Promise > MutationObserver > setImmediate > setTimeout

3)、应用场景:

    在Vue生命周期的created()钩子函数里,如果要进行DOM操作,一定要把DOM操作放在Vue.nextTick()的回调函数中。

    在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中。

26、何时需要使用beforeDestroy?

    总体来说,需要清除的是:当前组件不会自动销毁的数据(不属于当前组件的数据),并且该数据只是在当前组件里使用。

1)、清除定时器(定时器是window对象的,不主动清除,是不会清除的)

2)、$on方法,那需要在组件销毁前解绑。($on虽然属于Vue的实例方法,但是,这个实例很有可能不是当前vue组件(如:事件总线中的用法))

3)、解除事件的绑定 scroll mousemove (这些事件是绑定在window对象或者document对象上的)

27、Vue中v-html会导致哪些问题

1)、可能会导致XSS攻击。因为V-html更新的是元素的 innerHTML 。内容按普通 HTML 插入, 不会作为 Vue 模板进行编译 。

2)、在单文件组件里,scoped 的样式不会应用在 v-html 内部。因为那部分 HTML 没有被 Vue 的模板编译器处理。怎么解决呢?如果你希望针对 v-html 的内容设置带······作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局 <style>元素手动设置类似 BEM 的作用域策略。

3)、后台返回的html片段,以及css样式和js,但是返回的js是不执行的,因为浏览器在渲染的时候并没有将js渲染,这时要在$nextTick中动态创建script标签并插入

29、为什么v-for与v-if不能连用

  应该说,建议不要连用,或者说,在循环时,通过if只能拿到少部分数据时,建议不要使用。

  原因: v-for比v-if优先级高,如果遍历的数组元素个数比较多,但是满足v-if条件比较少的情况下。会浪费性能。而且,每次刷新页面时,都会执行这样性能不高的代码。

  解决:可以在computed里循环数组,通过filter的方式,过滤出需要的数据。而computed是有缓存的,这样可以节约内存
如:
      computed:{
          isCompleteTask:function(){
              return this.todos.filter(item=>item.isComplete);
          }          
      }

30、如何自定义v-model

答: 使用vue组件中的选项model。

    首先,官方组件里可以使用v-model。而且,vue框架针对官方组件(文本框,单选钮,复选框,下拉框)都有绑定的属性和事件。如:文本框所绑定的属性是value,绑定的事件是input。

    那么,自定义组件里,如何使用v-model指令。就需要在vue组件里增加model选项。

    如下示例:		
Vue.component('my-checkbox', {
  props:['ccc','ddd'],//声明了自定义组件的属性。
  model: {
    prop: 'ccc', //表示v-model所绑定的属性是 ccc
    event: 'change'//表示v-model所绑定的事件是是 change
  },
  ……………………
}

31 。10.Vue的vue-router的底层原理是什么?

vue-router 以插件方式“侵入”Vue,从而支持一个额外的 router 属性,以提供监听并改变组件路由数据的能力。这样每次路由发生改变后,可以同步到数据,进而“响应式”地触发组件的更新。

<router-view> 作为根组件下的子组件,从根组件那里可以获取到当前的路由对象,进而根据路由对象的组件配置,取出组件并用其替换当前位置的内容。这样,也就完成整个路由变更到视图变更的过程。

原文链接:vue-router底层实现原理_vuerouter底层原理-CSDN博客

32, vuex流程

vuex是全局保存数据的,她保存的数据可以在任意地方使用 而且也是响应式的, vuex中有四个 action 里面存放异步逻辑的操作

mutition 存放同步操作

state 存放数据

getters 相当于计算属性

组件里派发dispatch 到vuex的action中 在action cimmit提交mutation中 mutation里修改state state修改后响应到数据上

Vue3

1.setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。

  2. setup是所有Composition API(组合API)“ 表演的舞台 ”

  3. 组件中所用到的:数据、方法等等,均要配置在setup中。

  4. setup函数的两种返回值:

    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)

    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解)

  5. 注意点:

    1. 尽量不要与Vue2.x配置混用

      • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法。

      • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)。

      • 如果有重名, setup优先。

    2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

2.ref函数

  • 作用: 定义一个响应式的数据

  • 语法: const xxx = ref(initValue)

    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)

    • JS中操作数据: xxx.value

    • 模板中读取数据: 不需要.value,直接:<div>{{xxx}}</div>

  • 备注:

    • 接收的数据可以是:基本类型、也可以是对象类型。

    • 基本类型的数据:响应式依然是靠Object.defineProperty()getset完成的。

    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数。

3.reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)

  • reactive定义的响应式数据是“深层次的”。

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

4.Vue3.0中的响应式原理

vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。

    • 直接通过下标修改数组, 界面不会自动更新。

Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:Proxy - JavaScript | MDN

      • Reflect:Reflect - JavaScript | MDN

        new Proxy(data, {
            // 拦截读取属性值
            get (target, prop) {
                return Reflect.get(target, prop)
            },
            // 拦截设置属性值或添加新属性
            set (target, prop, value) {
                return Reflect.set(target, prop, value)
            },
            // 拦截删除属性
            deleteProperty (target, prop) {
                return Reflect.deleteProperty(target, prop)
            }
        })
        ​
        proxy.name = 'tom'   

5.reactive对比ref

  • 从定义数据角度对比:

    • ref用来定义:基本类型数据

    • reactive用来定义:对象(或数组)类型数据

    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象

  • 从原理角度对比:

    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。

    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。

  • 从使用角度对比:

    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value

    • reactive定义的数据:操作数据与读取数据:均不需要.value

6.setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined。

  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。

    • context:上下文对象

      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs

      • slots: 收到的插槽内容, 相当于 this.$slots

      • emit: 分发自定义事件的函数, 相当于 this.$emit

7.计算属性与监视

1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    ​
    setup(){
        ...
        //计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }

2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。

    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。

    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
        console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    ​
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
        console.log('sum或msg变化了',newValue,oldValue)
    }) 
    ​
    /* 情况三:监视reactive定义的响应式数据
                若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
                若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
        console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    ​
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    ​
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    ​
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效

3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。

    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })

8.生命周期

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:

    • beforeDestroy改名为 beforeUnmount

    • destroyed改名为 unmounted

  • Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

    • beforeCreate===>setup()

    • created=======>setup()

    • beforeMount ===>onBeforeMount

    • mounted=======>onMounted

    • beforeUpdate===>onBeforeUpdate

    • updated =======>onUpdated

    • beforeUnmount ==>onBeforeUnmount

    • unmounted =====>onUnmounted

9.自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

10.toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefstoRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

三、其它 Composition API

1.shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)。

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。

    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

2.readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)。

  • shallowReadonly:让一个响应式数据变为只读的(浅只读)。

  • 应用场景: 不希望数据被修改时。

3.toRaw 与 markRaw

  • toRaw:

    • 作用:将一个由reactive生成的响应式对象转为普通对象

    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。

  • markRaw:

    • 作用:标记一个对象,使其永远不会再成为响应式对象。

    • 应用场景:

      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。

      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

  • 实现防抖效果:

    <template>
        <input type="text" v-model="keyword">
        <h3>{{keyword}}</h3>
    </template>
    ​
    <script>
        import {ref,customRef} from 'vue'
        export default {
            name:'Demo',
            setup(){
                // let keyword = ref('hello') //使用Vue准备好的内置ref
                //自定义一个myRef
                function myRef(value,delay){
                    let timer
                    //通过customRef去实现自定义
                    return customRef((track,trigger)=>{
                        return{
                            get(){
                                track() //告诉Vue这个value值是需要被“追踪”的
                                return value
                            },
                            set(newValue){
                                clearTimeout(timer)
                                timer = setTimeout(()=>{
                                    value = newValue
                                    trigger() //告诉Vue去更新界面
                                },delay)
                            }
                        }
                    })
                }
                let keyword = myRef('hello',500) //使用程序员自定义的ref
                return {
                    keyword
                }
            }
        }
    </script>

5.provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
          ......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
    2. 后代组件中:

      setup(props,context){
          ......
          const car = inject('car')
          return {car}
          ......
      }

6.响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象

  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理

  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理

  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

四、Composition API 的优势

1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

五、新的组件

1.Fragment

  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用

2.Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

    <teleport to="移动位置">
    	<div v-if="isShow" class="mask">
    		<div class="dialog">
    			<h3>我是一个弹窗</h3>
    			<button @click="isShow = false">关闭弹窗</button>
    		</div>
    	</div>
    </teleport>

3.Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    • 使用Suspense包裹组件,并配置好defaultfallback

      <template>
      	<div class="app">
      		<h3>我是App组件</h3>
      		<Suspense>
      			<template v-slot:default>
      				<Child/>
      			</template>
      			<template v-slot:fallback>
      				<h3>加载中.....</h3>
      			</template>
      		</Suspense>
      	</div>
      </template>

六、其他

1.全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue3.x 实例 API (app)
      Vue.config.xxxxapp.config.xxxx
      Vue.config.productionTip移除
      Vue.componentapp.component
      Vue.directiveapp.directive
      Vue.mixinapp.mixin
      Vue.useapp.use
      Vue.prototypeapp.config.globalProperties

2.其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • 事假总线,无法使用,兄弟传值可以用vuex 或者发布订阅,或者第三方mitt包

七,其他使用方法的改变

1,vuex的使用方法变动

创建store文件夹 和index.js

在mian.js导入 import store from "./store";

app.use(store);注册

组件内使用时候需要导入 import { useStore } from "vuex";

setUp中注册 const store = useStore();

无法使用map辅助函数,其他用法没有改变

2,vue-router使用变动

创建router文件夹 和index.js

在mian.js导入 import router from "./router";

app.use(router );注册

组件内使用 import { useRoute, useRouter } from "vue-router";

setUp中注册

const $route = useRoute();

const $router = useRouter();

其他用法没有变动

3,获取ref使用变动

需要先定义一个空的ref对象

​ ​ ​ ​ ​

		 <h1 ref='refs'>1</h1>

setup() {

const refs =ref(null)

function x (){

console.log(refs );

}

return {refs}

});

4,自定义事件使用变动

setup({emit}) {

});

从setUp中解构出emit 可以使用 其他用法未变动

Webpack

1. 说说你对 Webpack 的理解?解决了什么问题?

1)背景

Webpack 最初的目标是实现前端项目的模块化,旨在更高效地管理和维护项目中的每一个资源

模块化

最早的时候,我们会通过文件划分的形式实现模块化,也就是将每个功能及其相关状态数据各自单独放到不同的 JS 文件中

约定每个文件是一个独立的模块,然后再将这些 js 文件引入到页面,一个 script 标签对应一个模块,然后调用模块化的成员

<script src="module-a.js"></script>
<script src="module-b.js"></script>

但这种模块弊端十分的明显,模块都是在全局中工作,大量模块成员污染了环境,模块与模块之间并没有依赖关系、维护困难、没有私有空间等问题

项目一旦变大,上述问题会尤其明显

随后,就出现了命名空间方式,规定每个模块只暴露一个全局对象,然后模块的内容都挂载到这个对象中

window.moduleA = {
  method1: function () {
    console.log("moduleA#method1");
  },
};

这种方式也并没有解决第一种方式的依赖等问题

再后来,我们使用立即执行函数为模块提供私有空间,通过参数的形式作为依赖声明,如下

// module-a.js
(function ($) {
  var name = "module-a";
​
  function method1() {
    console.log(name + "#method1");
    $("body").animate({ margin: "200px" });
  }
​
  window.moduleA = {
    method1: method1,
  };
})(jQuery);

上述的方式都是早期解决模块的方式,但是仍然存在一些没有解决的问题。例如,我们是用过 script 标签在页面引入这些模块的,这些模块的加载并不受代码的控制,时间一久维护起来也十分的麻烦

理想的解决方式是,在页面中引入一个 JS 入口文件,其余用到的模块可以通过代码控制,按需加载进来

除了模块加载的问题以外,还需要规定模块化的规范,如今流行的则是 CommonJS、ES Modules

2)问题

从后端渲染的 JSP、PHP,到前端原生 JavaScript,再到 jQuery 开发,再到目前的三大框架 Vue、React、Angular

开发方式,也从 javascript 到后面的 es5、es6、7、8、9、10,再到 typescript,包括编写 CSS 的预处理器 less、scss 等

现代前端开发已经变得十分的复杂,所以我们开发过程中会遇到如下的问题:

  • 需要通过模块化的方式来开发

  • 使用一些高级的特性来加快我们的开发效率或者安全性,比如通过 ES6+、TypeScript 开发脚本逻辑,通过 sass、less 等方式来编写 css 样式代码

  • 监听文件的变化来并且反映到浏览器上,提高开发的效率

  • JavaScript 代码需要模块化,HTML 和 CSS 这些资源文件也会面临需要被模块化的问题

  • 开发完成后我们还需要将代码进行压缩、合并以及其他相关的优化

而 webpack 恰巧可以解决以上问题

3)是什么

webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具

  • 静态模块

这里的静态模块指的是开发阶段,可以被 webpack 直接引用的资源(可以直接被获取打包进 bundle.js 的资源)

当 webpack 处理应用程序时,它会在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块(不再局限 js 文件),并生成一个或多个 bundle

webpack 的能力:

「编译代码能力」,提高效率,解决浏览器兼容问题

「万物皆可模块能力」,项目维护性增强,支持不同种类的前端模块类型,统一的模块化方案,所有资源文件的加载都可以通过代码控制

2. 说说 webpack 的构建流程?

1)运行流程

webpack 的运行流程是一个串行的过程,它的工作流程就是将各个插件串联起来

在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条 webpack 机制中,去改变 webpack 的运作,使得整个系统扩展性良好

从启动到结束会依次执行以下三大步骤:

  • 初始化流程:从配置文件和 Shell 语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数

  • 编译构建流程:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理

  • 输出流程:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统

初始化流程

从配置文件和 Shell 语句中读取与合并参数,得出最终的参数

配置文件默认下为 webpack.config.js,也或者通过命令的形式指定配置文件,主要作用是用于激活 webpack 的加载项和插件

关于文件配置内容分析,如下注释:

var path = require("path");
var node_modules = path.resolve(__dirname, "node_modules");
var pathToReact = path.resolve(node_modules, "react/dist/react.min.js");
​
module.exports = {
  // 入口文件,是模块构建的起点,同时每一个入口文件对应最后生成的一个 chunk。
  entry: "./path/to/my/entry/file.js",
  // 文件路径指向(可加快打包过程)。
  resolve: {
    alias: {
      react: pathToReact,
    },
  },
  // 生成文件,是模块构建的终点,包括输出文件与输出路径。
  output: {
    path: path.resolve(__dirname, "build"),
    filename: "[name].js",
  },
  // 这里配置了处理各模块的 loader ,包括 css 预处理 loader ,es6 编译 loader,图片处理 loader。
  module: {
    loaders: [
      {
        test: /\.js$/,
        loader: "babel",
        query: {
          presets: ["es2015", "react"],
        },
      },
    ],
    noParse: [pathToReact],
  },
  // webpack 各插件对象,在 webpack 的事件流中执行对应的方法。
  plugins: [new webpack.HotModuleReplacementPlugin()],
};

webpack 将 webpack.config.js 中的各个配置项拷贝到 options 对象中,并加载用户配置的 plugins

完成上述步骤之后,则开始初始化 Compiler 编译对象,该对象掌控者 webpack 声明周期,不执行具体的任务,只是进行一些调度工作

class Compiler extends Tapable {
  constructor(context) {
    super();
    this.hooks = {
      beforeCompile: new AsyncSeriesHook(["params"]),
      compile: new SyncHook(["params"]),
      afterCompile: new AsyncSeriesHook(["compilation"]),
      make: new AsyncParallelHook(["compilation"]),
      entryOption: new SyncBailHook(["context", "entry"]),
      // 定义了很多不同类型的钩子
    };
    // ...
  }
}
​
function webpack(options) {
  var compiler = new Compiler();
  // ...// 检查 options,若 watch 字段为 true,则开启 watch 线程
  return compiler;
}
// ...

Compiler 对象继承自 Tapable,初始化时定义了很多钩子函数

编译构建流程

根据配置中的 entry 找出所有的入口文件

module.exports = {
  entry: "./src/file.js",
};

初始化完成后会调用 Compiler 的 run 来真正启动 webpack 编译构建流程,主要流程如下:

  • compile 开始编译

  • make 从入口点分析模块及其依赖的模块,创建这些模块对象

  • build-module 构建模块

  • seal 封装构建结果

  • emit 把各个 chunk 输出到结果文件

  1. compile 编译

执行了 run 方法后,首先会触发 compile,主要是构建一个 Compilation 对象

该对象是编译阶段的主要执行者,主要会依次下述流程:执行模块创建、依赖收集、分块、打包等主要任务的对象

  1. make 编译模块

当完成了上述的 compilation 对象后,就开始从 Entry 入口文件开始读取,主要执行_addModuleChain()函数,如下:

_addModuleChain(context, dependency, onModule, callback) {
  // ...
  // 根据依赖查找对应的工厂函数
  const Dep = /** @type {DepConstructor} */ (dependency.constructor);
  const moduleFactory = this.dependencyFactories.get(Dep);
​
  // 调用工厂函数 NormalModuleFactory 的 create 来生成一个空的 NormalModule 对象
  moduleFactory.create(
    {
      dependencies: [dependency],
      // ...
    },
    (err, module) => {
      // ...
      const afterBuild = () => {
        this.processModuleDependencies(module, (err) => {
          if (err) return callback(err);
          callback(null, module);
        });
      };
​
      this.buildModule(module, false, null, null, (err) => {
        //    ...
        afterBuild();
      });
    }
  );
}

过程如下:

_addModuleChain 中接收参数 dependency 传入的入口依赖,使用对应的工厂函数 NormalModuleFactory.create 方法生成一个空的 module 对象

回调中会把此 module 存入 compilation.modules 对象和 dependencies.module 对象中,由于是入口文件,也会存入 compilation.entries 中

随后执行 buildModule 进入真正的构建模块 module 内容的过程

  1. build module 完成模块编译

这里主要调用配置的 loaders,将我们的模块转成标准的 JS 模块

在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST),以方便 Webpack 后面对代码的分析

从配置的入口模块开始,分析其 AST,当遇到 require 等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系

输出流程

  1. seal 输出资源

seal 方法主要是要生成 chunks,对 chunks 进行一系列的优化操作,并生成要输出的代码

webpack 中的 chunk ,可以理解为配置在 entry 中的模块,或者是动态引入的模块

根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表

  1. emit 输出完成

在确定好输出内容后,根据配置确定输出的路径和文件名

output: {
  path: path.resolve(__dirname, "build"),
  filename: "[name].js",
}

在 Compiler 开始生成文件前,钩子 emit 会被执行,这是我们修改最终文件的最后一个机会

从而 webpack 整个打包过程则结束了

小结

3. 说说 Webpack 中常见的 Loader?解决了什么问题?

1)是什么

loader 用于对模块的源代码进行转换,在 import 或"加载"模块时预处理文件

webpack 做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。如下图所示:

在 webpack 内部中,任何文件都是模块,不仅仅只是 js 文件

默认情况下,在遇到 import 或者 load 加载模块的时候,webpack 只支持对 js 文件打包

像 css、sass、png 等这些类型的文件的时候,webpack 则无能为力,这时候就需要配置对应的 loader 进行文件内容的解析

当 webpack 碰到不识别的模块的时候,webpack 会在配置的中查找该文件解析规则

关于配置 loader 的方式有三种:

  • 配置方式(推荐):在 webpack.config.js 文件中指定 loader

  • 内联方式:在每个 import 语句中显式指定 loader

  • CLI 方式:在 shell 命令中指定它们

配置方式

关于 loader 的配置,我们是写在 module.rules 属性中,属性介绍如下:

  • rules 是一个数组的形式,因此我们可以配置很多个 loader

  • 每一个 loader 对应一个对象的形式,对象属性 test 为匹配的规则,一般情况为正则表达式

  • 属性 use 针对匹配到文件类型,调用对应的 loader 进行处理

代码编写,如下形式:

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: "style-loader" },
          {
            loader: "css-loader",
            options: {
              modules: true,
            },
          },
          { loader: "sass-loader" },
        ],
      },
    ],
  },
};

2)特性

这里继续拿上述代码,来讲讲 loader 的特性

从上述代码可以看到,在处理 css 模块的时候,use 属性中配置了三个 loader 分别处理 css 文件

因为 loader 支持链式调用,链中的每个 loader 会处理之前已处理过的资源,最终变为 js 代码。顺序为相反的顺序执行,即上述执行方式为 sass-loader、css-loader、style-loader

除此之外,loader 的特性还有如下:

  • loader 可以是同步的,也可以是异步的

  • loader 运行在 Node.js 中,并且能够执行任何操作

  • 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块

  • 插件(plugin)可以为 loader 带来更多特性

  • loader 能够产生额外的任意文件

可以通过 loader 的预处理函数,为 JavaScript 生态系统提供更多能力。用户现在可以更加灵活地引入细粒度逻辑,例如:压缩、打包、语言翻译和更多其他特性

3)常见的 loader

在页面开发过程中,我们经常性加载除了 js 文件以外的内容,这时候我们就需要配置响应的 loader 进行加载

常见的 loader 如下:

  • style-loader: 将 css 添加到 DOM 的内联样式标签 style 里

  • css-loader :允许将 css 文件通过 require 的方式引入,并返回 css 代码

  • less-loader: 处理 less

  • sass-loader: 处理 sass

  • postcss-loader: 用 postcss 来处理 CSS

  • autoprefixer-loader: 处理 CSS3 属性前缀,已被弃用,建议直接使用 postcss

  • file-loader: 分发文件到 output 目录并返回相对路径

  • url-loader: 和 file-loader 类似,但是当文件小于设定的 limit 时可以返回一个 Data Url

  • html-minify-loader: 压缩 HTML

  • babel-loader :用 babel 来转换 ES6 文件到 ES

下面给出一些常见的 loader 的使用:

css-loader

分析 css 模块之间的关系,并合成⼀个 css

npm install --save-dev css-loader

rules: [
  // ...,
  {
    test: /\.css$/,
    use: {
      loader: "css-loader",
      options: {
        // 启用/禁用 url() 处理
        url: true,
        // 启用/禁用 @import 处理
        import: true,
        // 启用/禁用 Sourcemap
        sourceMap: false,
      },
    },
  },
],

如果只通过 css-loader 加载文件,这时候页面代码设置的样式并没有生效

原因在于,css-loader 只是负责将.css 文件进行一个解析,而并不会将解析后的 css 插入到页面中

如果我们希望再完成插入 style 的操作,那么我们还需要另外一个 loader,就是 style-loader

style-loader

把 css-loader 生成的内容,用 style 标签挂载到页面的 head 中

npm install --save-dev style-loader

rules: [
  // ...,
  {
    test: /\.css$/,
    use: ["style-loader", "css-loader"],
  },
],

同一个任务的 loader 可以同时挂载多个,处理顺序为:从右到左,从下往上

less-loader

开发中,我们也常常会使用 less、sass、stylus 预处理器编写 css 样式,使开发效率提高,这里需要使用 less-loader

npm install less-loader -D

rules: [
  // ...,
  {
    test: /\.css$/,
    use: ["style-loader", "css-loader", "less-loader"],
  },
],

raw-loader

在 webpack 中通过 import 方式导入文件内容,该 loader 并不是内置的,所以首先要安装

npm install --save-dev raw-loader

然后在 webpack.config.js 中进行配置

module.exports = {
  // ...,
  module: {
    rules: [
      {
        test: /\.(txt|md)$/,
        use: "raw-loader",
      },
    ],
  },
};

file-loader

把识别出的资源模块,移动到指定的输出目录,并且返回这个资源在输出目录的地址(字符串)

npm install --save-dev file-loader

rules: [
  // ...,
  {
    test: /\.(png|jpe?g|gif)$/,
    use: {
      loader: "file-loader",
      options: {
        // placeholder 占位符 [name] 源资源模块的名称
        // [ext] 源资源模块的后缀
        name: "[name]_[hash].[ext]",
        //打包后的存放位置
        outputPath: "./images",
        // 打包后文件的 url
        publicPath: "./images",
      },
    },
  },
],

url-loader

可以处理理 file-loader 所有的事情,但是遇到图片格式的模块,可以选择性的把图片转成 base64 格式的字符串,并打包到 js 中,对小体积的图片比较合适,大图片不合适。

npm install --save-dev url-loader

rules: [
  // ...,
  {
    test: /\.(png|jpe?g|gif)$/,
    use: {
      loader: "url-loader",
      options: {
        // placeholder 占位符 [name] 源资源模块的名称
        // [ext] 源资源模块的后缀
        name: "[name]_[hash].[ext]",
        //打包后的存放位置
        outputPath: "./images",
        // 打包后文件的 url
        publicPath: "./images",
        // 小于 100 字节转成 base64 格式
        limit: 100,
      },
    },
  },
],

4. 说说 webpack 中常见的 Plugin?解决了什么问题?

1)是什么

Plugin(Plug-in)是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能

是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯净系统提供的函数库或者数据

webpack 中的 plugin 也是如此,plugin 赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了 webpack 整个编译周期

目的在于解决 loader 无法实现的其他事

配置方式

这里讲述文件的配置方式,一般情况,通过配置文件导出对象中 plugins 属性传入 new 实例对象。如下所示:

const HtmlWebpackPlugin = require("html-webpack-plugin"); // 通过 npm 安装
const webpack = require("webpack"); // 访问内置的插件
module.exports = {
  // ...
  plugins: [
    new webpack.ProgressPlugin(),
    new HtmlWebpackPlugin({ template: "./src/index.html" }),
  ],
};

2)特性

其本质是一个具有 apply 方法 javascript 对象

apply 方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象

const pluginName = "ConsoleLogOnBuildWebpackPlugin";

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    compiler.hooks.run.tap(pluginName, (compilation) => {
      console.log("webpack 构建过程开始!");
    });
  }
}

module.exports = ConsoleLogOnBuildWebpackPlugin;

compiler hook 的 tap 方法的第一个参数,应是驼峰式命名的插件名称

关于整个编译生命周期钩子,有如下:

  • entry-option :初始化 option

  • run

  • compile:真正开始的编译,在创建 compilation 对象之前

  • compilation :生成好了 compilation 对象

  • make 从 entry 开始递归分析依赖,准备对每个模块进行 build

  • after-compile:编译 build 过程结束

  • emit :在将内存中 assets 内容写到磁盘文件夹之前

  • after-emit :在将内存中 assets 内容写到磁盘文件夹之后

  • done:完成所有的编译过程

  • failed:编译失败的时候

3)常见的 Plugin

下面介绍几个常用的插件用法:

HtmlWebpackPlugin

在打包结束后,⾃动生成⼀个 html ⽂文件,并把打包生成的 js 模块引⼊到该 html 中

npm install --save-dev html-webpack-plugin

// webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  // ...
  plugins: [
    new HtmlWebpackPlugin({
      title: "My App",
      filename: "app.html",
      template: "./src/html/index.html",
    }),
  ],
};
<!--./src/html/index.html-->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title><%=htmlWebpackPlugin.options.title%></title>
  </head>
  <body>
    <h1>html-webpack-plugin</h1>
  </body>
</html>

在 html 模板中,可以通过 <%=htmlWebpackPlugin.options.XXX%> 的方式获取配置的值

更多的配置可以自寻查找

clean-webpack-plugin

删除(清理)构建目录

npm install --save-dev clean-webpack-plugin

const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
  // ...
  plugins: [
    // ...,
    new CleanWebpackPlugin(),
    // ...
  ],
};

mini-css-extract-plugin

提取 CSS 到一个单独的文件中

npm install --save-dev mini-css-extract-plugin

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  // ...,
  module: {
    rules: [
      {
        test: /\.s[ac]ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
          },
          "css-loader",
          "sass-loader",
        ],
      },
    ],
  },
  plugins: [
    // ...,
    new MiniCssExtractPlugin({
      filename: "[name].css",
    }),
    // ...
  ],
};

DefinePlugin

允许在编译时创建配置的全局对象,是一个 webpack 内置的插件,不需要安装

const { DefinePlugun } = require("webpack");

module.exports = {
  // ...
  plugins: [
    new DefinePlugin({
      BASE_URL: '"./"',
    }),
  ],
};

这时候编译 template 模块的时候,就能通过下述形式获取全局对象

<link rel="icon" href="<%= BASE_URL%>favicon.ico>"

copy-webpack-plugin

复制文件或目录到指定区域,如 vue 的打包过程中,如果我们将一些文件放到 public 的目录下,那么这个目录会被复制到 dist 文件夹中

npm install copy-webpack-plugin -D

const { CopyWebpackPlugin } = require("copy-webpack-plugin");

module.exports = {
  // ...
  plugins: [
    new CopyWebpackPlugin({
      parrerns: [
        {
          from: "public",
          globOptions: {
            ignore: ["**/index.html"],
          },
        },
      ],
    }),
  ],
};

复制的规则在 patterns 属性中设置:

  • from:设置从哪一个源中开始复制

  • to:复制到的位置,可以省略,会默认复制到打包的目录下

  • globOptions:设置一些额外的选项,其中可以编写需要忽略的文件

5. 说说 Webpack 中 Loader 和 Plugin 的区别?编写 Loader,Plugin 的思路?

1)区别

前面两节我们有提到 Loader 与 Plugin 对应的概念,先来回顾下

  • loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中

  • plugin 赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事 从整个运行时机上来看,如下图所示:

可以看到,两者在运行时机上的区别:

  • loader 运行在打包文件之前

  • plugins 在整个编译周期都起作用

在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果

对于 loader,实质是一个转换器,将 A 文件进行编译形成 B 文件,操作的是文件,比如将 A.scss 或 A.less 转变为 B.css,单纯的文件转换过程

2)编写 loader

在编写 loader 前,我们首先需要了解 loader 的本质

  • 其本质为函数,函数中的 this 作为上下文会被 webpack 填充,因此我们不能将 loader 设为一个箭头函数

  • 函数接受一个参数,为 webpack 传递给 loader 的文件源内容

  • 函数中 this 是由 webpack 提供的对象,能够获取当前 loader 所需要的各种信息

  • 函数中有异步操作或同步操作,异步操作通过 this.callback 返回,返回值要求为 string 或者 Buffer

代码如下所示:

// 导出一个函数,source 为 webpack 传递给 loader 的文件源内容
module.exports = function (source) {
  const content = doSomeThing2JsString(source);

  // 如果 loader 配置了 options 对象,那么this.query将指向 options
  const options = this.query;

  // 可以用作解析其他模块路径的上下文
  console.log("this.context");

  /*
   * this.callback 参数:
   * error:Error | null,当 loader 出错时向外抛出一个 error
   * content:String | Buffer,经过 loader 编译后需要导出的内容
   * sourceMap:为方便调试生成的编译后内容的 source map
   * ast:本次编译生成的 AST 静态语法树,之后执行的 loader 可以直接使用这个 AST,进而省去重复生成 AST 的过程
   */
  this.callback(null, content); // 异步
  return content; // 同步
};

一般在编写 loader 的过程中,保持功能单一,避免做多种功能

如 less 文件转换成 css 文件也不是一步到位,而是 less-loader、css-loader、style-loader 几个 loader 的链式调用才能完成转换

3)编写 plugin

由于 webpack 基于发布订阅模式,在运行的生命周期中会广播出许多事件,插件通过监听这些事件,就可以在特定的阶段执行自己的插件任务

在之前也了解过,webpack 编译会创建两个核心对象:

  • compiler:包含了 webpack 环境的所有的配置信息,包括 options,loader 和 plugin,和 webpack 整个生命周期相关的钩子

  • compilation:作为 plugin 内置事件回调函数的参数,包含了当前的模块资源、编译生成资源、变化的文件以及被跟踪依赖的状态信息。当检测到一个文件变化,一次新的 Compilation 将被创建

如果自己要实现 plugin,也需要遵循一定的规范:

  • 插件必须是一个函数或者是一个包含 apply 方法的对象,这样才能访问 compiler 实例

  • 传给每个插件的 compiler 和 compilation 对象都是同一个引用,因此不建议修改

  • 异步的事件需要在插件处理完任务时调用回调函数通知 Webpack 进入下一个流程,不然会卡住

实现 plugin 的模板如下:

class MyPlugin {
  // Webpack 会调用 MyPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler) {
    // 找到合适的事件钩子,实现自己的插件功能
    compiler.hooks.emit.tap("MyPlugin", (compilation) => {
      // compilation: 当前打包构建流程的上下文
      console.log(compilation);

      // do something...
    });
  }
}

在 emit 事件发生时,代表源文件的转换和组装已经完成,可以读取到最终将输出的资源、代码块、模块及其依赖,并且可以修改输出资源的内容

6. 说说 Webpack 的热更新是如何做到的?原理是什么?

1)是什么

HMR 全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用

例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失

如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用

在 webpack 中配置开启热模块也非常的简单,如下代码:

const webpack = require("webpack");
module.exports = {
  // ...
  devServer: {
    // 开启 HMR 特性
    hot: true,
    // hotOnly: true
  },
};

通过上述这种配置,如果我们修改并保存 css 文件,确实能够以不刷新的形式更新到页面中

但是,当我们修改并保存 js 文件之后,页面依旧自动刷新了,这里并没有触发热模块

所以,HMR 并不像 Webpack 的其他特性一样可以开箱即用,需要有一些额外的操作

我们需要去指定哪些模块发生更新时进行 HRM,如下代码:

if (module.hot) {
  module.hot.accept("./util.js", () => {
    console.log("util.js 更新了");
  });
}

2)实现原理

上面图中,可以分成两个阶段:

  • 启动阶段为上图 1 - 2 - A - B

在编写未经过 webpack 打包的源代码后,Webpack Compile 将源代码和 HMR Runtime 一起编译成 bundle 文件,传输给 Bundle Server 静态资源服务器当某一个文件或者模块发生变化时,webpack 监听到文件变化对文件重新编译打包,编译生成唯一的 hash 值,这个 hash 值用来作为下一次热更新的标识

根据变化的内容生成两个补丁文件:manifest(包含了 hash 和 chundId,用来说明变化的内容)和 chunk.js 模块

由于 socket 服务器在 HMR Runtime 和 HMR Server 之间建立 websocket 链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的 hash 值,如下图的 h 属性,作为下一次热更新的标识

在浏览器接受到这条消息之前,浏览器已经在上一次 socket 消息中已经记住了此时的 hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件

mainfest 文件包含重新 build 生成的 hash 值,以及变化的模块,对应上图的 c 属性

浏览器根据 manifest 文件获取模块变化的内容,从而触发 render 流程,实现局部模块更新

3)总结

  • 关于 webpack 热模块更新的总结如下:

通过 webpack-dev-server 创建两个服务器:提供静态资源的服务(express)和 Socket 服务

  • express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)

  • socket server 是一个 websocket 的长连接,双方可以通信

  • 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest 文件)和.js 文件(update chunk)

  • 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)

  • 浏览器拿到两个新的文件后,通过 HMR runtime 机制,加载这两个文件,并且针对修改的模块进行更新

7. 说说 Webpack Proxy 工作原理?为什么能解决跨域?

1)是什么

webpack proxy,即 webpack 提供的代理服务 基本行为就是接收客户端发送的请求后转发给其他服务器 其目的是为了便于开发者在开发模式下解决跨域问题(浏览器安全策略限制) 想要实现代理首先需要一个中间服务器,webpack 中提供服务器的工具为 webpack-dev-server

webpack-dev-server

webpack-dev-server 是 webpack 官方推出的一款开发工具,将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起

目的是为了提高开发者日常的开发效率,「只适用在开发阶段」

关于配置方面,在 webpack 配置对象属性中通过 devServer 属性提供,如下:

// ./webpack.config.js
const path = require("path");

module.exports = {
  // ...
  devServer: {
    contentBase: path.join(__dirname, "dist"),
    compress: true,
    port: 9000,
    proxy: {
      "/api": {
        target: "https://api.github.com",
      },
    },
    // ...
  },
};

devServetr 里面 proxy 则是关于代理的配置,该属性为对象的形式,对象中每一个属性就是一个代理的规则匹配

属性的名称是需要被代理的请求路径前缀,一般为了辨别都会设置前缀为/api,值为对应的代理匹配规则,对应如下:

  • target:表示的是代理到的目标地址

  • pathRewrite:默认情况下,我们的 /api-hy 也会被写入到 URL 中,如果希望删除,可以使用 pathRewrite

  • secure:默认情况下不接收转发到 https 的服务器上,如果希望支持,可以设置为 false

  • changeOrigin:它表示是否更新代理后请求的 headers 中 host 地址

2)工作原理

proxy 工作原理实质上是利用 http-proxy-middleware 这个 http 代理中间件,实现请求转发给其他服务器

举个例子:

在开发阶段,本地地址为 http://localhost:3000,该浏览器发送一个前缀带有/api 标识的请求到服务端获取数据,但响应这个请求的服务器只是将请求转发到另一台服务器中

const express = require("express");
const proxy = require("http-proxy-middleware");

const app = express();

app.use(
  "/api",
  proxy({ target: "http://www.example.org", changeOrigin: true })
);
app.listen(3000);

// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar

3)跨域

在开发阶段, webpack-dev-server 会启动一个本地开发服务器,所以我们的应用在开发阶段是独立运行在 localhost 的一个端口上,而后端服务又是运行在另外一个地址上

所以在开发阶段中,由于浏览器同源策略的原因,当本地访问后端就会出现跨域请求的问题

通过设置 webpack proxy 实现代理请求后,相当于浏览器与服务端中添加一个代理者

当本地发送请求的时候,代理服务器响应该请求,并将请求转发到目标服务器,目标服务器响应数据后再将数据返回给代理服务器,最终再由代理服务器将数据响应给本地

在代理服务器传递数据给本地浏览器的过程中,两者同源,并不存在跨域行为,这时候浏览器就能正常接收数据

注意「服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制」

8. 说说你是如何利用 Webpack 来优化前端性能的?

1)背景

随着前端的项目逐渐扩大,必然会带来的一个问题就是性能

尤其在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至奔溃

一般项目在完成后,会通过 webpack 进行打包,利用 webpack 对前端项目性能优化是一个十分重要的环节

2)如何优化

通过 webpack 优化前端的手段有:

  • JS 代码压缩

  • CSS 代码压缩

  • Html 文件代码压缩

  • 文件大小压缩

  • 图片压缩

  • Tree Shaking

  • 代码分离

  • 内联 chunk

JS 代码压缩

terser 是一个 JavaScript 的解释、绞肉机、压缩机的工具集,可以帮助我们压缩、丑化我们的代码,让 bundle 更小

在 production 模式下,webpack 默认就是使用 TerserPlugin 来处理我们的代码的。如果想要自定义配置它,配置方法如下:

const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true, // 电脑 cpu 核数-1
      }),
    ],
  },
};

属性介绍如下:

  • extractComments:默认值为 true,表示会将注释抽取到一个单独的文件中,开发阶段,我们可设置为 false ,不保留注释

  • parallel:使用多进程并发运行提高构建的速度,默认值是 true,并发运行的默认数量:os.cpus().length - 1

  • terserOptions:设置我们的 terser 相关的配置:

  • compress:设置压缩相关的选项,mangle:设置丑化相关的选项,可以直接设置为 true

  • mangle:设置丑化相关的选项,可以直接设置为 true

  • toplevel:底层变量是否进行转换

  • keep_classnames:保留类的名称

  • keep_fnames:保留函数的名称

CSS 代码压缩

CSS 压缩通常是去除无用的空格等,因为很难去修改选择器、属性的名称、值等

CSS 的压缩我们可以使用另外一个插件:css-minimizer-webpack-plugin

npm install css-minimizer-webpack-plugin -D

配置方法如下:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      new CssMinimizerPlugin({
        parallel: true,
      }),
    ],
  },
};

Html 文件代码压缩

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

module.exports = {
  // ...
  plugin: [
    new HtmlwebpackPlugin({
      // ...
      minify: {
        minifyCSS: false, // 是否压缩 css
        collapseWhitespace: false, // 是否折叠空格
        removeComments: true, // 是否移除注释
      },
    }),
  ],
};

设置了 minify,实际会使用另一个插件 html-minifier-terser

文件大小压缩

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

npm install compression-webpack-plugin -D

new ComepressionPlugin({
  test: /\.(css|js)$/, // 哪些文件需要压缩
  threshold: 500, // 设置文件多大开始压缩
  minRatio: 0.7, // 至少压缩的比例
  algorithm: "gzip", // 采用的压缩算法
});

图片压缩

一般来说在打包之后,一些图片文件的大小是远远要比 js 或者 css 文件要来的大,所以图片压缩较为重要

配置方法如下:

module: {
  rules: [
    {
      test: /\.(png|jpg|gif)$/,
      use: [
        {
          loader: "file-loader",
          options: {
            name: "[name]_[hash].[ext]",
            outputPath: "images/",
          },
        },
        {
          loader: "image-webpack-loader",
          options: {
            // 压缩 jpeg 的配置
            mozjpeg: {
              progressive: true,
              quality: 65,
            },
            // 使用 imagemin\*\*-optipng 压缩 png,enable: false 为关闭
            optipng: {
              enabled: false,
            },
            // 使用 imagemin-pngquant 压缩 png
            pngquant: {
              quality: "65-90",
              speed: 4,
            },
            // 压缩 gif 的配置
            gifsicle: {
              interlaced: false,
            },
            // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
            webp: {
              quality: 75,
            },
          },
        },
      ],
    },
  ];
}

Tree Shaking

Tree Shaking 是一个术语,在计算机中表示消除死代码,依赖于 ES Module 的静态语法分析(不执行任何的代码,可以明确知道模块的依赖关系)

在 webpack 实现 Trss shaking 有两种不同的方案:

usedExports:通过标记某些函数是否被使用,之后通过 Terser 来进行优化的 sideEffects:跳过整个模块/文件,直接查看该文件是否有副作用 两种不同的配置方案, 有不同的效果

  1. usedExports

配置方法也很简单,只需要将 usedExports 设为 true

module.exports = {
  // ...
  optimization: {
    usedExports,
  },
};

使用之后,没被用上的代码在 webpack 打包中会加入 unused harmony export mul 注释,用来告知 Terser 在优化时,可以删除掉这段代码

如下面 sum 函数没被用到,webpack 打包会添加注释,terser 在优化时,则将该函数去掉

  1. sideEffects

sideEffects 用于告知 webpack compiler 哪些模块时有副作用,配置方法是在 package.json 中设置 sideEffects 属性

如果 sideEffects 设置为 false,就是告知 webpack 可以安全的删除未用到的 exports

如果有些文件需要保留,可以设置为数组的形式

sideEffecis: [
  "./src/util/format.js",
  "*.css", // 所有的 css 文件
],

上述都是关于 javascript 的 tree shaking,css 同样也能够实现 tree shaking

  1. css tree shaking

css 进行 tree shaking 优化可以安装 PurgeCss 插件

npm install purgecss-plugin-webpack -D

const PurgeCssPlugin = require("purgecss-webpack-plugin");
module.exports = {
  // ...
  plugins: [
    new PurgeCssPlugin({
      path: glob.sync(`${path.resolve("./src")}/**/*`, { nodir: true }), // src 里面的所有文件
      satelist: function () {
        return {
          standard: ["html"],
        };
      },
    }),
  ],
};
  • paths:表示要检测哪些目录下的内容需要被分析,配合使用 glob

  • 默认情况下,Purgecss 会将我们的 html 标签的样式移除掉,如果我们希望保留,可以添加一个 safelist 的属性

代码分离

将代码分离到不同的 bundle 中,之后我们可以按需加载,或者并行加载这些文件

默认情况下,所有的 JavaScript 代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载,就会影响首页的加载速度

代码分离可以分出出更小的 bundle,以及控制资源加载优先级,提供代码的加载性能

这里通过 splitChunksPlugin 来实现,该插件 webpack 已经默认安装和集成,只需要配置即可

默认配置中,chunks 仅仅针对于异步(async)请求,我们可以设置为 initial 或者 all

module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: "all",
    },
  },
};

splitChunks 主要属性有如下:

  • Chunks,对同步代码还是异步代码进行处理

  • minSize:拆分包的大小, 至少为 minSize,如何包的大小不超过 minSize,这个包不会拆分

  • maxSize:将大于 maxSize 的包,拆分为不小于 minSize 的包

  • minChunks:被引入的次数,默认是 1

内联 chunk

可以通过 InlineChunkHtmlPlugin 插件将一些 chunk 的模块内联到 html,如 runtime 的代码(对模块进行解析、加载、模块信息相关的代码),代码量并不大,但是必须加载的

const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  // ...
  plugin: [new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime.+\.js/])],
};

3)总结

关于 webpack 对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少 http 请求次数等方式,实现对前端性能的优化

9. 说说提高 webpack 的构建速度的手段有哪些?

1)背景

随着我们的项目涉及到页面越来越多,功能和业务代码也会随着越多,相应的 webpack 的构建时间也会越来越久

构建时间与我们日常开发效率密切相关,当我们本地开发启动 devServer 或者 build 的时候,如果时间过长,会大大降低我们的工作效率

所以,优化 webpack 构建速度是十分重要的环节

2)如何优化

常见的提升构建速度的手段有如下:

  • 优化 loader 配置

  • 合理使用 resolve.extensions

  • 优化 resolve.modules

  • 优化 resolve.alias

  • 使用 DLLPlugin 插件

  • 使用 cache-loader

  • terser 启动多线程

  • 合理使用 sourceMap

优化 loader 配置

在使用 loader 时,可以通过配置 include、exclude、test 属性来匹配文件,接触 include、exclude 规定哪些匹配应用 loader

如采用 ES6 的项目为例,在配置 babel-loader 时,可以这样:

module.exports = {
  module: {
    rules: [
      {
        // 如果项目源码中只有 js 文件就不要写成 /\.jsx?$/,提升正则表达式性能
        test: /\.js$/,
        // babel-loader 支持缓存转换出的结果,通过 cacheDirectory 选项开启
        use: ["babel-loader?cacheDirectory"],
        // 只对项目根目录下的 src 目录中的文件采用 babel-loader
        include: path.resolve(__dirname, "src"),
      },
    ],
  },
};

合理使用 resolve.extensions

在开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库, resolve 可以帮助 webpack 从每个 require/import 语句中,找到需要引入到合适的模块代码

通过 resolve.extensions 是解析到文件时自动添加拓展名,默认情况如下:

module.exports = {
  // ...
  extensions: [".warm", ".mjs", ".js", ".json"],
};

当我们引入文件的时候,若没有文件后缀名,则会根据数组内的值依次查找

当我们配置的时候,则不要随便把所有后缀都写在里面,这会调用多次文件的查找,这样就会减慢打包速度

优化 resolve.modules

resolve.modules 用于配置 webpack 去哪些目录下寻找第三方模块。默认值为['node_modules'],所以默认会从 node_modules 中查找文件 当安装的第三方模块都放在项目根目录下的 ./node_modules 目录下时,所以可以指明存放第三方模块的绝对路径,以减少寻找,配置如下:

module.exports = {
  resolve: {
    // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤
    // 其中 **dirname 表示当前工作目录,也就是项目根目录
    modules: [path.resolve(__dirname, "node_modules")],
  },
};

优化 resolve.alias

alias 给一些常用的路径起一个别名,特别当我们的项目目录结构比较深的时候,一个文件的路径可能是./../../的形式

通过配置 alias 以减少查找过程

module.exports = {
  // ...
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
};

使用 DLLPlugin 插件

DLL 全称是 动态链接库,是为软件在 winodw 中实现共享函数库的一种实现方式,而 Webpack 也内置了 DLL 的功能,为的就是可以共享,不经常改变的代码,抽成一个共享的库。这个库在之后的编译过程中,会被引入到其他项目的代码中

使用步骤分成两部分:

  • 打包一个 DLL 库

  • 引入 DLL 库

打包一个 DLL 库

webpack 内置了一个 DllPlugin 可以帮助我们打包一个 DLL 的库文件

module.exports = {
  // ...
  plugins: [
    new webpack.DllPlugin({
      name: "dll_[name]",
      path: path.resolve(__dirname, "./dll/[name].mainfest.json"),
    }),
  ],
};

引入 DLL 库

使用 webpack 自带的 DllReferencePlugin 插件对 mainfest.json 映射文件进行分析,获取要使用的 DLL 库

然后再通过 AddAssetHtmlPlugin 插件,将我们打包的 DLL 库引入到 Html 模块中

module.exports = {
  // ...
  plugins: [
    new webpack.DllReferencePlugin({
      context: path.resolve(__dirname, "./dll/dll_react.js"),
      mainfest: path.resolve(__dirname, "./dll/react.mainfest.json"),
    }),
    new AddAssetHtmlPlugin({
      outputPath: "./auto",
      filepath: path.resolve(__dirname, "./dll/dll_react.js"),
    }),
  ],
};

使用 cache-loader

在一些性能开销较大的 loader 之前添加 cache-loader,以将结果缓存到磁盘里,显著提升二次构建速度

保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader

module.exports = {
  module: {
    rules: [
      {
        test: /\.ext$/,
        use: ["cache-loader", ...loaders],
        include: path.resolve("src"),
      },
    ],
  },
};

terser 启动多线程

使用多进程并行运行来提高构建速度

module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
      }),
    ],
  },
};

合理使用 sourceMap

3)总结

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

10. 与 Webpack 类似的工具还有哪些?区别?

1)模块化工具

模块化是一种处理复杂系统分解为更好的可管理模块的方式

可以用来分割,组织和打包应用。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体(bundle)

在前端领域中,并非只有 webpack 这一款优秀的模块打包工具,还有其他类似的工具,例如 Rollup、Parcel、snowpack,以及最近风头无两的 Vite

通过这些模块打包工具,能够提高我们的开发效率,减少开发成本

这里没有提及 gulp、grunt 是因为它们只是定义为构建工具,不能类比

Rollup

Rollup 是一款 ES Modules 打包器,从作用上来看,Rollup 与 Webpack 非常类似。不过相比于 Webpack,Rollup 要小巧的多

现在很多我们熟知的库都都使用它进行打包,比如:Vue、React 和 three.js 等

举个例子:

// ./src/messages.js
export default {
  hi: "Hey Guys, I am zce~",
};

// ./src/logger.js
export const log = (msg) => {
  console.log("---------- INFO ----------");
  console.log(msg);
  console.log("--------------------------");
};

export const error = (msg) => {
  console.error("---------- ERROR ----------");
  console.error(msg);
  console.error("---------------------------");
};

// ./src/index.js
import { log } from "./logger";
import messages from "./messages";
log(messages.hi);

然后通过 rollup 进行打包

$ npx rollup ./src/index.js --file ./dist/bundle.js

打包结果如下图

可以看到,代码非常简洁,完成不像 webpack 那样存在大量引导代码和模块函数

并且 error 方法由于没有被使用,输出的结果中并无 error 方法,可以看到,rollup 默认开始 Tree-shaking 优化输出结果

因此,可以看到 Rollup 的优点:

  • 代码效率更简洁、效率更高

  • 默认支持 Tree-shaking

但缺点也十分明显,加载其他类型的资源文件或者支持导入 CommonJS 模块,又或是编译 ES 新特性,这些额外的需求 Rollup 需要使用插件去完成

综合来看,rollup 并不适合开发应用使用,因为需要使用第三方模块,而目前第三方模块大多数使用 CommonJs 方式导出成员,并且 rollup 不支持 HMR,使开发效率降低

但是在用于打包 JavaScript 库时,rollup 比 webpack 更有优势,因为其打包出来的代码更小、更快,其存在的缺点可以忽略

Parcel

Parcel ,是一款完全零配置的前端打包器,它提供了 “傻瓜式” 的使用体验,只需了解简单的命令,就能构建前端应用程序

Parcel 跟 Webpack 一样都支持以任意类型文件作为打包入口,但建议使用 HTML 文件作为入口,该 HTML 文件像平时一样正常编写代码、引用资源。如下所示:

<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Parcel Tutorials</title>
  </head>
  <body>
    <script src="main.js"></script>
  </body>
</html>

main.js 文件通过 ES Moudle 方法导入其他模块成员

// ./src/main.js
import { log } from "./logger";
log("hello parcel");
// ./src/logger.js
export const log = (msg) => {
  console.log("---------- INFO ----------");
  console.log(msg);
};

运行之后,使用命令打包

npx parcel src/index.html

执行命令后,Parcel 不仅打包了应用,同时也启动了一个开发服务器,跟 webpack Dev Server 一样

跟 webpack 类似,也支持模块热替换,但用法更简单

同时,Parcel 有个十分好用的功能:支持自动安装依赖,像 webpack 开发阶段突然使用安装某个第三方依赖,必然会终止 dev server 然后安装再启动。而 Parcel 则免了这繁琐的工作流程

同时,Parcel 能够零配置加载其他类型的资源文件,无须像 webpack 那样配置对应的 loader

打包命令如下:

npx parcel src/index.html

由于打包过程是多进程同时工作,构建速度会比 Webpack 快,输出文件也会被压缩,并且样式代码也会被单独提取到单个文件中

可以感受到,Parcel 给开发者一种很大的自由度,只管去实现业务代码,其他事情用 Parcel 解决

Snowpack

Snowpack,是一种闪电般快速的前端构建工具,专为现代 Web 设计,较复杂的打包工具(如 Webpack 或 Parcel)的替代方案,利用 JavaScript 的本机模块系统,避免不必要的工作并保持流畅的开发体验

开发阶段,每次保存单个文件时,Webpack 和 Parcel 都需要重新构建和重新打包应用程序的整个 bundle。而 Snowpack 为你的应用程序每个文件构建一次,就可以永久缓存,文件更改时,Snowpack 会重新构建该单个文件

在重新构建每次变更时没有任何的时间浪费,只需要在浏览器中进行 HMR 更新

Vite

vite ,是一种新型前端构建工具,能够显著提升前端开发体验

它主要由两部分组成:

  • 一个开发服务器,它基于 原生 ES 模块 提供了丰富的内建功能,如速度快到惊人的 [模块热更新 HMR

  • 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可以输出用于生产环境的优化过的静态资源

其作用类似 webpack+ webpack-dev-server,其特点如下:

  • 快速的冷启动

  • 即时的模块热更新

  • 真正的按需编译

vite 会直接启动开发服务器,不需要进行打包操作,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快

利用现代浏览器支持 ES Module 的特性,当浏览器请求某个模块的时候,再根据需要对模块的内容进行编译,这种方式大大缩短了编译时间

原理图如下所示:

在热模块 HMR 方面,当修改一个模块的时候,仅需让浏览器重新请求该模块即可,无须像 webpack 那样需要把该模块的相关依赖模块全部编译一次,效率更高

webpack

相比上述的模块化工具,webpack 大而全,很多常用的功能做到开箱即用。有两大最核心的特点:「一切皆模块」和「按需加载」

与其他构建工具相比,有如下优势:

  • 智能解析:对 CommonJS 、 AMD 、ES6 的语法做了兼容

  • 万物模块:对 js、css、图片等资源文件都支持打包

  • 开箱即用:HRM、Tree-shaking 等功能

  • 代码分割:可以将代码切割成不同的 chunk,实现按需加载,降低了初始化时间

  • 插件系统,具有强大的 Plugin 接口,具有更好的灵活性和扩展性

  • 易于调试:支持 SourceUrls 和 SourceMaps

  • 快速运行:webpack 使用异步 IO 并具有多级缓存,这使得 webpack 很快且在增量编译上更加快

  • 生态环境好:社区更丰富,出现的问题更容易解决

v2-3区别

1.请简单叙述Vue2和Vue3的区别和变化至少说6点
vue2中响应式原理是object.defineProperty;vue3中响应式原理是proxy
vue2中不支持多根节点;vue3中支持多个根节点
main.js文件:vue2中使用的是构造函数,vue3中使用的是工厂函数
指令与插槽:
vue2中使用slot可以直接使用slot,而vue3中必须使用v-slot的形式
v-for与v-if在vue2中v-for指令的优先级较高,不建议一起使用;
在vue3中,只是把当前的v-if当成一个判断语句,不会相互冲突
vue3中移除了v-on.native修饰符
vue3中移除了过滤器
生命周期:
vue3中setUp函数替代了vue2中的beforeCreate和created,vue3中生命周期函数都带有on前缀
vue2中卸载阶段是beforeDestory,destoryed;vue3中卸载阶段是onBeforeUnmount,onUnmounted
碎片化:vue2不支持碎片化,vue3是支持碎片化的
vue2中的数据都是在data(){return{}}中存储的,vue3的数据课方法都定义在setup语法糖中,并统一进行return
父给子传值:props,r e f s ,自定义事件, p r o v i d e i n j e c t , v − m o d e l , v u e x ,消息订阅与发布 v u e 2 中:在父组件中引入子组件地址,然后在上面定义一个子组件标签,并在里面定义一个属性 = “要传递过来的值”,子组件通过 p r o p s 接收父组件传递过来的值 v u e 3 中:和 v u e 2 传值方式一样,在子组件接收的时候也是也是用到了 p r o p s ,但是需要在 s e t u p 中定义一下,如果不定义 p r o p s 的话会不显示子给父传值: v u e 2 中:通过 t h i s . refs,自定义事件,provide inject ,v-model,vuex,消息订阅与发布 vue2中:在父组件中引入子组件地址,然后在上面定义一个子组件标签,并在里面定义一个属性=“要传递过来的值”,子组件通过props接收父组件传递过来的值 vue3中:和vue2传值方式一样,在子组件接收的时候也是也是用到了props,但是需要在setup中定义一下,如果不定义props的话会不显示 子给父传值: vue2中:通过this.refs,自定义事件,provideinject,v−model,vuex,消息订阅与发布vue2中:在父组件中引入子组件地址,然后在上面定义一个子组件标签,并在里面定义一个属性=“要传递过来的值”,子组件通过props接收父组件传递过来的值vue3中:和vue2传值方式一样,在子组件接收的时候也是也是用到了props,但是需要在setup中定义一下,如果不定义props的话会不显示子给父传值:vue2中:通过this.emit(“名称”,this.data中定义的那个数据),父组件通过v-on接收,@方法名称=“方法名称"
vue3中,用到了context方法,context.emit(方法的名字,要传入的值),父组件还是通过v-on接收,@方法名称=“方法名称"
API:
Vue2:选项形API,以vue为后缀的文件,组件状态写在data中,组件方法写在mothods中等,
Vue3:组合式API,将所以的API放到一起,形成高内聚低耦合在状态
在逻辑组织和逻辑复用方面,Composition API是优于Options API
因为Composition API几乎是函数,会有更好的类型推断。
Composition API对 tree-shaking 友好,代码也更容易压缩
Composition API中见不到this的使用,减少了this指向不明的情况
如果是小型组件,可以继续使用Options API,也是十分友好的

2.Vue3中组件通信的方法有些,流程是什么?
父子传值:在vue2中父给子传值用到了:方法=要传递过去的数据,子组件通过props接收父组件传递过来的数据,子传父:子组件通过this.$emite(方法,要传递过去的数据),父组件通过@方法=方法名称接收;在vue3中父给子传值用到了:方法=要传递过去的数据,子组件通过props接收父组件传递过来的数据,但是在子组件中这个方法要写在setup中,不然不会展示出来,子传父:子组件通过contex(方法,要传递过去的数据),父组件通过@方法=方法名称接收
Provide和inject传值:用于祖孙传值,这两个是成对出现的,有inject就必须有provide,这个方法不需要知道他的组件是谁,只需要在需要传值的地方写入inject即可将下载provide的地方的数据直接传递过去,当然他也可以用于父子传值,不需要想props那也复杂,也不需要知道父组件是谁,即可立即使用,并把数据接收,传递成功
Vuex传值
Refs传值

3.说说对盒子模型的理解?
盒子模型分为两种,W3C盒子模型,IE怪异盒子模型
盒子模型是由content,border,margin,padding这四部分组成
W3C盒子模型的总宽度是:width+padding+border+margin
总高度是:height+padding+border+margin
也就是说,width/height 不包含padding和border值

IE怪异盒子模型的总宽度是:width+margin
总高度是:height+margin
也就是说,width/height 包含了padding和border值

4.Css的选择器有哪些?优先级?哪些可以继承
Id选择器,class选择器,标签选择器,属性选择器,内联选择器,后代选择器,标签选择器
选择器的优先级是由其特定性决定的。特定性通常由选择器中包含的各种不同类型的选择器组成
Id选择器>class选择器>标签选择器

5.元素水平垂直居中的方法有哪些?如果元素不定宽高呢?
负margin居中
Tranform居中
Flex居中
绝对定位,这个方法最好用,最实用
不确定宽高居中
table-cell居中

6.怎么理解回流跟重绘?什么场景下会触发?
回流:指元素的布局或几何属性发生变化时,浏览器重新计算元素的几何属性和页面布局,这个过程称为回流。回流会使其他的元素的布局也受到影响,可能会引起其他元素的重新布局和回流,导致性能问题。
重绘:指元素的绘制属性发生变化时,浏览器会重新绘制元素的外观,但不会重新布局,这个过程称为重绘。例如,修改元素的颜色、背景颜色、边框颜色等属性都只会引起重绘
添加、删除、修改 DOM 元素。
改变窗口大小,或者滚动页面。
修改元素的样式,如改变元素的颜色、大小、位置等。
读取某些属性,如 offsetWidth、offsetHeight、clientWidth、clientHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight 等

7.说说重排和重绘的区别?触发条件有哪些?
重排:指对 DOM 结构进行修改后,浏览器需要重新计算元素的布局和位置,以及页面的几何属性,重新构建渲染树,并进行重新布局。重排是相对耗费时间和性能的操作。
重绘:指对 DOM 结构进行修改后,浏览器需要重新绘制元素的外观和样式,但不需要改变其位置和大小,因为在重排时已经计算好了。重绘是比重排更轻量的操作。
触发条件:
页面初次渲染时;
浏览器窗口大小发生变化;
元素的位置、大小或布局发生变化;
元素的内容发生变化;
元素的样式发生变化;
用户交互事件,如鼠标滚动、点击

8.什么是响应式设计?响应式设计的基本原理是什么?如何做?
Vue2中响应式原理是双向绑定的原则,是在数据获取的时候获取到getter和setter这两个方法,然后对这两个方法进行监听处理,最后在实现之中就实现了响应式原理

Definproperty,不能将数组传入进去,也不能获取到数据发生变化的时候。立即获取到的值,
Definproperty中的data是一次性传值的,用户体验感不好,会使页面加载速度慢,出现卡顿,不动的现象
Proxy可以监听到数组,以及数据进行更改的时的操作,可以获取到添加和修改的操作数据
其次在proxy中,data可以分批次渲染的,这样不会影响性能,也不会影响用户体验
在proxy和defineproperty中都是不可以使用异步操作的

9.如果要做优化,CSS提高性能的方法有哪些?
谨慎使用高性能属性:浮动,定位
尽量减少页面的重排和重绘
空规则的产生的原因一般来说是为了预留样式。去除这些空规则则无疑能减少css文档体积。
属性值为0时,不加单位。
属性值为浮动小数,可以省略小数点之前的0。
标准化各种浏览器前缀,带浏览器前缀的在前。标准属性在后。
不使用@import前缀,它会影响css的加载速度。
选择器优先嵌套,尽量避免层级过深。

10.对前端工程师这个职位是怎么样理解的?它的前景会怎么样
主要负责开发和构建网站、Web 应用程序和移动应用程序的用户界面。前端工程师需要具备一定的编程技能,熟悉 HTML、CSS 和 JavaScript 等前端技术,能够将设计师提供的视觉稿转化为网页或应用程序,并确保用户界面的高度可用性和响应性
主要工作:
开发并优化用户界面,确保良好的用户体验;
实现交互功能,与后端接口进行数据交互;
优化页面加载速度和性能,提升网页的响应速度;
解决不同浏览器和设备间的兼容性问题;
参与产品需求分析、原型设计和技术评审等工作。

11.说说JavaScript中的数据类型?存储上的差别?
JavaScript中的数据类型分为基本数据类型和引用数据类型
基本数据类型:null,string,number,布尔类型,symbol,undefined,其中symbol是es6中新增的特性
引用数据类型统称为object,细分为object,array,date,regexp,function
引用数据类型存储在堆中,基本数据类型存储在栈中
堆栈池存储
栈:存放变量
堆:存放复杂对象
池:存放常量
引用数据类型存储在堆内存中,每个对象在堆内存中有一个引用地址,栈中保护的就是这个对象在堆内存中的引用地址

12.请简单叙述js数据类型判断的方法有哪些?
Typeof方法:typeof是一个操作符,可以判断基本数据类,返回值有null,string,number,布尔类型,symbol,undefined,function,object这几种类型
Instanof方法:一般用来检测引用数据类型,表达式A instancof B,就是判断A是否是B的实例,如果是则返回true,不是就返回False有构造类型判断出数据类型
Constructor方法:Constructor是prototype对象上的属性,指向构造函数object.prototype.toString方法来检查对象类型

13.说说你对闭包的理解?闭包使用场景
闭包就是一个可以读取其他内部变量的函数,函数内可以再次嵌套函数,内部函数可以引用只外层的参数和变量,参数和变量不慌被垃圾回收站机制回收
使用闭包就是为了设置私有属性的方法和变量,闭包的优点是完全可以避免全局变量的污染
缺点:闭包会常驻内存,增大内存的使用率,使用不当的时候也会造成内存的泄漏,在js中,函数就是闭包只有函数才会产生作用域的概念,
闭包的好处:可以读取内部的变量;也可以让变量始终保持在内存中,可以很好的封装私有属性和方法
弊端:由于闭包会在函数中的变量都被保存在内存中,内存消耗增大,会造成网页的性能不佳,然后造成内存泄漏的风险

14.Javascript本地存储的方式有哪些?区别及应用场景?
分为Localstore,cookie,sessionStore这三种
Localstore是浏览器本地缓存,存储容量比较大,能够达到5mb,可以存储很多的数据,页面销毁数据不会丢失,除手动删除之外,他就会永远存在
Cookie是由服务器返回它标记已经过期的时间,经常用于跟踪用户行为等操作,但是它存储的数据量非常小,只有4kb左右
Sessionstore浏览器缓存,存储容量和localstore一样,都可以达到5mb,他们的不同之处在于sessionStore的存储时间,当页面标签页关闭并且销毁时,存储内容就会消失

15.请叙述Vue2和Vue3数据响应式原理的不同?
在vue2中数据响应式原来用到的是object.definprototype,定义变量的时候在data这个函数中去定义一些需要的变量Vue2中响应式原理是双向绑定的原则,是在数据获取的时候获取到getter和setter这两个方法,然后对这两个方法进行监听处理,最后在实现之中就实现了响应式原理
Vue3中数据响应式原理用到的是proxy,如果setup语法糖,那么在script标签下就直接定义就可以了,如果是在写了一个setup函数,那么就需要在这个函数内部去定义,然后返回一个return的返回值,需要将上面定义的变量全部暴露出去
Definproperty,不能将数组传入进去,也不能获取到数据发生变化的时候。立即获取到的值,
Definproperty中的data是一次性传值的,用户体验感不好,会使页面加载速度慢,出现卡顿,不动的现象
Proxy可以监听到数组,以及数据进行更改的时的操作,可以获取到添加和修改的操作数据
其次在proxy中,data可以分批次渲染的,这样不会影响性能,也不会影响用户体验
在proxy和defineproperty中都是不可以使用异步操作的

16.请叙述Vue2和Vue3的diff算法的区别?
vue2中采用的是双指针算法,称之为虚拟dom的精细比较,在每次更新组件的时候,vue会对新旧虚拟节点进行比较,并找出需要更新的节点,然后应用到真实dom上,单某些情况可能会有性能问题
vue3中采用了完全不同的算法,静态提升+预编译,即模板编译阶段将模板转换成渲染函数,直接生成可执行代码,这种方式可以减少运行时的开销,避免由于虚拟节点比较导致的一些额外计算,提高了应用程序的性能
vue3中diff算法的实现也发生了变化,vue3中diff算法更加优化和灵活,采用的是单层级虚拟dom对比的方式,当一个节点发生变化时,只需要更新他自身的子树即可,不需要遍历整个树,同时还引入了静态节点标记和动态节点标记等优化措施,可以更细粒地控制组件的渲染和更新,提高应用性能

React

★★★ React 事件绑定原理

/*
        一、react并没有使用原生的浏览器事件,而是在基于Virtual DOM的基础上实现了合成事件,采用小驼峰命名法,默认的事件传播方式是冒泡,如果想改为捕获的话,直接在事件名后面加上Capture即可;事件对象event也不是原生事件对象,而是合成对象,但通过nativeEvent属性可以访问原生事件对象;
        二、react合成事件主要分为以下三个过程:
        1、事件注册
        在该阶段主要做了两件事:document上注册、存储事件回调。所有事件都会注册到document上,拥有统一的回调函数dispatchEvent来执行事件分发,类似于document.addEventListener("click",dispatchEvent)。
      register:
          addEventListener-click
          addEventListener-change

      listenerBank:
           {
                click: {key1: fn1, key2: fn2},
                change: {key1: fn3, key3: fn4}
           }
    2、事件合成
        事件触发后,会执行一下过程:
        (1)进入统一的事件分发函数dispatchEvent;
    (2)找到触发事件的 ReactDOMComponent;
    (3)开始事件的合成;
            —— 根据当前事件类型生成指定的合成对象
                —— 封装原生事件和冒泡机制
                —— 查找当前元素以及他所有父级
                —— 在listenerBank根据key值查找事件回调并合成到 event(合成事件结束)
   3、批处理
           批量处理合成事件内的回调函数
*/

★★★ React中的 setState 缺点是什么呢

/*
        setState执行的时候可以简单的认为,隶属于原生js执行的空间,那么就是属于同步,被react处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用setState修改值,那么在异步中会先进行合并,再进行渲染,降低了操作dom的次数,具体如下:

        (1)setState 在合成事件和钩子函数中是“异步”的,在原生事件和 `setTimeout` 中都是同步的。
        (2)setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
        (3)setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState, setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
        (4)正是由于setState存在异步的机制,如果setState修改值的时候依赖于state本身的值,有时候并不可靠,这时候我们需要传入一个回调函数作为其入参,这个回调函数的第一个参数为更新前的state值。
*/

★★★ React组件通信如何实现

/*
        react本身:
                (1)props——父组件向子组件通过props传参
                (2)实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子组件的实例方法了
                (3)回调函数——用于子组件向父组件通信,子组件调用props传递过来的方法
                (4)状态提升——两个子组件可以通过父组件定义的参数进行传参
                (5)Context上下文——一般用作全局主题
                (6)Render Props——渲染的细节由父组件控制

            状态管理:
                (1) mobx/redux/dva——通过在view中触发action,改变state,进而改变其他组件的view
*/

★★★ 类组件和函数组件的区别

/*            
            (1)语法上:函数组件是一个函数,返回一个jsx元素,而类组件是用es6语法糖class定义,继承component这个类
            (2)类组件中可以通过state进行状态管理,而在函数组件中不能使用setState(),在react16.8以后,函数组件可以通过hooks中的useState来模拟类组件中的状态管理;
            (3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助hooks来使用生命周期函数;
            (4)类组件能够捕获**最新**的值(永远保持一致),这是因为当实例的props属性发生修改时,class组件能够直接通过this捕获到组件最新的props;而函数式组件是捕获**渲染**所使用的值,已经因为javascript**闭包**的特性,之前的props参数保存在内存之中,无法从外部进行修改。
*/

★★★ 请你说说React的路由是什么?

/*
        路由分为前端路由和后端路由,后端路由是服务器根据用户发起的请求而返回不同内容,前端路由是客户端根据不同的URL去切换组件;在web应用前端开发中,路由系统是最核心的部分,当页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。

        react生态中路由通常是使用react-router来进行配置,其主要构成为:
(1)Router——对应路由的两种模式,包括<BrowsersRouter>与<HashRouter>;
(2)route matching组件——控制路径对应的显示组件,可以进行同步加载和异步加载,<Route>;
(3)navigation组件——用做路由切换和跳转,<Link>;

        BrowserRouter与HashRouter的区别:
(1)底层原理不一样:BrowserRouter使用的是H5的history API,不兼容IE9及以下版本;HashRouter使用的是URL的哈希值;
(2)path表现形式不一样:BrowserRouter的路径中没有#,例如:localhost:3000/demo/test;HashRouter的路径包含#,例如:localhost:3000/#/demo/test;
(3)刷新后对路由state参数的影响:BrowserRouter没有任何影响,因为state保存在history对象中;HashRouter刷新后会导致路由state参数的丢失;
*/

★★★★★ React有哪些性能优化的手段?

/*
    1、使用纯组件;
    2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对于相同的输入,不重复执行;
    3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
    4、路由懒加载;
    5、使用 React Fragments 避免额外标记;
    6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
    7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
    8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
    9、避免使用内联样式属性;
    10、优化 React 中的条件渲染;
    11、不要在 render 方法中导出数据;
    12、列表渲染的时候加key;
    13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
    14、类组件中使用immutable对象;
*/

★★★★ React hooks 用过吗,为什么要用?

/*
        Hooks 是React在16.8版本中出的一个新功能,本质是一种函数,可以实现组件逻辑复用,让我们在函数式组件中使用类组件中的状态、生命周期等功能,hooks的名字都是以use开头。

        react:
        1、useState——创建状态
                接收一个参数作为初始值;返回一个数组,第一个值为状态,第二个值为改变状态的函数
        2、useEffect——副作用(数据获取、dom操作影响页面——在渲染结束之后执行
                (1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的时候先执行返回函数再执行参数函数
                (2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数
    3、useRef
        返回一个可变的ref对象,此索引在整个生命周期中保持不变。可以用来获取元素或组件的实例,用来做输入框的聚焦或者动画的触发。    
    4、useMemo——优化函数组件中的功能函数——在渲染期间执行
       (1)接收一个函数作为参数,同样接收第二个参数作为依赖列表,返回值可以是任何,函数、对象等都可以
       (2)这种优化有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算            
        5、useContext——获取上下文注入的值    
                (1)接受一个context 对象,并返回该对象<MyContext.Provider> 元素的 value值;
                        const value = useContext(MyContext);        
        6、useLayoutEffect——有DOM操作的副作用——在DOM更新之后执行
                和useEffet类似,但是执行时机不同,useLayoutEffect在DOM更新之后执行,useEffect在render渲染结束后执行,也就是说useLayoutEffect比useEffect先执行,这是因为DOM更新之后,渲染才结束或者渲染还会结束         
        7、useCallback——与useMemo类似
                useMemo与useCallback相同,接收一个函数作为参数,也同样接收第二个参数作为依赖列表;useCallback是对传过来的回调函数优化,返回的是一个函数

react-router:
        被route包裹的组件,可以直接使用props进行路由相关操作,但是没有被route包裹的组件只能用withRouter高阶组件修饰或者使用hooks进行操作
        1、useHistory——跳转路由
        2、useLocation——得到url对象
        3、useParams——得到url上的参数

react-redux:
        1、useSelector——共享状态——从redux的store中提取数据
        2、useDispatch——共享状态——返回redux的store中对dispatch的引用    
*/

★★★★ 虚拟DOM的优劣如何?实现原理?

/*
        虚拟dom是用js模拟一颗dom树,放在浏览器内存中,相当于在js和真实dom中加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
        优点:
        (1)虚拟DOM具有批处理和高效的Diff算法,最终表现在DOM上的修改只是变更的部分,可以保证非常高效的渲染,优化性能;
        (2)虚拟DOM不会立马进行排版与重绘操作,对虚拟DOM进行频繁修改,最后一次性比较并修改真实DOM中需要改的部分;
        (3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比较差异,可以只渲染局部;
        缺点:
        (1)首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢;

        React组件的渲染过程:
        (1)使用JSX编写React组件后所有的JSX代码会通过Babel转化为 React.createElement执行;
        (2)createElement函数对 key和 ref等特殊的 props进行处理,并获取 defaultProps对默认 props进行赋值,并且对传入的子节点进行处理,最终构造成一个 ReactElement对象(所谓的虚拟 DOM)。
        (3)ReactDOM.render将生成好的虚拟 DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。

        虚拟DOM的组成——ReactElementelement对象结构:
        (1)type:元素的类型,可以是原生html类型(字符串),或者自定义组件(函数或class)
        (2)key:组件的唯一标识,用于Diff算法,下面会详细介绍
        (3)ref:用于访问原生dom节点
        (4)props:传入组件的props,chidren是props中的一个属性,它存储了当前组件的孩子节点,可以是数组(多个孩子节点)或对象(只有一个孩子节点)
        (5)owner:当前正在构建的Component所属的Component
        (6)self:(非生产环境)指定当前位于哪个组件实例
        (7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数(lineNumber)
*/

★★★★ React 和 Vue 的 diff 时间复杂度从 O(n^3) 优化到 O(n) ,那么 O(n^3) 和 O(n) 是如何计算出来的?

/*
        react的diff算法只需要O(n),这是因为react对树节点的比较做了一些前提假设,限定死了一些东西,不做过于复杂的计算操作,所以降低了复杂度。react和vue做了以下的假设,这样的话diff运算时只进行同层比较,每一个节点只遍历了一次。
       (1)Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计;
        (2)拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构;
        (3)对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。

        而传统的diff运算时间复杂度为O(n^3),这是因为传统的树节点要做非常完整的检查,首先需要节点之间需要两两比较,找到所有差异,这个对比过程时间复杂度为O(n^2),找到差异后还要计算出最小的转换方式,最终复杂度为O(n^3)
*/

★★★ 聊聊 Redux 和 Vuex 的设计思想

/*
        Flux的核心思想就是数据和逻辑永远单向流动,由三大部分组成 dispatcher(负责分发事件), store(负责保存数据,同时响应事件并更新数据)和 view(负责订阅store中的数据,并使用这些数据渲染相应的页面),Redux和Vuex是flux思想的具体实现,都是用来做状态管理的工具,Redux主要在react中使用,Vuex主要在vue中使用。

        Redux设计和使用的三大原则:
      (1)单一的数据源:整个应用的 state被储存在唯一一个 store中;
      (2)状态是只读的:Store.state不能直接修改(只读),必须调用dispatch(action) => store.reducer => return newState;action是一个对象,有type(操作类型)和payload(新值)属性;
      (3)状态修改均由纯函数完成:在Redux中,通过纯函数reducer来确定状态的改变,因为reducer是纯函数,所以相同的输入,一定会得到相同的输出,同时也不支持异步;返回值是一个全新的state;

        vuex由State + Muatations(commit) + Actions(dispatch) 组成:
      (1)全局只有一个Store实例(单一数据源);
      (2)Mutations必须是同步事务,不同步修改的话,会很难调试,不知道改变什么时候发生,也很难确定先后顺序,A、B两个mutation,调用顺序可能是A -> B,但是最终改变 State的结果可能是B -> A;
      (3)Actions负责处理异步事务,然后在异步回调中触发一个或多个mutations,也可以在业务代码中处理异步事务,然后在回调中同样操作;
      (4)模块化通过module方式来处理,这个跟Redux-combineReducer类似,在应用中可以通过namespaceHelper来简化使用;
*/

★★★ React中refs的作用是什么?

// ref是React提供的用来操纵React组件实例或者DOM元素的接口。主要用来做文本框的聚焦、触发强制动画等;
// 类组件
class Foo extends React.Component {
      constructor(props) {
        super(props)
        this.myRef = React.createRef()
      }
      render() {
            return 
                  <div>
                <input ref={ this.myRef } />
                <button onClick = {()=>this.handle()}>聚焦</button>
               </div>
      }
      handle() {
          // 通过current属性访问到当前元素
            this.myRef.current.focus()
      }
}
// 函数组件
function Foo() {
  const inputEl = useRef(null)
  const handle = () => {
        inputEl.current.focus()
  }
  return 
          <div>
                <input type="text" ref={ inputEl }/>
            <button onClick = {handle}>聚焦</button>
      </div>
}

★★★★ 请列举react生命周期函数。

/*
第一阶段:装载阶段3
    constructor()
    render()
    componentDidMount()
第二阶段:更新阶段2
    [shouldComponentUpdate()]
    render()
    componentDidUpdate()
第三阶段:卸载阶段1
    componentWillUnmount()

constructor生命周期:
    (1)当react组件实例化时,是第一个运行的生命周期;
    (2)在这个生命周期中,不能使用this.setState();
    (3)在这个生命周期中,不能使用副作用(调接口、dom操作、定时器、长连接等);
    (4)不能把props和state交叉赋值;

componentDidMount生命周期:
    (1)相当于是vue中的mounted;
    (2)它表示DOM结构在浏览器中渲染已完成;
    (3)在这里可以使用任何的副作用;

shouldComponentUpdate(nextProps,nextState)生命周期:
    (1)相当于一个开关,如果返回true则更新机制正常执行,如果为false则更新机制停止;
    (2)在vue中是没有的;
    (3)存在的意义:可以用于性能优化,但是不常用,最新的解决方案是使用PureComponent;
    (4)理论上,这个生命周期的作用,用于精细地控制声明式变量的更新问题,如果变化的声明式变量参与了视图渲染则返回true,如果被变化的声明式变量没有直接或间接参与视图渲染,则返回false;

componentDidUpdate生命周期:
    (1)相当于vue中的updated();
    (2)它表示DOM结构渲染更新已完成,只发生在更新阶段;
    (3)在这里,可以执行大多数的副作用,但是不建议;
    (4)在这里,可以使用this.setState(),但是要有终止条件判断。

componentWillUnmount生命周期:
    (1)一般在这里清除定时器、长连接等其他占用内存的构造器;

render生命周期:
    (1)render是类组件中唯一必须有的生命周期,同时必须有return(return 返回的jsx默认只能是单一根节点,但是在fragment的语法支持下,可以返回多个兄弟节点);
    (2)Fragment碎片写法: <React.Fragment></React.Fragment> 简写成<></>;
    (3)return之前,可以做任意的业务逻辑,但是不能使用this.setState(),会造成死循环;
    (4)render()在装载阶段和更新阶段都会运行;
    (5)当render方法返回null的时候,不会影响生命周期函数的正常执行。
*/

★★★ 组件绑定和js原生绑定事件哪个先执行?

// 先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段

★★ fetch的延时操作

// fetch语法:fetch(resource, config).then( function(response) { ... } );resource为要获取的资源,config是配置对象,包含method请求方法,headers请求头信息等

// 定义一个延时函数,返回一个promise
const delayPromise = (timeout=5000) => {
      return new Promise((resolve, reject) => {
          setTimeout(()=>{
              reject(new Error("网络错误"))
        }, timeout)
    })
}

// 定义一个fetch网络请求,返回一个promise
const fetchPromise = (resource, config) => {
      return new Promise((resolve, reject)=>{
          fetch(resource, config).then(res=>{
              resolve(res)
        })
    })
}

// promise的race静态方法接受多个promise对象组成的数组,该数组中哪个promise先执行完成,race方法就返回这个promise的执行结果
const fetchRequest = (resource, config, timeout) => {
          Promise.race([
        delayPromise(timeout), 
        fetchPromise(resource,config)
      ])
}

★★ A 组件嵌套 B 组件,生命周期执行顺序

/*
    父组件创建阶段的生命周期钩子函数 constructor
    父组件创建阶段的生命周期钩子函数 render
    子组件创建阶段的生命周期钩子函数 constructor
    子组件创建阶段的生命周期钩子函数 render
    子组件创建阶段的生命周期钩子函数 componentDidMount
    父组件创建阶段的生命周期钩子函数 componentDidMount
*/

★★★ diff 和 Key 之间的联系

/*
            diff算法即差异查找算法,对于DOM结构即为tree的差异查找算法,只有在React更新阶段才会有Diff算法的运用;react的diff运算为了降低时间复杂度,是按层比较新旧两个虚拟dom树的。diff运算的主要流程见下:

            1、tree diff : 新旧两棵dom树,逐层对比的过程就是 tree diff, 当整棵DOM树逐层对比完毕,则所有需要被按需更新的元素,必然能够被找到。
        2、component diff : 在进行tree diff的时候,每一层中,都有自己的组件,组件级别的对比,叫做 component diff。如果对比前后,组件的类型相同,则暂时认为此组件不需要更新;如果对比前后,组件的类型不同,则需要移除旧组件,创建新组件,并渲染到页面上。
        React只会匹配类型相同的组件,也就是说如果<A>被<B>替换,那么React将直接删除A组件然后创建一个B组件;如果某组件A转移到同层B组件上,那么这个A组件会先被销毁,然后在B组件下重新生成,以A为根节点的树整个都被重新创建,这会比较耗费性能,但实际上我们很少跨层移动dom节点,一般都是同层横向移动;
        3、element diff :在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做element diff。
        对于列表渲染,react会在创建时要求为每一项输入一个独一无二的key,这样就能进行高效的diff运算了。比如我们要在b和c节点中间插入一个节点f,jquery会将f这个节点后面的每一个节点都进行更新,比如c更新成f,d更新成c,e更新成d,这样操作的话就会特别多,而加了key的react咋不会频繁操作dom,而是优先采用移动的方式,找到正确的位置去插入新节点;所以我们不能省略key值,因为在对比两个新旧的子元素是,是通过key值来精确地判断两个节点是否为同一个,如果没有key的话则是见到谁就更新谁,非常耗费性能。
            当我们通过this.setState()改变数据的时候,React会将其标记为脏节点,在事件循环的最后才会重新渲染所有的脏节点以及脏节点的子树;另外我们可以使用shouldComponentUpdate这个生命周期来选择性的渲染子树,可以基于组件之前的状态或者下一个状态来决定它是否需要重新渲染,这样的话可以组织重新渲染大的子树。
*/

★★★ 虚拟 dom 和原生 dom

/*
        (1)原生dom是浏览器通过dom树渲染的复杂对象,属性非常多;
        (2)虚拟dom是存在于内存中的js对象,属性远少于原生的dom对象,它用来描述真实的dom,并不会直接在浏览器中显示;
        (3)原生dom操作、频繁排版与重绘的效率是相当低的,虚拟dom则是利用了计算机内存高效的运算性能减少了性能的损耗;
        (4)虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分,最后并在真实DOM中对修改部分进行排版与重绘,减少过多DOM节点排版与重绘损耗
*/

★★★★ 新出来两个钩子函数?和砍掉的will系列有啥区别?

// react16 中废弃了三个钩子
componentWillMount                     // 组件将要挂载的钩子
componentWillReceiveProps        // 组件将要接收一个新的参数时的钩子
componentWillUpdate                    // 组件将要更新的钩子

// 新增了方法
getDerivedStateFromProps        // 静态方法
getSnapshotBeforeUpdate

/*
        在16.8版本以后,react将diff运算改进为Fiber,这样的话当我们调用setState方法进行更新的时候,在reconciler 层中js运算会按照节点为单位拆分成一个个小的工作单元,在render前可能会中断或恢复,就有可能导致在render前这些生命周期在进行一次更新时存在多次执行的情况,此时如果我们在里面使用ref操作dom的话,就会造成页面频繁重绘,影响性能。
        所以废弃了这几个will系列的勾子,增加了 getDerivedStateFromProps这个静态方法,这样的话我们就不能在其中使用this.refs以及this上的方法了;getSnapshotBeforeUpdate 这个方法已经到了commit阶段,只会执行一次,给想读取 dom 的用户一些空间。
*/

★★★ react中如何打包上传图片文件

★★★ 对单向数据流和双向数据绑定的理解,好处?

/*
        react的单向数据流是指只允许父组件向子组件传递数据,子组件绝对不能修改父组件传的数据,如果想要修改数据,则要在子组件中执行父组件传递过来的回调函数,提醒父组件对数据进行修改。数据单向流让所有的状态改变可以追溯,有利于应用的可维护性;
        angular中实现了双向数据绑定,代码编写方便,但是不利于维护
*/

★★ React 组件中 props 和 state 有什么区别?

/*
        1、props是从外部传入组件的参数,一般用于父组件向子组件通信,在组件之间通信使用;state一般用于组件内部的状态维护,更新组建内部的数据,状态,更新子组件的props等
        2、props不可以在组件内部修改,只能通过父组件进行修改;state在组件内部通过setState修改;
*/

★★★★ react中 setState 之后做了什么?

/*
        如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法,渲染数据;
        如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;
*/

★★★★ redux本来是同步的,为什么它能执行异步代码?中间件的实现原理是什么?

/*
        当我们需要修改store中值的时候,我们是通过 dispatch(action)将要修改的值传到reducer中的,这个过程是同步的,如果我们要进行异步操作的时候,就需要用到中间件;中间件其实是提供了一个分类处理action的机会,在 middleware 中,我们可以检阅每一个流过的action,并挑选出特定类型的 action进行相应操作,以此来改变 action;

         applyMiddleware 是个三级柯里化的函数。它将陆续的获得三个参数:第一个是 middlewares 数组,第二个是 Redux 原生的 createStore,最后一个是 reducer;然后applyMiddleware会将不同的中间件一层一层包裹到原生的 dispatch 之上;
         redux-thunk 中间件的作用就是让我们可以异步执行redux,首先检查参数 action 的类型,如果是函数的话,就执行这个 action这个函数,并把 dispatch, getState, extraArgument 作为参数传递进去,否则就调用next让下一个中间件继续处理action。
*/

// redux-thunk部分源码
function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument)
    }
    return next(action)
  }
}
const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware
export default thunk

★★★★ 列举重新渲染 render 的情况

this.setState() 
this.forceUpdate()
// 接受到新的props
// 通过状态管理,mobx、redux等
// 改变上下文

★★★ React 按需加载

// 1、使用React.lazy, 但是React.lazy技术还不支持服务端渲染
const OtherComponent = React.lazy(() => import('./OtherComponent'))
// 2、使用Loadable Components这个库
import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))

★★★★★ 调用this.setState之后,React都做了哪些操作?怎么拿到改变后的值?

/*
        如果是在隶属于原生js执行的空间,比如说setTimeout里面,setState是同步的,那么每次执行setState将立即更新this.state,然后触发render方法;因为是同步执行,可以直接获取改变后的值;
        如果是在被react处理过的空间执行,比如说合成事件里,此时setState是异步执行的,并不会立即更新this.state的值,当执行setState的时候,会将需要更新的state放入状态队列,在这个空间最后再合并修改this.state,触发render;setState接受第二个参数,是一个回调函数,可以在这里获取改变后的state值;
        触发render执行后,会生成一个新的虚拟dom结构,然后触发diff运算,找到变化的地方,重新渲染;        
*/

★★★ 如果我进行三次setState会发生什么

// 看情况,如果是在原生js空间,则会同步执行,修改三次state的值,调用三次render函数;如果是在react函数空间下,则会进行合并,只修改一次state的值,调用一次render。

★★★ 渲染一个react组件的过程

/*
        1、babel编译
        当我们对代码进行编译的时候,babel会将我们在组件中编写的jsx代码转化为React.createElement的表达式,createElement方法有三个参数,分别为type(元素类型)、attributes(元素所有属性)、children(元素所有子节点);
        2、生成element
        当render方法被触发以后,createElement方法会执行,返回一个element对象,这个对象描述了真实节点的信息,其实就是虚拟dom节点;
        3、生成真实节点(初次渲染)
        这时候我们会判断element的类型,如果是null、false则实例一个ReactDOMEmptyComponent对象; 是string、number类型的话则实例一个ReactDOMTextComponent对象; 如果element是对象的话,会进一步判断type元素类型,是原生dom元素,则实例化ReactDOMComponent; 如果是自定义组件,则实例化ReactCompositeComponentWrapper;
        在这些类生成实例对象的时候,在其内部会调用 mountComponent方法,这个方法里面有一系列浏览器原生dom方法,可以将element渲染成真实的dom并插入到文档中;
        4、生命周期
        componentDidMount:会在组件挂载后(插入DOM树中) 立即调用。一般可以在这里请求数据;
        componentDidUpdate:会在数据更新后立即调用,首次渲染不会执行此方法;可以在其中直接调用 setState,但必须用if语句进行判断,防止死循环;
        conponentWillUnmount:会在组件卸载及销毁之前调用,在此方法中执行必要的清理操作,如清除timer;
        static getDerivedStateFromProps(prps,state):这个生命周期函数代替了componentWillMount和componentWillUpdate生命周期;props和state发生改变则调用,在初始化挂载及后续更新时都会被调用,返回一个对象来更新state,如果返回null则不更新任何内容;
        shouldComponentUpdate(nextProps,nextState):这个生命周期函数的返回值用来判断React组件是否因为当前 state 或 props 更改而重新渲染,默认返回值是true;这个方法在初始化渲染或使用forceUpdate()时不会调用;当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化。
*/

★★★ React类组件,函数组件,在类组件修改组件对象会使用。

★★★★ 类组件怎么做性能优化?函数组件怎么做性能优化?

/*
类组件:
    (1)使用shouldComponentUpdate:这个生命周期可以让我们决定当前状态或属性的改变是否重新渲染组件,默认返回ture,返回false时不会执行render,在初始化渲染或使用forceUpdate()时不会调用;如果在shouldComponentUpdate比较的值是引用类型的话,可能达不到我们想要的效果,因为引用类型指向同一个地址;
        当将旧的state的值原封不动赋值给新的state(即不改变state的值,但是调用了setState)和 无数据交换的父组件的重新渲染都会导致组件重新渲染,这时候都可以通过shouldComponentUpdate来优化;
    (2)React.PureComponent:基本上和Component用法一致,不同之处在于 PureComponent不需要开发者自己设置shouldComponentUpdate,因为PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate;但是如果props和state对象包含复杂的数据结构,它可能会判断错误(表现为对象深层的数据已改变,视图却没有更新);
    (4)使用Immutable:immutable是一种持久化数据,一旦被创建就不会被修改,修改immutable对象的时候返回新的immutable;也就是说在使用旧数据创建新数据的时候,会保证旧数据同时可用且不变;为了避免深度复制所有节点的带来的性能损耗,immutable使用了结构共享,即如果对象树中的一个节点发生变化,只修改这个节点和受他影响的父节点,其他节点仍然共享;
    (5)bind函数:在react中改变this的指向有三种方法,a)constructor中用bind绑定; b)使用时通过bind绑定; 3)使用箭头函数;选择第一种只在组件初始化的时候执行一次,第二种组件在每次render都要重新绑定,第三种在每次render时候都会生成新的箭头函数,所以选择第一种;

函数组件:
    (1)useCallback:接收一个函数作为参数,接收第二个参数作为依赖列表,返回值为函数,有助于避免在每次渲染时都进行高开销的计算,仅会在某个依赖项改变时才重新计算;可以使用useCallback把要传递给子组件的函数包裹起来,这样父组件刷新的时候,传递给子组件的函数指向不会发生改变,可以减少子组件的渲染次数;
            const handleUseCallback=useCallback(handleClick,[])
            <Child handleClick={handleUseCallback} />
    (2)useMemo:useMemo的使用和useCallback差不多,只是useCallback返回的是一个函数,useMemo返回值可以是函数、对象等都可以;

两者都可使用:
    (1)React.memo:React.memo 功能同React.PureComponent,但React.memo是高阶组件,既可以用在类组件中也可以用在函数组件中;memo还可以接收第二个参数,是一个可定制化的比较函数,其返回值与 shouldComponentUpdate的相反;
    (2)使用key:在列表渲染时使用key,这样当组件发生增删改、排序等操作时,diff运算后可以根据key值直接调整DOM顺序,避免不必要的渲染而避免性能的浪费;
    (3)不要滥用props:尽量只传需要的数据,避免多余的更新,尽量避免使用{…props};
*/

★★★ useEffect 和 useLayoutEffect 的区别

/*    
2、useEffect和useLayout都是副作用hooks,两则非常相似,同样都接收两个参数:
            (1)第一个参数为函数,第二个参数为依赖列表,只有依赖更新时才会执行函数;返回一个函数,当页面刷新的或销毁的时候执行return后的代码;
            (2)如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用useEffect的回调函数;

        useEffect和 useLayout的主要区别就是他们的执行时机不同,在浏览器中js线程与渲染线程是互斥的,当js线程执行时,渲染线程呈挂起状态,只有当js线程空闲时渲染线程才会执行,将生成的 dom绘制。useLayoutEffect在js线程执行完毕即dom更新之后立即执行,而useEffect是在渲染结束后才执行,也就是说 useLayoutEffect比 useEffect先执行。
*/

★★★ hooks 的使用有什么注意事项

/*
        (1)只能在React函数式组件或自定义Hook中使用Hook。
        (2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
*/

★★★ 纯函数有什么特点,副作用函数特点

/*
        纯函数与外界交换数据只有一个唯一渠道——参数和返回值;函数从函数外部接受的所有输入信息都通过参数传递到该函数内部;函数输出到函数外部的所有信息都通过返回值传递到该函数外部。
        纯函数的优点:无状态,线程安全;纯函数相互调用组装起来的函数,还是纯函数;应用程序或者运行环境可以对纯函数的运算结果进行缓存,运算加快速度。
        函数副作用是指当调用函数时,除了返回函数值之外,还对主调用函数产生附加的影响。比如调接口、修改全局变量、抛出一个异常或以一个错误终止、打印到终端或读取用户输入、读取或写入一个文件等,所以说副作用是编程中最关键的部分,因为我们需要跟用户、跟数据进行交互。
*/

★★★ 在构造函数调用 `super` 并将 `props` 作为参数传入的作用是啥?

/*
        ES6 中在调用 super()方法之前,子类构造函数无法使用this引用,在react的类组件中也是如此;将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
*/

★★★ 讲讲什么是 JSX ?

/*
        JSX全称为JavaScript XML,是react中的一种语法糖,可以让我们在js代码中脱离字符串直接编写html代码;本身不能被浏览器读取,必须使用@babel/preset-react和webpack等工具将其转换为传统的JS。
        主要有以下特点:
        (1)类XML语法容易接受,结构清晰;
    (2)增强JS语义;
    (3)抽象程度高,屏蔽DOM操作,跨平台;
    (4)代码模块化;
*/

★★★ 为什么不直接更新 `state` 呢?

// 如果试图直接更新 state ,则不会重新渲染组件;需要使用setState()方法来更新 state这样组件才会重新渲染;

★★★ 这三个点(...)在 React 干嘛用的?

// ...是es6语法新出的规范,叫做展开运算符;在react中可以将对象或数组进行展开,让我们操作改变数据结构非常方便。

★★★ React 中的 `useState()` 是什么?

// useState是一个内置的React Hook,可以让我们在函数组件中像类组件一样使用state并且改变state的值。

★★★ React 中的StrictMode(严格模式)是什么?

/*
    React的StrictMode是一种辅助组件,用<StrictMode />包装组件,可以帮助我们编写更好的react组件,不会渲染出任何可见的ui;仅在开发模式下运行,它们不会影响生产构建,可以做以下检查:
    (1)验证内部组件是否遵循某些推荐做法,如果没有,会在控制台给出警告;
    (2)验证是否使用的已经废弃的方法,如果有,会在控制台给出警告;
    (3)通过识别潜在的风险预防一些副作用。
*/

★★★ 为什么类方法需要绑定到类实例?

// 在 JS 中,this 值会根据当前上下文变化。在 React 类组件方法中,开发人员通常希望 this 引用组件的当前实例,因此有必要将这些方法绑定到实例。通常这是在构造函数中完成的:

★★★★ 什么是 prop drilling,如何避免?

/*
        从一个外部组件一层层将prop传递到内部组件很不方便,这个问题就叫做 prop drilling;主要缺点是原本不需要数据的组件变得不必要地复杂,并且难以维护,代码看起来也变得冗余,不优雅;
        为了避免prop drilling,一种常用的方法是使用React Context。通过定义提供数据的Provider组件,并允许嵌套的组件通过 Consumer组件或 useContext Hook 使用上下文数据。
*/

★★ 描述 Flux 与 MVC?
/*
        传统的 MVC 模式在分离数据(Model)、UI(View和逻辑(Controller)方面工作得很好,但是 MVC 架构经常遇到两个主要问题:
        数据流不够清晰——跨视图发生的级联更新常常会导致混乱的事件网络,难于调试。
        缺乏数据完整性——模型数据可以在任何地方发生突变,从而在整个UI中产生不可预测的结果。

        使用 Flux 模式的复杂用户界面不再遭受级联更新,任何给定的React 组件都能够根据 store 提供的数据重建其状态。Flux 模式还通过限制对共享数据的直接访问来加强数据完整性。
*/

★★★ 这段代码有什么问题吗?

this.setState((prevState, props) => {
  return {
    streak: prevState.streak + props.count
  }
})
// 没有问题

★★★★ 什么是 React Context?

React Context
源码解析

Context提供了一个无需为每层组件手动添加props,就能在组件树间进行数据传递的功能

某些全局属性,通过父子props传递太过繁琐,Context提供了一种组件之间共享此类值的方式,而不必显式的通过组件树逐层传递props

共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或者首选语言等

  • Context应用场景在于很多不同层级的组件访问同样的数据,这样也使得组件的复用性变差。
  • 如果你只是想避免层层传递一些属性,组件组合有时候是一个比Context更好的方案,也就是直接传递组件
  • 所以一个技术方案的选定需要针对不同的场景具体分析,采取合适的方案
// ①创建
const ThemeContext = React.createContext('xxx')

// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层
return (
  <ThemeContext.Provider value="yyy">
  {children}
  <ThemeContext.Provider>
)

// ③使用---消费者 需要使用共享数据的子类中
// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext 
render() {
  let value = this.context
  /* 基于这个值进行渲染工作 */
}
// 方式三
return(
  <ThemeContext.Consumer>
  { value => /* 基于 context 值进行渲染*/ }
  </ThemeContext.Consumer>
)

动态Context---类似父子组件

// ①创建
const ThemeContext = React.createContext({
  value: 'xxx',
  changeFunc: () => {} //通过context传递这个函数,让consumers组件更新context
})
// ②注入
return (
  <ThemeContext.Provider value="yyy">
    <Child changeFunc={this.changeFunc}> 
  <ThemeContext.Provider>
)
// ③消费
return(
  <ThemeContext.Consumer>
  { ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
  </ThemeContext.Consumer>
)

消费多个Context、注意事项等参考React中文网

★★★★★ 什么是 React Fiber?

React Fiber En

  • Fiber是React16中新的协调引擎,它的主要目的是使Virtual DOM可以进行增量式渲染,让界面渲染更流畅
  • 一种流程控制原语,也称为协程,可以类比es6中的generator函数;React渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
  • 一个执行单元,每次执行完一个“执行单元”,React就会检查现在还剩多少时间,如果没有时间就将控制权让出去。
  • 目标
  • 把可中断的工作拆分成小任务
  • 对正在做的工作调整优先次序、重做、复用上次(做了一半的)成果
  • 在父子任务之间从容切换(yield back and forth),以支持React执行过程中的布局刷新
  • 支持render()返回多个元素
  • 更好地支持error boundary
  • 特性
  • 增量渲染(把渲染任务拆分成块,匀到多帧)
  • 更新时能够暂停,终止,复用渲染任务
  • 给不同类型的更新赋予优先级
  • 并发方面新的基础能力

★★★ 如何在 React 的 Props 上应用验证?

使用PropTypes进行类型检查

PropTypes自React v15.5起,请使用这个库prop-types

  • 随着应用的不断增长,也是为了使程序设计更加严谨,我们通常需要对数据的类型(值)进行一些必要的验证
  • 出于性能方面的考虑,propTypes仅在开发模式下进行检测,在程序运行时就能检测出错误,不能使用到用户交互提醒用户操作错误等
  • 也可以使用Flow或者TypeScript做类型检查,后期建议用typescript进行替代更好

我们在组件类下添加一个静态属性 propTypes (属性名不能更改),它的值也是一个对象,用来设置组件中props的验证规则,key 是要验证的属性名称,value 是验证规则。

// 类组件
import PropTypes from 'prop-types';
class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}
// 指定 props 的默认值:
Greeting.defaultProps = {
  name: 'Stranger'
};
// 类组件在这里做检测
Greeting.propTypes = {
  // v15.4 and below
  // name: React.PropTypes.string
  name: PropTypes.string
};

// 函数组件
unction HelloWorldComponent({ name }) {
  return (
    <div>Hello, {name}</div>
  )
}
// 函数组件在这里做检测
HelloWorldComponent.propTypes = {
  name: PropTypes.string
}
export default HelloWorldComponent

更多检测的类型

★★ 在 React 中使用构造函数和 getInitialState 有什么区别?

参考-stackoverflow

// ES6
class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { /* initial state */ };
  }
}
// ES5
var MyComponent = React.createClass({
  getInitialState() {
    return { /* initial state */ };
  },
});
  • 本质上其实是等价的?
  • 区别在于ES6和ES5本身,getInitialState 是搭配 React.createClass 使用的, constructor 是搭配 React.Component 使用的
  • 在React组件的生命周期中 constructor 先于 getInitialState

如何有条件地向 React 组件添加属性?

参考-stackoverflow

  • 对于某些属性,React足够智能可以忽略该属性,比如值为boolean值属性的值
  • 也可以写控制语句管理是否给组件添加属性

★★★★ Hooks 会取代 `render props` 和高阶组件吗?

官方简答

  • 可以取代,但没必要
  • 在Hook的渐进策略中也有提到,没有计划从React中移除class,在新的代码中同时使用Hook和class,所以这些方案目前还是可以有勇武之地
  • 为什么要把这3种技术拿过来对比?

都在处理同一个问题,逻辑复用

  • 高阶组件HOC---不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的 简单技术?

React16.8新增的特性,是一些可以让你在函数组件里“钩入”React state及生命周期等特性的函数,

虽然 HOC & Render Props 能处理逻辑复用的问题,但是却存在各自的问题。

HOC 存在的问题

  • 写法破坏了原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 不要在 render 方法中使用 HOC 每次调用render函数会创建一个新的高阶组件导致该组件及其子组件的状态丢失
  • 需要修复静态方法,即拷贝原组件的静态方法到高级组件中
  • 如需传递Ref则需要通过React.forwardRef创建组件

Render Props 存在的问题

  • 同样的写法会破坏原来组件的结构,DevTools中组件会形成“嵌套地狱”
  • 与React.PureComponent组件使用有冲突

Hook 目前最优雅的实现,React为共享状态逻辑提供最好的原生途径

  • 没有破坏性改动,完全可选,100%向后兼容
  • 解决复杂组件,中逻辑状态、副作用和各种生命周期函数中逻辑代码混在一起,难以拆分,甚至形成bug的问题
  • 处理class组件中
  • 在函数组件中意识到要向其添加一些state---useState
  • 有副作用的行为时
  • 只能在函数最外层调用Hook,不要在循环、条件判断或者子函数中调用
  • 只能在函数组件或者自定义Hook中调用Hook

★★★ 如何避免组件的重新渲染?

当porps/state改变时组件会执行render函数也就是重新渲染

  • class组件中 使用shouldComponentUpdate钩子函数
  • PureComponent默认有避免重新渲染的功能
  • 函数组件使用高阶组件memo处理

★★★ 什么是纯函数?

一个不会更改入参,且多次调用下相同的入参始终返回相同的结果

★★★★ 当调用`setState`时,React `render` 是如何工作的?

调用setState()

  • 检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,expirationtime等…)
  • 根据优先级相关参数判断更新模式是sync(同步更新)或是batched(批量处理)
  • 加入执行更新事件的队列,生成事件队列的链表结构
  • 根据链表顺序执行更新

setState既是同步的,也是异步的。同步异步取决于setState运行时的上下文。且setState 只在合成事件和钩子函数中是“异步”的,在原生DOM事件和 setTimeout 中都是同步的

render如何工作

  • React在props或state发生改变时,会调用React的render方法,创建一颗不同的树
  • React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
  • diff算法,将两颗树完全比较更新的算法从O(n^3^),优化成O(n);
  • 同层节点之间相互比较,不会跨节点比较
  • 不同类型的节点,产生不同的树结构
  • 设置key来指定节点在不同的渲染下保持稳定

★★★ 如何避免在React重新绑定实例?

  • 将事件处理程序定义为内联箭头函数
  • 使用箭头函数来定义方法
  • 使用带有 Hooks 的函数组件

★★★ 在js原生事件中 onclick 和 jsx 里 onclick 的区别

js原生中

  • onclick添加事件处理函数是在全局环境下执行,污染了全局环境,
  • 且给很多dom元素添加onclick事件,影响网页的性能,
  • 同时如果动态的从dom树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露

jsx里的onClick

  • 挂载的函数都控制在组件范围内,不会污染全局空间
  • jsx中不是直接使用onclick,而是采取了事件委托的方式,挂载最顶层DOM节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个onClick都挂载一个事件处理函数要高
  • 加上React控制了组件的生命周期,在unmount的时候自然能够清楚相关的所有事件处理函数,内存泄露不再是一个问题

★★★★ diff复杂度原理及具体过程画图

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 通过分层求异的策略,对 tree diff 进行算法优化;
  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
  • React 通过设置唯一 key的策略,对 element diff 进行算法优化;
  • 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
  • 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

★★★★ shouldComponentUpdate的作用是什么?

shouldComponentUpdate的作用

  • 不常用的生命周期方法,能影响组件是否重新渲染
  • 在更新阶段,当有new props 或者 调用了 setState()方法,在render方法执行前会执行到,默认返回值为true,如果返回false则不刷新组件
  • 如果你知道在什么情况下组件不需要更新,你可以让其返回值为false跳过整个渲染过程
  • 次方法仅作为 性能优化方式 而存在,不要企图靠此方法来阻止渲染,
  • 大部分情况下,使用PureComponent代替手写shouldComponentUpdate,仅浅层对比
  • 不建议在shoulComponentUpdate中进行深层或者使用JSON.stringify(),这样非常影响效率和性能
  • 作为React组件中不常用的生命周期函数,能影响组件是否重渲染
  • 建议做浅层次的比较,来优化性能,当然这里也可以用PureComponent组件代替
  • 如果有较深层次的比较则可能会导致更严重的性能问题,因此在这种情况下不要靠手动管理组件的重新渲染来优化性能,要找其他方式
  • 比如?

★★★ React组件间信息传递

  • 1.(父组件)向(子组件)传递信息 : porps传值
  • 2.(父组件)向更深层的(子组件) 进行传递信息 : context
  • 3.(子组件)向(父组件)传递信息:callback
  • 4.没有任何嵌套关系的组件之间传值(比如:兄弟组件之间传值): 利用共同父组件context通信、自定义事件
  • 5.利用react-redux进行组件之间的状态信息共享 : 组件间状态信息共享:redux、flux、mobx等

★★★ React状态管理工具有哪些?redux actionCreator都有什么?

  • 简单状态管理:组件内部state、基*于Context API封装、
  • 复杂状态管理:redux(单项数据流)、mobx(响应式数据流)、RxJS(stream)、dva
  • 创建各种action,包含同步、异步,然后在组件中通过dispatch调用

★★★★ 什么是高阶组件、受控组件及非受控组件?都有啥区别

定义

  • 高阶组件HOC---不是 React API 的一部分,是基于 React 的组合特性形成的设计模式。

高阶组件是参数为组件,返回值为新组件的函数(将组件转换为另一个组件,纯函数,无副作用)

在表单元素中,state是唯一数据源,渲染表单的React组件控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素叫做受控组件

表单数据由DOM节点来处理,而不是用state来管理数据,一般可以使用ref来从DOM节点中获取表单数据

区别

  • 受控组件和非受控组件是表单中的组件,高阶组件相当于对某个组件注入一些属性方法
  • 高阶组件是解决代码复用性问题产生的技术
  • 受控组件必须要有一个value,结合onChange来控制这个value,取值为event.target.value/event.target.checked
  • 非受控组件相当于操作DOM,一般有个defaultValue,通过onBlur触发响应方法

★★★ vuex 和 redux 的区别?

Redux

  • Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

Redux

  • 随着JS单页面应用日趋复杂,JS需要管理比任何时候都要多的state(服务器响应,缓存数据,本地生成尚未持久化到服务器的数据,UI状态等)
  • 伴随的现象就是models和views相互影响,你难以弄清楚变化的源头
  • 也就是变化和异步让我们的state变得一团糟

★★★ Redux遵循的三个原则是什么?

单一数据源

  • 整个应用的state被存储在一棵object tree中,并且整个 object tree 只存在于唯一一个 store 中

State是只读的

  • 唯一改变state的方法就是触发 action,action是一个描述已发生事件的普通对象
  • 这样确保视图和网络请求不能直接修改state

使用纯函数来执行修改

  • 为了描述action如何改变state tree,你需要编写reducers

★★★ React中的keys的作用是什么?

key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个稳定的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。

  • key 的唯一性

在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同 key 的都是同一个组件。

  • key 值不可读

虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给 react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等

★★★ redux中使用setState不能立刻获取值,怎么办

setState 只在合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步

  • ①addeventListener添加的事件或者dom事件中触发
  • ②setState接收的参数还可以是一个函数,在这个函数中可以拿先前的状态,并通过这个函数的返回值得到下一个状态。
 this.setState((preState) => {
  return {
    xxx: preState.xxx + yyy
  }
})
  • ③async/await 异步调用处理

★★ 什么是JSX

当 Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了

  • 是一个 JavaScript 的语法扩展
  • 具有 JavaScript 的全部功能
  • 可以生成 React “元素”
  • JSX 也是一个表达式

★★★ React新老版生命周期函数

生命周期

New Version

  • 挂载:constructor --> getDerivedStateFromProps --> render --> componentDidMount
  • 更新:
  • New props、setState() --> getDerivedStateFromProps --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
  • forceUpdate() --> getDerivedStateFromProps --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
  • 卸载: componentWillUnmount

Old Version*

  • 挂载:constructor --> getDerivedStateFromProps --> render --> ComponentDidMount
  • 更新:
  • New props --> getDerivedStateFromProps --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
  • setState() --> shouldComponentUpdate --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
  • forceUpdate() --> render --> getSnapshotBeforeUpdate --> componentDidUpdate
  • 卸载:componentWillUnmount

★★★★ vue react都怎么检测数据变化

  • React

React默认是通过比较引用的方式(diff)进行的,不精确监听数据变化,如果不优化可能导致大量不必要的VDOM重新渲染

  • 16之前 componentWillReveiveProps 监听 props 变化
  • 16之后 getDerivedStateFromProps 监听 props
  • Vue
  • vue监听变量变化依靠 watch Object.defineProperty,Vue通过“getter/setter”以及一些函数的劫持,能精确知道数据变化

★★★ React中怎么让 setState 同步更新?

  • setState 回调,setState,第二个参数是一个回调函数,可实现同步
  • 引入 Promise 封装 setState,在调用时我们可以使用 Async/Await 语法来优化代码风格
setStateAsync(state) {
  return new Promise((resolve) => {
    this.setState(state, resolve)
  });
}
  • 传入状态计算函数, setState 的第一个参数,
this.setState((prevState, props) => ({
 count: prevState.count + 1
}));
  • 在 setTimeout 函数中调用 setState
  • more?

★★★★ 什么是 immutable?为什么要使用它?

immutable是一种持久化数据。一旦被创建就不会被修改。修改immutable对象的时候返回新的immutable。但是原数据不会改变。

在Rudux中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节点)。 但当你使用immutable数据的时候:只会拷贝你改变的节点,从而达到了节省性能。 总结:immutable的不可变性让纯函数更强大,每次都返回新的immutable的特性让程序员可以对其进行链式操作,用起来更方便。

因为在react中,react的生命周期中的setState()之后的shouldComponentUpdate()阶段默认返回true,所以会造成本组件和子组件的多余的render,重新生成virtual dom,并进行virtual dom diff,所以解决办法是我们在本组件或者子组件中的shouldComponentUpdate()函数中比较,当不需要render时,不render。

当state中的值是对象时,我们必须使用深拷贝和深比较!

如果不进行深拷贝后再setState,会造成this.state和nextState指向同一个引用,所以shouldComponentUpdate()返回值一定是false,造成state值改了,而组件未渲染(这里不管shouldComponentUpdate中使用的是深比较还是浅比较)。所以必须深拷贝。

如果不在shouldComponentUpdate中进行深比较,会造成即使state中的对象值没有改变,因为是不同的对象,而在shouldComponentUpdate返回true,造成不必要的渲染。

所以只能是深拷贝和深比较。

★★★ 为什么不建议在 componentWillMount 做AJAX操作

  • Fiber原因,React16之后,采用了Fiber架构,只有componentDidMount的生命周期函数确定会执行一次,其他像componentWillMount可能会执行多次
  • render 阶段 可能会被React暂停,中止或重启

★★★★ 如何在React中构建一个弹出的遮罩层

//css部分
.mask{
  background: rgba(0,0,0,0.4) !important;
  z-index: 10;
  height: 100vh;
  position: fixed;
  width: 100vw;
}
.selectMask_box{
  background: rgba(0,0,0,0);
  transition: all .2s linear
}
//js部分
handleMask=()=>{
this.setState({
   dateSelected: !this.state.dateSelected
})
}
<div
    onClick={this.handleMask}
    className={`selectMask_box ${this.state.dateSelected ? "mask" : ""} `} >

  //这里是待展示的内容,<div>...</div>
 //你可以自己设置dataSelected的初始值,同时请注意注意三元运算的顺序。
</div>

★★★★★ React中的Context的使用

React Context
源码解析

// ①创建
const ThemeContext = React.createContext('xxx')

// ②注入---提供者 在入口或者你想要注入的父类中,且可以嵌套,里层覆盖外层
return (
  <ThemeContext.Provider value="yyy">
  {children}
  <ThemeContext.Provider>
)

// ③使用---消费者 需要使用共享数据的子类中
// 方式一
static contextType = ThemeContext
// 方式二
Class.contextType = ThemeContext 
render() {
  let value = this.context
  /* 基于这个值进行渲染工作 */
}
// 方式三
return(
  <ThemeContext.Consumer>
  { value => /* 基于 context 值进行渲染*/ }
  </ThemeContext.Consumer>
)

动态Context---类似父子组件

// ①创建
const ThemeContext = React.createContext({
  value: 'xxx',
  changeFunc: () => {} //通过context传递这个函数,让consumers组件更新context
})
// ②注入
return (
  <ThemeContext.Provider value="yyy">
    <Child changeFunc={this.changeFunc}> 
  <ThemeContext.Provider>
)
// ③消费
return(
  <ThemeContext.Consumer>
  { ({value, changeFunc}) => /* 基于 context 值进行渲染,同时把changeFunc绑定*/ }
  </ThemeContext.Consumer>
)

消费多个Context、注意事项等参考React中文网

★★★★ React路由懒加载的实现

  • 原理
  • webpack代码分割
  • React利用 React.lazy与import()实现了渲染时的动态加载
  • 利用Suspense来处理异步加载资源时页面应该如何显示的问题
  • 1.React.lazy
  • 通过lazy() api来动态import需要懒加载的组件
  • import的组件目前只支持export default的形式导出
  • Suspense来包裹懒加载的组件进行加载,可以设置fallback现实加载中效果
  • React.lazy可以结合Router来对模块进行懒加载。
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'))
const AnyComponent = lazy(() => import('./routes/AnyComponent'))

...
return (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/anyManage" component={AnyComponent}/>
        ...
      </Switch>
    </Suspense>
  </Router>
  • 2.react-loadable

react-loadable是以组件级别来分割代码的,这意味着,我们不仅可以根据路由按需加载,还可以根据组件按需加载,使用方式和路由分割一样,只用修改组件的引入方式即可

// 路由懒加载(异步组件)
import Loadable from 'react-loadable';
//通用过场组件
const LoadingComponent = () => {
  return (
    <div>loading</div>
  )
}
...
export default (loader, loading=LoadingComponent) => {
  return Loadable({
    loader,
    loading
  })
}

//Route中调用
import { BrowserRouter, Route } from 'react-router-dom'
const loadable from './loadable';
const AnyComponent = loadable(() => import('./AnyComponent'))
const Routes = () => (
  <BrowserRouter>
    <Route path="/home" component={AnyComponent}/>
  </BrowserRouter>
);
export default Routes;
以下是老版中的方法
  • 3.webpack配置中使用lazyload-loader
// webpack 配置中
module: {
 rules: [
 {
 test: /.(js|jsx)$/,,
 use: [
 'babel-loader',
 'lazyload-loader'
 ]
},

// 业务代码中
// 使用lazy! 前缀 代表需要懒加载的Router
 import Shop from 'lazy!./src/view/Shop';
 // Router 正常使用
 <Route path="/shop" component={Shop} />
  • 4.import() webpack v2+

符合ECMAScript提议的import()语法,该提案与普通 import 语句或 require 函数的类似,但返回一个 Promise 对象

function component() {
 return import( /* webpackChunkName: "lodash" */ 'lodash').then(_ => {
 var element = document.createElement('div');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
 }).catch(error => 'An error occurred while loading the component');
}
// 或者使用async
async function getComponent() {
 var element = document.createElement('div');
 const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
 element.innerHTML = _.join(['Hello', 'webpack'], ' ');
 return element;
}
  • 5.requre.ensure webpack v1 v2
require.ensure([], function(require){
 var list = require('./list');
 list.show();
,'list');
<!-- Router -->
const Foo = require.ensure([], () => {
 require("Foo");
}, err => {
 console.error("We failed to load chunk: " + err);
}, "chunk-name");
//react-router2 or 3
<Route path="/foo" getComponent={Foo} />

★★★★ React-router-dom内部是怎么样实现的,怎么做路由守卫?

内部实现

react-router-dom利用了Context API,通过上下文对象将当前路由信息对象注入到 Router 组件中,所以 Router 组件中 render() 渲染的内容就是 ContextAPI 提供的 Provider 组件,然后接收 Router 组件中的当前路由信息对象。 这样 Router 组件下的所有组件都能通过上下文拿到当前路由信息对象,即其中的Switch 、 Route 、 Link 、Redirect 等组件都可以拿到当前路由信息对象,然后通过改变当前路由信息来实现动态切换 Route 组件的渲染。

  • RouterContext:react-router使用context实现跨组件间数据传递,所以react-router定义了一个routerContext作为数据源,
  • Router:BrowserRouter和HashRouter将当前路由注入到上下文中,同时路由信息包含location、match、history
  • Route:路由规则,获取RouterContext的信息(location对象),获取path和component属性,判断path和当前的location是否匹配,如果匹配,则渲染component,否则返回null,不渲染任何内容
  • Switch:遍历所有子元素(Route),判断Route的path和location是否匹配,如果匹配,则渲染,否则不渲染
  • Redireact:未能配则重定向到指定页面
  • Link/NavLink: Link组件本质就是a标签,它修改了a标签的默认行为,当点击Link时,会导航到对应的路由,导致locaiton对象的改变,出发组件的更新
  • withRouter:对传入的组件进行加强,功能就是获取routerContext上面的信息,然后作为props传给需要加强的组件
怎么做路由守卫
  • 路由里设置meta元字符实现路由拦截
  • React Router 4.0之前也像vue中一样有个钩子函数 onEnter 可实现
  • ReactRouter 4.0开始自己实现如下
// routerMap.js中
import Index from './page/index'
export default [
  { path:'/', name: 'App', component:Index, auth: true },
  ...
]

//入口文件 app.js中
import { BrowserRouter as Router, Switch } from "react-router-dom";
import FrontendAuth from "./FrontendAuth";
import routerMap from "./routerMap";
...

return (
  <Router>
    <div>
      <Switch>
        <FrontendAuth routerConfig={routerMap} />
      </Switch>
    </div>
  </Router>
)

// 高阶组件FrontendAuth 处理路由跳转,即路由守卫功能

//FrontendAuth.js
import React, { Component } from "react";
import { Route, Redirect } from "react-router-dom";
class FrontendAuth extends Component {
  // eslint-disable-next-line no-useless-constructor
  constructor(props) {
    super(props);
  }
  render() {
    const { routerConfig, location } = this.props;
    const { pathname } = location;
    const isLogin = sessionStorage.getItem("username");
    console.log(pathname, isLogin);
    console.log(location);
    // 如果该路由不用进行权限校验,登录状态下登陆页除外
    // 因为登陆后,无法跳转到登陆页
    // 这部分代码,是为了在非登陆状态下,访问不需要权限校验的路由
    const targetRouterConfig = routerConfig.find(
      (item) => item.path === pathname
    );
    console.log(targetRouterConfig);
    if (targetRouterConfig && !targetRouterConfig.auth && !isLogin) {
      const { component } = targetRouterConfig;
      return <Route exact path={pathname} component={component} />;
    }
    if (isLogin) {
      // 如果是登陆状态,想要跳转到登陆,重定向到主页
      if (pathname === "/login") {
        return <Redirect to="/" />;
      } else {
        // 如果路由合法,就跳转到相应的路由
        if (targetRouterConfig) {
          return (
            <Route path={pathname} component={targetRouterConfig.component} />
          );
        } else {
          // 如果路由不合法,重定向到 404 页面
          return <Redirect to="/404" />;
        }
      }
    } else {
      // 非登陆状态下,当路由合法时且需要权限校验时,跳转到登陆页面,要求登陆
      if (targetRouterConfig && targetRouterConfig.auth) {
        return <Redirect to="/login" />;
      } else {
        // 非登陆状态下,路由不合法时,重定向至 404
        return <Redirect to="/404" />;
      }
    }
  }
}
export default FrontendAuth;
  • 总结一下,实现路由守卫需要考虑到以下的问题:
  • 未登录情况下,访问不需要权限校验的合法页面:允许访问
  • 未登录情况下,访问需要权限校验的页面:禁止访问,跳转至登陆页
  • 未登录情况下,访问所有的非法页面:禁止访问,跳转至 404
  • 登陆情况下,访问登陆页面:禁止访问,跳转至主页
  • 登陆情况下,访问除登陆页以外的合法页面:允许访问
  • 登陆情况下,访问所有的非法页面:禁止访问,跳转至 404

★★★★ redux中sages和thunk中间件的区别,优缺点

  • 区别
  • redux-thunk异步采取 async/await redux-saga采取generate函数
  • 优缺点
  • redux-thunk

优点: 库小,代码就几行 缺点:代码臃肿,reducer不再是纯粹函数,直接返回对象,违背了当初的设计原则;action的形式不统一,异步操作太为分散,分散在了各个action中

  • redux-saga

优点: 将异步与reducer区分开了,更加优雅,适合大量APi请求,而且每个请求之间存在复杂的以来关系 缺点:学习曲线比较陡,理解async await;而且库也比较大,即使发布的最小也有25kb,gzip压缩后也有7KB,React压缩后才45kb

★★ 为什么说React是view(视图层)

  • react, 是 Facebook 推出的一个用来构建用户界面的 JavaScript 库. React 主要用于构建 UI
  • React被认为是视图层的框架是因为它是基于组件的,一切都是组件,而组件就是渲染页面的基础。不论组件中包含的jsx,methods,state,props,都是属于组件内部的
  • View(视图)是应用程序中处理数据显示的部分。视图层主要包括二个部分:1.视图层显示及交互逻辑;2.视图层的数据结构ViewObj, 包括React中的props和stats;

★★★ 怎么用useEffect模拟生命周期函数?

useEffect

  • ①默认情况下,它在第一次渲染之后和每次更新之后都会执行,无需清除的effect
// 在函数式组件中 在 return之前
// Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });
  • ②需要清除的effect:React 会在组件卸载的时候执行清除操作
 useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    // Specify how to clean up after this effect:
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  • ③使用多个effect实现关注点的分离

把类组件中的分散在多个生命周期中的同一件事件的处理,合并到同一个effect中处理

  • ④通过跳过effect进行性能优化
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

★★★ useCallback是干什么的?使用useCallback有什么好处?

useCallback

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized(缓存)版本,该回调函数仅在某个依赖项改变时才会更新 好处 当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用

★★★ 能简单说一下redux-sage的使用流程吗?

redux-saga

redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

  • Reducers 负责处理 action 的 state 更新
  • Sagas 负责协调那些复杂或异步的操作
  • ①connet to the store:本质是管理 Redux 应用异步操作的中间件
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// Create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// Mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// Then run the saga
sagaMiddleware.run(mySaga)

// Render the application
  • ②initiate a side effect:初始化副作用
import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'

// Worker saga will be fired on USER_FETCH_REQUESTED actions
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED", user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED", message: e.message});
   }
}

// Starts fetchUser on each dispatched USER_FETCH_REQUESTED action
// Allows concurrent fetches of user
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
  • ③dispath an action:组件中使用
class UserComponent extends React.Component {
  ...
  onSomeButtonClicked() {
    const { userId, dispatch } = this.props
    dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
  }
  ...
}
  • ④more:takeEvery、takeLatest、take、put、call、fork、select

★★★★ React复用组件的状态和增强功能的方法

  • ①render props模式
  • 创建Mouse组件,在组件中提供复用的状态逻辑代码
  • 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
  • 使用 props.render() 的返回值作为要渲染的内容
// 子组件
class Mouse extends React.Component{
    // mouse本组件的数据
    state={
        x:0,
        y:0
    }
    // mouse本组件的方法
    handleMouse=(e)=>{
        this.setState({
            x:e.clientX,
            y:e.clientY
        })
    }
    componentDidMount(){
        window.addEventListener('mousemove',this.handleMouse)
    };
    render(){
        // 在这里用props接收从父传过来的render函数,再把state数据作为实参传递出去
        // 其实渲染的就是从父传过来的UI结构,只是公用了Mouse组件的数据和方法
        return this.props.render(this.state)
    }
}
// 父组件
class App extends React.Component{
    render(){
        return(<div>
            <h1>app组件:{this.props.name}</h1>
            {/* 在使用mouse组件时,给mouse传递一个值(父传子),
            只不过这里的props是函数,这个函数将要用形参接受从mouse组件传递过来的实参(state数据) */}
            <Mouse render={(mouse)=>{
                return (<div>
                    <h5>我用的是Mouse的state和方法:X坐标{mouse.x}-Y坐标{mouse.y}</h5>
                </div>)
            }} />
        </div>)
    }
}
  • ②高阶组件 HOC
const EnhancedComponent = withHOC(WrappedComponent)
// 高阶组件内部创建的类组件:
class Mouse extends React.Component {
  render() {
    return <WrappedComponent {...this.state} />
  }
}
  • ③hooks:自定义hook
// 自定义hook
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);
  // ...
  return isOnline;
}
// 使用
function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

★★★ redux 和 mobx 的区别

  • ①Redux的编程范式是函数式的而Mobx是面向对象的
  • ②因此数据上来说Redux理想的是immutable的,每次都返回一个新的数据,而Mobx从始至终都是一份引用。因此Redux是支持数据回溯的
  • ③然而和Redux相比,使用Mobx的组件可以做到精确更新,这一点得益于Mobx的observable;对应的,Redux是用dispatch进行广播,通过Provider和connect来比对前后差别控制更新粒度,有时需要自己写SCU;Mobx更加精细一点
  • ④Mobx-react vs React-rdux:

redux,采取Provider和connect方式,mobx采取Provider和inject、observer

★★★ react中如何实现命名插槽

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class ParentCom extends React.Component {
    constructor(props) {
        super(props)
        console.log(props);
    }
    render() {
        return (
            <div>
                <h1>组件插槽</h1>
                {this.props.children}
                <ChildCom>
                    <h1 data-position="header">这是放在头部的内容</h1>
                    <h1 data-position="main">这是放在主体的内容</h1>
                    <h1 data-position="footer">这是放在尾部的内容</h1>
                </ChildCom>
            </div>
        )
    }
}
class ChildCom extends React.Component {
    render() {
        console.log(this.props);
        let headerCom, mainCom, footerCom
        this.props.children.forEach((item,index) => {
            if (item.props['data-position'] === 'header') {
                headerCom = item
            }else if (item.props['data-position'] === 'main') {
                mainCom = item
            }else {
                footerCom = item
            }
        })
        return (
            <div>
                <div className="header">
                   {headerCom}
                </div>
                <div className="main">
                   {mainCom}
                </div>
                <div className="footer">
                    {footerCom}
                </div>
            </div>
        )
    }
}
ReactDOM.render(
    <ParentCom>
        <h2>子组件1</h2>
        <h2>子组件2</h2>
        <h2>子组件3</h2>
    </ParentCom>,
    document.getElementById('root')
)

 简单说一下,如何在react中实现瀑布流加载?(左右两列的一个商品长列表)

瀑布流

根据红线,将数据分为两部分,然后根据两边的高度(哪边少往那边加内容)去渲染两个盒子,然后达到一个瀑布流的效果

import React, { Component,Fragment } from 'react';
import {connect} from'react-redux'
import Axios from '_axios@0.19.0@axios';
class Waterfall extends Component {
    constructor(props) {
        super(props);
        this.state = { 
            data:[],//整体的数据
            leftData:[],//左边的数据
            rightData:[]//右边的数据
         }
    }
    getHW(data){
        let heightDate = [0,0];//接收累计高度的容器数组
        let rightData =[]//渲染右侧盒子的数组
        let leftData = []//渲染左侧盒子的数组
        data.forEach(item => {
            let height = item.src.replace('http://dummyimage.com/','').substr(0,7).split('x')[1]*1;//对url地址进行一个截取,拿到高度
            let minNum = Math.min.apply(null,heightDate)// 从heighetData筛选最小项
            let minIndex = heightDate.indexOf(minNum);// 获取 最小项的小标 准备开始进行累加
            heightDate[minIndex] = heightDate[minIndex] + height;//从 heightData 中找到最小的项后进行累加, 
            if(minIndex===0){//[0]加到left [1]加到 right
                leftData.push(item)
            }else{
                rightData.push(item)
            }
        })
        this.setState({ leftData,rightData  });//重新set state
    }
    render() { 
        let {leftData,rightData} = this.state;
        console.log(leftData,rightData)
        return ( 
            <Fragment>
                <div className='left'>
                    {
                        leftData && leftData.map((item,index)=>{
                            return <img src={item.src} alt={index} key={index}/>
                        })
                    }
                </div>
                <div className='right'>
                    {
                        rightData && rightData.map((item,index)=>{
                            return <img src={item.src} alt={index} key={index}/>
                        })
                    }
                </div>
            </Fragment>
         );
    }
    componentDidMount(){
        Axios.get('/api/data').then(res=>{
            this.props.dispatch({
                type:'SET_DATA',
                data:res.data.data
            })
            this.getHW(this.props.data) //调用
        })
    }
}

export default connect(
    (state)=>{
        return{
            data:state.data,
        }
    }
)(Waterfall);

Angular

1.angular 的数据绑定采用什么机制?详述原理

答案:脏检查机制。

解析:
双向数据绑定是 AngularJS 的核心机制之一。当 view 中有任何数据变化时,会更新到 model ,当 model 中数据有变化时,view 也会同步更新,显然,这需要一个监控。

原理就是,Angular 在 scope 模型上设置了一个监听队列,用来监听数据变化并更新 view 。每次绑定一个东西到 view 上时 AngularJS 就会往 $watch 队列里插入一条 $watch ,用来检测它监视的 model 里是否有变化的东西。当浏览器接收到可以被 angular context 处理的事件时, $digest 循环就会触发,遍历所有的 $watch ,最后更新 dom。

2.AngularJS 的数据双向绑定是怎么实现的?

答案:

1、每个双向绑定的元素都有一个 watcher

2、在某些事件发生的时候,调用 digest 脏数据检测。

这些事件有:表单元素内容变化、Ajax 请求响应、点击按钮执行的函数等。

3、脏数据检测会检测 rootscope 下所有被 watcher 的元素。

$digest 函数就是脏数据监测

3,什么是Angular服务?

Angular服务是一种可注入的类,用于处理与组件无关的应用程序逻辑。它们允许您将应用程序的业务逻辑从组件中抽象出来,使代码更加可维护和可测试。

4.什么是Angular的依赖注入?

依赖注入是一种设计模式,Angular中使用它来管理组件和服务之间的依赖关系。通过使用依赖注入,您可以将组件和服务分离,并将它们的依赖关系委托给Angular框架,从而使代码更具可读性和可维护性。

依赖注入是Angular中用于管理组件和服务之间依赖关系的机制。它允许您将一个组件所依赖的服务注入到组件中,从而使组件能够使用该服务的方法和属性。

5,Angular 生命周期


生命周期函数通俗的讲就是组件创建、组件更新、组件销毁的时候会触发的一系列的方法

当 Angular 使用构造函数新建一个组件或指令后,就会按下面规定的顺序在特定时刻调用生命周期钩子:

constructor :构造函数永远首先被调用,一般用于变量初始化以及类实例化
ngOnChanges :被绑定的输入属性变化时被调用,首次调用一定在 ngOnInit 之前。输入属性发生变化是触发,但组件内部改变输入属性是不会触发的。注意:如果组件没有输入,或者使用它时没有提供任何输入,那么框架就不会调用 ngOnChanges
ngOnInit :组件初始化时被调用,在第一轮 ngOnChanges 完成之后调用,只调用一次。使用 ngOnInit 可以在构造函数之后马上执行复杂的初始化逻辑,同时在 Angular 设置完输入属性之后,可以很安全的对该组件进行构建
ngDoCheck :脏值检测时调用,在变更检测周期中 ngOnChanges 和 ngOnInit 之后
ngAfterContentInit :内容投影 ng-content 完成时调用,只在第一次 ngDoCheck 之后调用
ngAfterContentChecked: 每次完成被投影组件内容的变更检测之后调用(多次)
ngAfterViewInit :组件视图及子视图初始化完成时调用,只在第一次 ngAfterContentChecked 调用一次
ngAfterViewChecked: 检测组件视图及子视图变化之后调用(多次)
ngOnDestroy :当组件销毁时调用,可以反订阅可观察对象和分离事件处理器,以防内存泄漏
父子组件初始化时生命周期钩子调用顺序:

parent-ngOnChanges
parent-ngOnInit
parent-ngDoCheck
parent-ngAfterContent
parent-ngAfterContentked

child-ngChanges
child-ngOnInit
child-ngDoCheck
child-ngAfterContentInit
child-ngAfterContentChecked
child-ngAfterViewInit
child-ngAfterViewChecked

parent-ngAfterViewInit
parent-ngAfterViewChecked

再次检测时

parent-ngDoCheck
parent-ngAfterContentked
child-ngDoCheck
child-ngAfterContentChecked
child-ngAfterViewChecked
parent-ngAfterViewChecked

Angular 变更检测策略


Angular 有两种变更检测策略:Default 和 OnPush

可以通过在 @Component 元数据中设置 changeDetection: ChangeDetectionStrategy.OnPush 进行切换

Default:

优点:每一次有异步事件发生,Angular 都会触发变更检测,从根组件开始遍历其子组件,对每一个组件都进行变更检测,对 dom 进行更新。

缺点:有很多组件状态没有发生变化,无需进行变更检测。如果应用程序中组件越多,性能问题会越来越明显。

OnPush:

优点:组件的变更检测完全依赖于组件的输入 (@Input),只要输入值不变就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升。

缺点:必须保证输入 (@Input) 是不可变的 (可以用 Immutable.js 解决),每一次输入变化都必须是新的引用

常用装饰器

  • component 组件
  • directive 指令
  • pipe 管道
  • ngModule 模块
  • input 父传子 输入
  • output 子传父 传出
  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值