2023前端面试题

新增技术

WebP图片

谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3。

HTML

*HTML5或css新特性

  1. 新语义化标签:header、footer、section、nav、aside、article
  2. 拖拽释放(Drag and drop) API
  3. 画布Canvas绘图,用于在网页上绘制图形,可以直接在 HTML 上进行图形操作。
  4. 音频、视频API(audio,video)
  5. 本地存储 WebStorage(localStorage和sessionStorage)
  6. 表单增强(元素、属性placehoder)
  7. WebSocket:单个 TCP 连接上进行全双工通讯的协议。
  8. WebWorker 多任务
  9. 跨域资源共享(CORS) Access-Control-Allow-Origin
  10. 跨窗口通信 PostMessage
  11. 地图地理定位 Geolocation
  12. 桌面通知 Notifications

HTML5语义化标签的理解

div标签是没有语义的,语义化标签,特定的标签做特定的事。例h1,就会标识为最重要的标题。
h5新增:header、nav、footer、article、section、aside

语义化的优点:

  • 为了在没有CSS的情况下,页面也能呈现出很好地内容结构、代码结构
  • 比< div>标签有更加丰富的含义,方便开发与维护
  • 方便搜索引擎能识别页面结构,有利于SEO
  • 方便其他设备解析(如移动设备、盲人阅读器等)

图片缓存

png-8,png-24,jpeg,gif,svg。

WebP格式,谷歌(google)开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3。

图片懒加载,混动条

幻灯片

压缩

html和xml、json

html被称为超文本标记语言, 是一种描述性语言,用html 可以创建能在互联网上传输的信息页,是构成网页文档的主要语言,它是由很多的标签组成,具有简易性、与平台无关性两大要点。

xml 即可扩展标记语言,

html和xml 都是标记语言,都是基于文本编辑和修改的。

都是用于操作系统或数据结构,结构上大致相同。

都可以通过DOM 变成方式来访问。

都可以通过CSS来改变外观。

不同点

比较内容htmlxml
设计目标显示数据,如何更好地显示数据,焦点是数据外观描述数据,什么是数据,如何存放数据,焦点是数据的内容
语法不要求标记的嵌套、配对等; 不区分大小写 引号是可用可不用的; 可以拥有不带值的属性名; 过滤掉空格;严格要求嵌套、配对,并遵循DTD的树形结构; 区分大小写; 属性值必须分装在引号中; 所有的属性都必须带有相应的值; 空白部分不会被解析器自动删除; xml比html 语法要求更严格
数据和显示的关系内容描述与显示方式整合为一体内容描述与显示方式分离
标签预定义免费、自定义、可扩展
可读性及可维护性难于阅读、维护结构清晰、便于阅读、维护
结构描述不支持深层的结构描述文件结构嵌套可以复杂到任何程度
与数据库的关系没有直接联系与关系型和层状数据库均可对应和转换
超链接单文件、书签链接可以定义双向链接、多目标链接、扩展链接

xml的优势

  • xml文档的内容和结构完全分离

    在xml文档中,数据的显示样式已从文档中分离出来,而被放入相关的样式表文件中。这样一来,如果要改动数据的表现形式,就不需要改动数据本身,只要改动控制数据显示的样式表文件就可以了。xml能够确保同一网络站点的数据信息能够在不同的设备上成功显示。

  • 轻松地跨平台应用

    xml文档是基于文本的,所以很容易被人和机器阅读,也非常容易使用,纯文本文件可以方便地穿越防火墙,便于不同设备和不同系统间的信息交换。

  • 支持不同文字、不同语种间的信息交互

    xml所以来的Unicode标准,是一个支持世界上所有主要语言的混合文字符号编码系统,xml技术不但使得各种信息能在不同的计算机系统之间交互,还能跨语种、跨文化进行交流。

  • 便于信息的检索

​ 由于xml通过给数据内容贴上标记来描述其含义,并且把数据的显示格式分离出去,所以对xml文档数据的搜索就可以简单高效地进行。在此情况下,搜索引擎没有必要再去遍历整个文档,只需查找制定标记的内容就可以了。

  • 可扩展性

​ xml 允许各个组织或个人简历适合他们自己需要的标记集合或标记库,并且这些标记集合可以快速地投入到互联网的使用中。比较典型的有化学标记语言CML、数据标记语言MathML、矢量图形标记语言VML、无线通信标记语言WML等。

  • 适合面向对象的程序开发

    xml文档是非常容易阅读的,对机器也是如此。xml文档数据的逻辑结构是一种树形的层次结构,文档中的每一个元素都可以映射为一个对象,同时也可以有相应的属性和方法,因而非常适合使用面向对象的程序设计方式来开发处理这些xml文档的应用程序。

    xml不是要来取代html的,是对html的补充,用来与html协同工作的语言,基于上面这些优势,xml将来成为所有的数据处理和数据传输的常用工具非常可观。

json和xml数据的区别

  1. 数据体积方面:xml是重量级的,json是轻量级的,传递的速度更快些。
  2. 数据传输方面:xml在传输过程中比较占带宽,json占带宽少,易于压缩。
  3. 数据交互方面:json与javascript的交互更加方便,更容易解析处理,更好的进行数据交互
  4. 数据描述方面:json对数据的描述性比xml较差
  5. xml和json都用在项目交互下,xml多用于做配置文件,json用于数据交互。

如何实现浏览器内多个标签页之间的通信?

localstorge cookies等本地存储方式;

CSS

CSS 选择符、优先级、伪元素

  • id选择器(#id)
  • class类选择器 (.class)
  • 标签选择器 (div, h1, p )
  • 相邻选择器 (h1+p)
  • 子代选择器 (ui>li) 关系选择器
  • 后代选择器 (li a)
  • 通配符选择器 (*)
  • 属性选择器(a[title])
  • 伪类选择器(a:hover, li:nth-child)

多选择器(h,p)

E:first-child 匹配父元素的第一个子元素

E:hover 匹配鼠标悬停其上的E元

E:focus 匹配获得当前焦点的E

E:active 匹配鼠标已经其上按下、还没有释放的E元素

  1. 优先级就近原则,相同权重下采用离得最近的样式。
  2. 外部载入是以最后载入的定位为准。
  3. !importent >id >class >tag标签
  4. !importent比内联的style样式优先级高,内联style比id高。
  5. !importent>内联style>id选择器>class类选择器>tag标签>*通配符

*引入样式link和@import的区别

1. link是HTML标签,@import是css提供的
2. link引入的样式页面加载时同时加载,@import引入的样式需等页面加载完后再加载
 3. link是XHTML标签没有兼容性问题,@import是css2.1不兼容ie5以下。link权重大于import。
 4. link可以通过js操作DOM动态引入样式表改变样式,而@import不可以
 5. @import可以在css中再次引入其他的样式表;而link不支持。

position

  1. absolute :生成绝对定位的元素, 相对于最近一级的 定位不是 static 的父元素来进行定位,不占位置。
  2. fixed (老IE不支持)生成绝对定位的元素,通常相对于浏览器窗口来进行定位。
  3. relative 生成相对定位的元素,相对于其在普通流中的位置进行定位,占据位置,根据原本位置偏移。
  4. static [ˈstætɪk] 默认值,静止的。没有定位,元素出现在正常的流中。
  5. sticky [ˈstɪki] 生成粘性定位的元素,容器的位置根据正常文档流计算得出

display

  1. none:元素不会被显示。
  2. block:元素将显示为块级元素,此元素前后会带有换行符。
  3. inline:默认。此元素会被显示为内联元素,元素前后没有换行符。
  4. inline-block:行内块元素;
  5. display:flex;

flex

Flex 是 Flexible Box 的缩写,意为"弹性布局" 。display:flex;

flex容器六个属性

  • flex-direction 设置主轴方向,默认水平向右

     row 默认值 水平向右
     row-reverse 水平向左
     column 垂直向下
     column-reverse 垂直向下
    
  • flex-wrap 主轴项目多时,是否换行,默认不换行

     nowrap  默认值  不换行
     wrap  换行
     wrap-reverse 换行,第一行在下面
    
  • flex-flow 是flex-direction flex-wrap复合属性

     flex-flow:flex-direction flex-wrap
     flex-flow:column wrap;
    
  • justify-content 处理主轴富余空间,即项目在主轴的对齐方式(当项目的宽度没有容器的宽度大时)

     flex-start: 默认值  富余空间在主轴的结束点
     flex-end: 富余空间在主轴的起点
     
     center: 富余空间在主轴的两侧
     space-between: 富余空间在项目和项目之间
     space-around: 富余空间环绕在项目两侧
    
  • align-items: 处理交叉轴的富余空间(处理在交叉轴的摆放位置)

     flex-start: 交叉轴的起点对齐,富余空间在交叉轴的结束点
     flex-end: 富余空间在交叉轴的起点
     center: 富余空间在交叉轴的两侧
     baseline: 项目的第一行文字的基线对齐。
     stretch(默认值):如果项目未设置高度或设为auto,将占满整个容器的高度。
    
  • align-content: 当有多根主轴,设置多根主轴的位置关系,处理交叉轴上的富余空间

     flex-start:  有多根主轴,富余空间在交叉轴的结束点
     flex-end:  有多根主轴,富余空间在交叉轴的起点
     center: 有多根主轴,富余空间在交叉轴的两侧
     space-between: 有多根主轴,富余空间在多根主轴多间
     space-around: 有多根主轴,富余空间环绕在多根主轴之间
     stretch(默认值):轴线占满整个交叉轴。
    

flex项目六个属性

  • flex-grow grow生长的意思,放大比例,属性值数字,有富余空间,让项目伸长。默认0,有也不放大。

    .son1{ flex-grow: 1; }
    .son2{ flex-grow: 2; }
    .son3{ flex-grow: 4; }
    表示把富余空间分成7份,老大占1份,老二占2份,老三占4份
    
  • **flex-shrink ** shrink 压缩的意思,默认1,空间不足项目压缩。

     如果项目总宽度大于容器的宽度,设置每一个项目的压缩比例
     如果所有项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。如果一个项目的flex-shrink属性为0,其他项目都为1,则空间不足时,前者不缩小。
    
  • flex-basis 设置项目在主轴上占据的大小

    它的默认值为auto,即项目的本来大小。设置了宽度,auto就是设置的宽度,没设置宽度就是内容的宽度。
    如果也设置了宽度,那么会心flex-basis为准
    
  • flex 是上面三个属性的复合属性 用的也比较多

     flex:flex-grow flex-shrink flex-basis
     默认值:flex: 0 1 auto
     	auto表示:	设置了宽度,auto是设置的宽度;
                  没设置宽度,auto就是内容的宽度
     	flex:最后两个属性值可以不写  
       最常见的写法:flex:1;  等价于:flex-grow:1;富裕空间均分。
    
  • order 设置项目在主轴的排列顺序,属性值数字,数字越小,在主轴上越靠前

    默认0
    
  • align-self 单独设置某一个项目在交叉轴上的位置

     flex-start
     flex-end
     center   
     center
     baseline
     stretch
    

flex属性是flex-grow, flex-shrinkflex-basis, 默认值为0 1 auto。后两个属性可选。

  1. flex-grow属性定义项目的放大比例,默认为0,即如果存在剩余空间,也不放大。
  2. flex-shrink属性定义了项目的缩小比例,默认为1,即如果空间不足,该项目将缩小。
  3. flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。
  • flex:1相当于 flex-grow:1, flex-shrink:1flex-basis:0% (子元素宽度 = 父级宽度/3)
  • flex:auto相当于flex-grow:1, flex-shrink:1flex-basis:auto (子元素宽度 = 子元素宽度 +(父级宽度-子元素宽度之和/3)
  • flex:none相当于flex-grow:0, flex-shrink:0flex-basis:auto

*BFC(块级格式化上下文)

https://www.jianshu.com/p/0d713b32cd0d

  • 普通流 (Flow)

    ​ 元素在 HTML 中按照先后位置从上至下的流式排列方式布局。

  • 浮动流(Float)

在浮动布局中,元素首先按照普通流的位置呈现,然后根据浮动的方向向左边或右边偏移。

  • 定位流(Position)

在绝对定位布局中,元素会整体脱离普通流,而元素的具体位置由绝对定位的坐标决定。

BFC(Block formatting context)块级格式化上下文。它属于普通流。是页面中一个独立渲染的区域,具有 BFC 特性的元素可以看作是隔离了的独立容器,容器中的子元素在布局上不会影响到外面的元素。

是Web页面的可视CSS渲染的一部分,

是块盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

块级格式化上下文的创建,产生BFC

  1. float 元素:除 none 的值,float: left | right;
  2. 绝对定位元素:position: absolute | fixed;
  3. display: inline-block | table-cells | table-caption | inline-flex | flex;
  4. overflow 除了 visible 的值,overflow: hidden | scroll | auto;
  • 根元素
  • 浮动元素 (元素的 float 不是 none)
  • 绝对定位元素 (元素具有 position 为 absolute 或 fixed)
  • 行内元素,内联块 (元素具有 display: inline-block)
  • 表格单元格 (元素具有 display: table-cell,HTML表格单元格默认属性)
  • 表格标题 (元素具有 display: table-caption, HTML表格标题默认属性)
  • 具有overflow 且值不是 visible 的块元素,
  • display: flow-root
  • column-span: all 应当总是会创建一个新的格式化上下文,即便具有 column-span: all 的元素并不被包裹在一个多列容器中。
  • 一个块格式化上下文包括创建它的元素内部所有内容,除了被包含于创建新的块级格式化上下文的后代元素内的元素。

BFC 特性(功能)

  1. 同一个 BFC 下外边距折叠

    两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。

    两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。

    两个外边距一正一负时,折叠结果是两者的相加的和。

    ​ 上下两个盒子 div,一个设置了 margin-bottom,一个设置了 margin-top,两个 div 的边距就会重叠而取其中最大的边距为两个 div 之间的距离。如果要避免这种外边距折叠,可以新建一个 BFC 容器,将两个 div 放在不同的 BFC 容器中。

    <div> DIV1 </div>
    <div> DIV2 </div>
     
    <!-- 可以更改成如下 -->
    <div overflow: hidden;>
        <p> DIV1 </p>
    </div>
    <div> DIV2 </div>
    
  2. 清除浮动,使 BF C 内部浮动元素不会到处乱跑;

    ​ 在正常的文档流中,块级元素是按照从上自下,内联元素从左到右的顺序排列的。但如果给里面的元素一个 float 或者绝对定位,它就会脱离普通文档流中。此时还想让外层元素包裹住内层元素让外层元素产生一个 BFC。(overflow:hidden)

    <div>
        <div style="width: 100px;height: 100px;float: left;"></div>
    </div>
     
    <!-- 触发容器的BFC后 -->
    <div style="overflow: hidden">
        <div style="width: 100px;height: 100px;float: left;"></div>
    </div>
    
  3. BFC 可以阻止元素被浮动元素覆盖,双栏自适应布局

    <div style="width: 100px;height: 100px;float: left;">我是一个左浮动的元素</div>
    <div style="height: 200px;">我是一个没有设置浮动, 也没有触发 BFC 元素</div>
    
    <!-- 触发 BFC 后 -->
    <div style="width: 100px;height: 100px;float: left;">我是一个左浮动的元素</div>
    <div style="height: 200px; overflow: hidden;">我是一个没有设置浮动, 也没有触发 BFC 元素</div>
    
  4. 和浮动元素产生边界。

    <div class="left"></div>
    <div class="right"></div>
    
    div{border:3px solid red;heifht:100px}
    .left{min-width:200px;margin-right:20px;float:left;}
    .right{border-color:blue;overflow: hidden;}
    

    我们想要让普通元素与浮动元素产生左右边距,需要将非浮动元素的 margin-left 设置为 200px+20px 。

而在BFC区域的规则是:

  1. BFC内块级元素从上到下排列(似乎与非BFC一样)
  2. BFC内元素不会与float盒子重叠
  3. BFC内元素排列与外部元素不会互相影响
  4. BFC的两个相邻盒子的margin值将会重叠
  5. BFC高度的计算时,浮动元素也参与

margin塌陷和margin合并:

  1. margin塌陷一般是父子间,margin合并一般是兄弟间
  2. 解决margin合并就是以兄弟间最大的margin值进行隔开,忽略小的margin值,解决方案是给其中一个元素设置margin值为两个元素之和(不用刻意解决)
  3. 解决margin塌陷是指如果父元素没有设置border或者padding-top,那么子元素的上边距就会和父元素重叠,最好的解决办法是给父元素添加overflow:hidden(创建一个新的BFC)

清除浮动

清除浮动是为了清除使用浮动元素产生的影响。浮动的元素,高度会塌陷,而高度的塌陷使我们页面后面的布局不能正常显示

  • 父级设置高度
  • clear: both。在浮动元素末尾添加空标签清除浮动 clear:both (缺点:增加无意义标签
  • 创建父级 BFC(overflow:hidden)父元素触发块级格式化上下文
<div class="divcss5"> 
    <div float>left浮动</div> 
    <div float>right浮动</div> 
    <div style={clear:both}></div>  在父级</div>结束前使用
</div> 

盒模型

行内元素没有margin

盒模型的组成,由里向外content,padding,border,margin.

在标准的盒子模型中,width指content部分的宽度
在新的盒子模型中,width表示content+padding+border这三个部分的宽度

box-sizing: content-box 是W3C 旧的盒子模型
box-sizing: border-box 是IE 新的盒子模型

box-sizing的默认属性是content-box

三栏布局,两栏布局

三栏布局

position布局:

在三栏的父盒子设置relative,让左右两栏使用绝对定位会脱离标准文档流,中间一栏将margin分别设置为左右两栏的宽度。

<div class="content">		{position: relative;}
  <div class="left">
  	左侧	{position: absolute;top: 0;left: 0;width: 200px; height: 100px;}
  </div> 
  <div class="middle">
  	中间	{margin-left: 200px;margin-right: 200px;height: 100px;}
  </div>
  <div class="right">
  	右侧	{position: absolute;top: 0;right: 0;width: 200px; height: 100px;}
  </div>
</div>

float布局:

需要将中间的内容放在html结构的最后,否则右侧会沉在中间内容的下侧。 原理: 元素浮动后,脱离文档流,后面的元素受浮动影响,设置受影响元素的margin值即可。

<div class="content">		{overflow: hidden;} 
  <div class="left">
  	左侧	{float:left;width: 200px; height: 100px;}
  </div> 
  <div class="middle">
  	中间	{margin-left: 200px;margin-right: 200px;height: 100px;}
  </div>
  <div class="right">
  	右侧	{float:right;width: 200px; height: 100px;}
  </div>
</div>
.content{overflow: hidden;} 
.left{float:left;width: 200px; height: 100px;background-color: blue;}
.right{float:right;width: 200px; height: 100px;background-color: red;}
.middle{margin-left: 200px;margin-right: 200px;height: 100px;background-color: yellow;}

flex布局:
flex布局最简洁使用,并且没有明显缺陷。

  • 仅需将容器设置为display:flex;,盒内元素两端对其,将中间元素设置为100%宽度即可填充空白,再利用margin值设置边距即可。

  • 并且盒内元素的高度撑开容器的高度。

<div class="content">		{display: flex;} 
  <div class="left">
  	左侧	{width: 200px; height: 100px;}
  </div> 
  <div class="middle">
  	中间	{flex:1;height: 100px;}
  </div>
  <div class="right">
  	右侧	{width: 200px; height: 100px;}
  </div>
</div>
.content{display: flex;justify-content: space-between;} 
.left{width: 200px; height: 100px;background-color: blue;}
.right{width: 200px; height: 100px;background-color: red;}
.middle{width:100%;height: 100px;background-color: yellow;}

两栏布局

1、flex布局:外面display:flex;左边定宽,右边flex:1.

2、float浮动:外面 overflow: hidden;块级格式化上下文BFC,左边float:left定宽,右边margin-left:宽。

3、position:absolute。左边top:0,left:0;右边margin-left :宽。

4、calc函数:左浮动,右边width: calc(100% - 200px);

css和css3

css严格的说就是css2.0,随着css的慢慢发展,css2.0简称css了。

css3比css多了一些样式而已,一般网站上的css样式都是属于css2.0属性,而一些浏览器中的如果不兼容css3的话,就会以css样式的方式显示,最常见的方法就是圆弧角,

css3比css多了一些样式设置而已。 css3是向前兼容的,也就是说,css中有效的code在css3也有效。

box-shadow(阴影效果)

border-colors(为边框设置多种颜色)

boder-image(图片边框)

text-shadow(文本阴影)

text-overflow(文本截断)

border-radius(圆角边框)

opacity(不透明度)

过渡效果transform

先说下 transform、transition、translate的区别

transform 和 transition是css的2个属性,translate属于transform里的一个方法;

​ transform有4个方法,分别是translate平移、rotate旋转、scale缩放、skew斜切

transform: translate(xxxpx);        // 表示水平方向移动的距离
transform: translate(xxxpx, xxxpx); // 表示水平、垂直方向移动的距离

transform:rotate(xxxdeg); //使元素旋转多少度,正数为顺时针, 负数为逆时针

transform: scale(xxx); // 表示水平和垂直同时缩放多少倍
transform: scale(xxx,xxx); // 表示水平和垂直各缩放多少倍
transform: scaleX(xxx); // x轴缩放多少倍
transform: scaleY(xxx); // y轴缩放多少倍

transform: skew(xxxdeg); // 表示水平方向倾斜多少度
transform: skew(xxxdeg, xxxdeg); // 表示水平和垂直方向各倾斜多少度
transform: skewX(xxxdeg); // x轴旋转多少度
transform: skewY(xxxdeg); // Y轴旋转多少度

transition有4个值(默认是前2个值):property(指定css属性的name)、duration(动画持续时间)、timing-function(切换动画的速度)、delay(动画执行前的延迟时间)

多数显示器默认频率是60Hz,即1秒刷新60次,所以理论上最小间隔为1/60*1000ms = 16.7ms

*自适应rem布局原理(如何适应不同的手机)

rem: 相对于根元素(html)的字体大小→ 1rem = html标签的font-size

浏览器默认的font-size的大小都为16px。

rem布局的原理:

通过媒体查询的方式动态改变html标签的font-size的大小

  • 当屏幕越大,让html标签的font-size变大即可
  • 当屏幕越小,让html标签的font-size变小即可

优点:rem布局盒子适配所有的屏幕,并且可以在多个屏幕大小中完美还原设计图(等比例缩放)

适应不同的手机:就是根据屏幕的大小,动态的改变html标签的font-size的大小,此时就可以配合媒体查询做到不同屏幕的适配。

为保证等比例缩放

设计图的屏幕宽度/给设计图设置的font-size = 你需要适配的屏幕宽度/你需要适配屏幕的fong-size

如:设计图750px,设计图设置字体75px,那么当适配屏360px,适配屏的字体就是36px

calc, support, media各自的含义及用法?

  • @support主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补。
  • calc() 函数用于动态计算长度值。 calc()函数支持 “+”, “-”, “*”, “/” 运算;
  • @media 查询,你可以针对不同的媒体类型定义不同的样式。

居中

水平

行内元素: text-align: center
块级元素: margin: 0 auto
position:absolute +left:50%+ transform:translateX(-50%)
display:flex + justify-content: center

垂直

设置line-height 等于height
position:absolute +top:50%+ transform:translateY(-50%)
display:flex + align-items: center
display:table+display:table-cell + vertical-align: middle;

水平垂直居中

/* 方式一 */ 不止高度,css3偏移居中
.box {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* 方式二 */ 知道高度margin 居中
.box {
  position: absolute;
  width: 100px;
  height: 100px;
  top: 50%;
  left: 50%;
  margin-top: -50px;
  margin-left: -50px;
}
/* 方式三 */ flex居中
.box {
  display: flex;
  justify-content: center;
  align-item: center;
}

画0.5px线、画三角形

//划0.5px线
height: 1px;
transform: scale(0.5);

//画三角形
把上、左、右三条边隐藏掉(颜色设为 transparent)
 .a{
     width: 0;
     height: 0;
     border-width: 100px;
     border-style: solid;
     border-color: transparent #0099CC transparent transparent;
     transform: rotate(90deg); /*顺时针旋转90°*/
 }

opacity不透明的,visibility能见度,display显示

opacity: 0、visibility: hidden、display: none

联系:它们都能让元素不可见

  • opacity: 0: 不会让元素从渲染树消失,渲染元素占据空间,内容不可见,可以点击
  • visibility: hidden:不会让元素从渲染树消失,渲染元素占据空间,只是内容不可见,不能点击
  • display:none: 会让元素完全从渲染树中消失,渲染时不占据任何空间, 不能点击,

继承:
display: none:是非继承属性,子孙节点消失由于元素从渲染树消失造成,通过修改子孙节点属性无法显示。
visibility: hidden:是继承属性,子孙节点消失由于继承了hidden,通过设置visibility: visible;可以让子孙节点显式。

rgba()和opacity的透明效果有什么不同?

rgba()和opacity都能实现透明效果,但最大的不同是opacity作用于元素,以及元素内的所有内容的透明度,

而rgba()只作用于元素的颜色或其背景色。(设置rgba透明的元素的子元素不会继承透明效果!)

display,float,position 的关系

  1. 如果display为 none,那么 position 和 float 都不起作用,这种情况下元素不产生框
  2. 否则,如果 position 值为 absolute 或者 fixed,框就是绝对定位的,float 的计算值为 none,display 根据下面的表格进行调整。
  3. 否则,如果 float 不是 none,框是浮动的,display 根据下表进行调整
  4. 否则,如果元素是根元素,display 根据下表进行调整
  5. 其他情况下 display 的值为指定值 总结起来:绝对定位、浮动、根元素都需要调整display

浏览器的兼容性,初始化样式

兼容

png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8.

浏览器默认的margin和padding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一

Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,

可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决。

怎么让Chrome支持小于12px 的文字

  1. 用图片
  2. 使用12及以上字体,和顾客讨论
  3. -webkit-text-size-adjust:none

初始化样式

因为浏览器的兼容问题,不同浏览器对有些标签的默认值是不同的,如果没对CSS初始化往往会出现浏览器之间的页面显示差异

当然,初始化样式会对SEO有一定的影响,但鱼和熊掌不可兼得,但力求影响最小的情况下初始化。

最简单的初始化方法: * {padding: 0; margin: 0;} (强烈不建议)

1rem、1em、1vh、1px各自代表的含义?

  • rem 是根据根节点的 font-size 变化,em em值不固定,是相对单位,其相对应父级元素的字体大小会调整。

  • rem是全部的长度都相对于根元素元素。通常做法是给html元素设置一个字体大小,然后其他元素的长度单位就为rem。

  • 子元素字体大小的em是相对于父元素字体大小。元素的width/height/padding/margin用em的话是相对于该元素的font-size

  • 全称是 Viewport Width 和 Viewport Height,视窗的宽度和高度,相当于 屏幕宽度和高度的 1%,不过,处理宽度的时候%单位更合适,处理高度的 话 vh 单位更好。

  • px像素(Pixel)。值固定,容易计算。相对长度单位。像素px是相对于显示器屏幕分辨率而言的。1920*1024 前者是屏幕宽度总共有1920个像素,后者则是高度为1024个像素

Sass、LESS、scss是什么?大家为什么要使用他们?

他们是CSS预处理器。他是CSS上的一种抽象层。他们是一种特殊的语法/语言编译成CSS。比css多出很多功能,如变量、运算、函数等

SASS版本3.0之前的后缀名为.sass,而版本3.0之后的后缀名.scss。scss使用{}取代了sass的缩进。

  1. 变量符:less是@、scss是$
  2. less不支持条件语句
    scss语句支持if{}else{}、for{}循环语句
  3. scss的功能比less强大,less上手简单。

完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译

css文本超出隐藏

单行隐藏

<div class="my">单行隐藏单行隐藏单行隐藏单行隐藏单行隐藏单行隐藏单行隐藏</div>
.my {
    width: 100px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space:nowrap;
}1234567

2. 多行隐藏

<div class="my">当文字超过范围的时候,超出部分会隐藏起来。可以设置第几行开始隐藏。多行隐藏多行隐藏多行隐藏多行隐藏多行隐藏多行隐藏多行隐藏多行隐藏多行隐藏</div>

.my {
    width: 100px;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient:vertical;
    -webkit-line-clamp:3;//从第3行开始隐藏
}

iframe有什么优点、缺点

优点:

  1. iframe能够原封不动的把嵌入的网页展现出来。
  2. 如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
  3. 网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
  4. 如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。

缺点:

  1. iframe会阻塞主页面的onload事件弹窗遮罩层
  2. iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。会产生很多页面,不容易管理。
  3. iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
  4. 代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO)。
  5. 很多的移动设备无法完全显示框架,设备兼容性差
  6. iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。

ES6

常用特性

  • 类(class)
  • 模块化
  • 箭头函数
  • 函数参数默认值
  • 模板字符串
  • 解构赋值
  • 延展操作符
  • 对象属性简写
  • Promise
  • Let与Const

类(class)

传统的javascript中只有对象,没有类的概念。

ES6引入了Class(类)这个概念,通过class关键字可以定义类。

使JavaScript更像是一种面向对象的语言。

*注意项:*ES6不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。

模块化(Module)

ES6的模块化分为导出(export)导入(import)两个模块。

export的用法

在ES6中每一个模块即是一个文件,在文件中定义的变量,函数,对象在外部是无法获取的。如果你希望外部可以读取模块当中的内容,就必须使用export来对其进行暴露(输出)。默认导出(default export) 一个模块只能有一个默认导出,如果要输出多个变量可以将这些变量包装成对象进行模块化输出:

*箭头函数和this

this的指向:

  • ES5中: this 永远指向最后调用它的那个对象
  • ES6箭头函数: 箭头函数的 this 始终指向函数定义时的 this,而非执行时。
  • class:this指向new的实例

怎么改变this的指向:

  • 使用 ES6 的箭头函数
  • 在函数内部使用 _this = this
  • 使用 apply、call、bind
  • new 实例化一个对象

箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值(箭头函数本身没有this,但是在它声明时可以捕获别人的this供自己使用。),如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this

function a() {
    return () => {
        return () => {
            console.log(this)
        }
    }
}
console.log(a()()())

箭头函数其实是没有 this的,这个函数中的 this只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a符合前面代码中的第一个情况,所以 thiswindow。并且 this一旦绑定了上下文,就不会被任何代码改变。

函数参数默认值

ES6支持在定义函数的时候为其设置默认值

function foo(height = 50, color = 'red') {    // ... }

模板字符串

var name = `Your name is ${first} ${last}.`

在ES6中通过${}就可以完成字符串的拼接,只需要将变量放在大括号之中。

解构赋值

可以方便的从数组或者对象中快速提取值赋给定义的变量。

延伸扩展符

//我们可以这样合并数组: 
var arr1=['a','b','c']; 
var arr2=[...arr1,'d','e']; //['a','b','c','d','e']

//用于解构赋值
let [arg1,arg2,...arg3] = [1, 2, 3, 4];
arg1 //1
arg2 //2
arg3 //['3','4']

对象属性简写

//使用ES6
const name='Ming',age='18',city='Shanghai'; 
const student = {
    name,
    age,
    city
};
console.log(student);//{name: "Ming", age: "18", city: "Shanghai"}
//对象中直接写变量,非常简洁。

ver、let、const、作用域

const与let定义的变量形成块级作用域,

ES6不存在变量提升,所以需要先定义再使用。因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。

块级作用域

变量的赋值可以分为三个阶段:

  • 创建变量,在内存中开辟空间
  • 初始化变量,将变量初始化为undefined
  • 真正赋值

关于letvarfunction

  • let的「创建」过程被提升了,但是初始化没有提升。
  • var的「创建」和「初始化」都被提升了。
  • function的「创建」「初始化」和「赋值」都被提升了。

var ——ES5 变量声明方式

  1. 在变量未赋值时,变量undefined(为使用声明变量时也为undefined)
  2. 作用域——var的作用域为方法作用域;只要在方法内定义了,整个方法内的定义变量后的代码都可以使用

let——ES6变量声明方式

  1. 在变量为声明前直接使用会报错
  2. 作用域——let为块作用域——通常let比var 范围要小
  3. let禁止重复声明变量,否则会报错;var可以重复声明

const——ES6常量声明方式

  1. const为常量声明方式;声明变量时必须初始化,在后面出现的代码中不能再修改该常量的值

  2. const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动

作用域

作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。

*promise

Promise对象用于异步操作,它表示一个尚未完成且预计在未来完成的异步操作,属于异步编程的一种解决方式。由于它的then方法和catch、finally方法会返回一个新的Promise所以可以允许我们链式调用,解决了传统的回调地狱问题。比传统的异步解决方案【回调函数】和【事件】更合理、更强大。

  1. Promise 构造函数是同步执行的,但.then是异步的

  2. Promise状态一经改变就不能再改变。

    pedding【待定】初始状态;fulfilled【实现】操作状态;rejected【被否决】操作失败

  3. .then.catch都会返回一个新的Promise

  4. .catch不管被连接到哪里,都能捕获上层未捕捉过的错误。

  5. Promise中,返回任意一个非 promise 的值都会被包裹成 promise 对象,例如return 2会被包装为return Promise.resolve(2)

  6. Promise.then 或者 .catch 可以被调用多次, 但如果Promise内部的状态一经改变,并且有了一个值,那么后续每次调用.then或者.catch的时候都会直接拿到该值。

  7. .then 或者 .catchreturn 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获。

  8. .then.catch 返回的值不能是 promise 本身,否则会造成死循环。

  9. .then 或者 .catch 的参数期望是函数,传入非函数则会发生值透传。

  10. .then方法是能接收两个参数的,第一个是处理成功的函数,第二个是处理失败的函数,再某些时候你可以认为catch.then第二个参数的简便写法。

  11. **.finally方法也是返回一个Promise,**他在Promise结束的时候,无论结果为resolved还是rejected,都会执行里面的回调函数。


(1) Promise 构造函数是同步执行的,但.then是异步的

const promise = new Promise((resolve, reject) => {
  console.log(1)
  resolve()
  console.log(2)
})
promise.then(() => {
  console.log(3)
})
console.log(4)
//运行结果:1 2 4 3

解析:Promise 构造函数是同步执行的,promise.then 中的函数是异步执行的。

(2) promise 状态一旦改变则不能再变

const promise = new Promise((resolve, reject) => {
  resolve('success1')
  reject('error')
  resolve('success2')
})
promise
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//运行结果:then: success1

解析:构造函数中的 resolve 或 reject 只有第一次执行有效,多次调用没有任何作用,promise 状态一旦改变则不能再变

(3) .then 或者 .catch 都会返回一个新的 promise

Promise.resolve(1)
  .then((res) => {
    console.log(res)
    return 2
  })
  .catch((err) => {
    return 3
  })
  .then((res) => {
    console.log(res)
  })
//运行结果:1 2

解析:promise 可以链式调用。提起链式调用我们通常会想到通过 return this 实现,不过 Promise 并不是这样实现的。promise 每次调用 .then 或者 .catch 都会返回一个新的 promise,从而实现了链式调用。

(4) .then 或者 .catch 都会返回一个新的 promise

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    console.log('once')
    resolve('success')
  }, 1000)
})
const start = Date.now()
promise.then((res) => {
  console.log(res, Date.now() - start)
})
promise.then((res) => {
  console.log(res, Date.now() - start)
})
//运行结果:once
          success 1005
          success 1007

解析:promise 的 .then 或者 .catch 可以被调用多次,但这里 Promise 构造函数只执行一次。或者说 promise 内部状态一经改变(第一次调用.then就改变了),并且有了一个值,那么后续每次调用 .then 或者 .catch 都会直接拿到该值。

(5) .then 或者 .catch 都会返回一个新的 promise

Promise.resolve()
  .then(() => {
    return new Error('error!!!')
  })
  .then((res) => {
    console.log('then: ', res)
  })
  .catch((err) => {
    console.log('catch: ', err)
  })
//运行结果:then: Error: error!!!
    at Promise.resolve.then (...)
    at ...

解析:.then 或者 .catch 中 return 一个 error 对象并不会抛出错误,所以不会被后续的 .catch 捕获,需要改成其中一种:

1. return Promise.reject(new Error('error!!!'))
2. throw new Error('error!!!')

因为返回任意一个非 promise 的值都会被包裹成 promise 对象,即 return new Error('error!!!') 等价于 return Promise.resolve(new Error('error!!!'))

(6) .then 或 .catch 返回的值不能是 promise 本身

const promise = Promise.resolve()
  .then(() => {
    return promise
  })
promise.catch(console.error)
//运行结果:TypeError: Chaining cycle detected for promise #<Promise>

解析:.then 或 .catch 返回的值不能是 promise 本身,否则会造成死循环。

(7) .then函数返回值类型与参数传递

Promise.resolve(2) // resolve(2) 函数返回一个Promise<number>对象
.then(x=>{
   console.log( x ); // 输出2, 表示x类型为number且值为2,也就是上面resolve参数值
   return "hello world"; // 回调函数返回字符串类型,then函数返回Promise<string>
}) // then函数返回Promise<string>类型
.then(x=>{
   console.log( x ); // 输出hello world,也就是上一个then回调函数返回值,表明上一个then的返回值就是下一个then的参数
}) // then函数回调函数中没有返回值,则为Promise<void>
.then(x=>{ // 前面的then的回调函数没有返回值所以这个x是undefined
   console.log( x ); // undefined
}) // Promise<void>
.then(()=>{ // 前面没有返回值,这里回调函数可以不加返回值
   return Promise.resolve("hello world"); // 返回一个Promise<string>类型
}) // 这里then的返回值是Promise<string>
.then(x=>{ // 虽然上面的then中回调函数返回的是Promise<string>类型但是这里x并不是Promise<string>类型而是string类型
   console.log(x); // hello world
   return Promise.resolve(2); // 返回一个Promise<number>类型对象
}) // 返回Promise<number>类型

(8) .then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log)  //1

解析:.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。

(9) .catch 相当于.then的简写(省略了.then的第二个参数)

Promise.resolve()
  .then(function success (res) {
    throw new Error('error')
  }, function fail1 (e) {
    console.error('fail1: ', e)
  })
  .catch(function fail2 (e) {
    console.error('fail2: ', e)
  })
//运行结果:fail2: Error: error
          at success (...)
          at ...

解析:.then 可以接收两个参数第一个是处理成功的函数,第二个是处理错误的函数。.catch也相当于是一个.then,只不过把.then的第二个参数省略了,但是它们用法上有一点需要注意:.then 的第二个处理错误的函数捕获不了第一个处理成功的函数抛出的错误,而后续的 .catch 可以捕获之前的错误。

(10) 微任务宏任务执行顺序

process.nextTick(() => {
  console.log('nextTick')
})
Promise.resolve()
  .then(() => {
    console.log('then')
  })
setImmediate(() => {
  console.log('setImmediate')
})
console.log('end')
//运行结果:end
          nextTick
          then
          setImmediate

解析:process.nextTickpromise.then 都属于 microtask,而 setImmediate 属于 macrotask,在事件循环的 check 阶段执行。事件循环的每个阶段(macrotask)之间都会执行 microtask,事件循环的开始会先执行一次 microtask。

async/await,区别promise

async await是promise的语法糖

  • 概念
    Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的

    async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。

  • 两者的区别
    1. Promise的出现解决了传统callback函数导致的“地域回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而**async await代码看起来会简洁些,使得异步代码看起来像同步代码,**await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。
    2. async await与Promise一样,是非阻塞的。
    3. async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。

在ES8中加入了对async/await的支持,也就我们所说的异步函数async其实就是 Generator的语法糖。 一个自动执行的Generator函数

  1. 基于promise的语法糖, 简化了promise对象的使用(不再使用回调函数编码)

  2. 以同步编码方式实现的异步流程

  3. 是js异步编程的终极解决方案(基本上可以这样说)

async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。 aysnc函数返回值为 Promiseasync函数完全可以看作多个异步操作,包装成的一个Promise 对象,而await命令就是内部then命令的语法糖。

优缺点

优点

(1)async/awsit他做到了真正的串行的同步写法,代码阅读相对容易
(2)对于条件语句和其他流程语句比较友好,可以直接写到判断条件里面
(3)async/await处理复杂流程时,在代码清晰度方面具有优势

缺点

无法处理Promise返回reject对象,要借助try…catch

async/await中await只能串行,做不到并行,{await不在同一个async函数里就可以并行}

缺点在于滥用 await 可能会导致性能问题,因为 await 会阻塞代码,也许之后的异步代码并不依赖于前者,但仍然需要等待前者完成,导致代码失去了并发

同步和异步

牛客网异步和同步:https://www.nowcoder.com/tutorial/96/33fec0f911f34bccbb8215d41814f70c

同步

  • 指在 主线程上排队执行的任务,只有前一个任务执行完毕,才能继续执行下一个任务。
  • 也就是调用一旦开始,必须这个调用 返回结果(划重点——)才能继续往后执行。程序的执行顺序和任务排列顺序是一致的。

异步

  • 异步任务是指不进入主线程,而进入 任务队列的任务,只有任务队列通知主线程,某个异步任务可以执行了,该任务才会进入主线程。
  • 每一个任务有一个或多个 回调函数。前一个任务结束后,不是执行后一个任务,而是执行回调函数,后一个任务则是不等前一个任务结束就执行。
  • 程序的执行顺序和任务的排列顺序是不一致的,异步的。
  • 我们常用的setTimeout和setInterval函数,Ajax都是异步操作。
实现异步的方法

回调函数(Callback)、事件监听、发布订阅、Promise/A+、生成器Generators/ yield、async/await

  1. JS 异步编程进化史:callback -> promise -> generator -> async + await

  2. async/await 函数的实现,就是将 Generator 函数和自动执行器,包装在一个函数里。

  3. async/await可以说是异步终极解决方案了。

    (1) async/await函数相对于Promise,优势体现在:

    • 处理 then 的调用链,能够更清晰准确的写出代码
    • 并且也能优雅地解决回调地狱问题。

    当然async/await函数也存在一些缺点,因为 await 将异步代码改造成了同步代码,如果多个异步代码没有依赖性却使用了 await 会导致性能上的降低,代码没有依赖性的话,完全可以使用 Promise.all 的方式。

    (2) async/await函数对 Generator 函数的改进,体现在以下三点:

    • 内置执行器。 Generator 函数的执行必须靠执行器,所以才有了 co 函数库,而 async 函数自带执行器。也就是说,async 函数的执行,与普通函数一模一样,只要一行
    • 更广的适用性。 co 函数库约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以跟 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)
    • 更好的语义。 async 和 await,比起星号和 yield,语义更清楚了。async 表示函数里有异步操作,await 表示紧跟在后面的表达式需要等待结果。

js

原生js

1、作用域,闭包,原型链,原型继承

1.作用域:变量或者函数的有效作用范围

作用域链:我们需要查找某个变量值,会先在当前作用域查找,如果找不到会往上一级查,如果找到的话,就返回停止查找,返回查找的值,这种向上查找的链条关系,叫作用域

ES6中let和const定义的变量具有块级作用域特性;var定义的变量会在自身的作用域中提升,let和const不会;js程序都具有一个全局作用域,每个函数也会有一个作用域;函数嵌套,作用域也会嵌套,形成作用域链,字可以访问父,父不可以访问子;执行函数找变量值会在作用域链中找;

2.闭包函数:闭包的实质是因为函数嵌套而形成的作用域链

闭包指的是能够访问另一个函数作用域中变量的函数。

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

3.原型链:

prototype原型对象 : 每个函数都会有这个属性,这里强调,是函数,普通对象是没有这个属性的(这里为什么说普通对象呢,因为JS里面,一切皆为对象,所以这里的普通对象不包括函数对象)。它是构造函数的原型对象;

proto原型: 每个对象都有这个属性,这里强调,是对象,同样,因为函数也是对象,所以函数也有这个属性。它指向构造函数的原型对象;

constructor 构造函数: 这是原型对象上的一个指向构造函数的属性。

4.原型链继承:将父类的实例作为子类的原型。实现简单,容易理解

原型继承,将子对象的prototype指向父对象的一个实例

原型对象:每一个构造器都有一个属性叫prototype,它的值是原型对象。

**隐式原型:**每一个对象都有一个属性叫__proto__,它指向了创造了这个对象的构造器的原型对象。

constructor的理解

创建的每个函数都有一个prototype(原型)对象,这个属性是一个指针,指向一个对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(继承自构造函数的prototype),指向构造函数的原型对象。注意当将构造函数的prototype设置为等于一个以对象字面量形式创建的新对象时,constructor属性不再指向该构造函数。

简述一下原型 / 构造函数 / 实例

  • 原型(prototype): 一个简单的对象,用于实现对象的 属性继承。可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个JavaScript对象中都包含一个__proto__(非标准)的属性指向它爹(该对象的原型),可obj.__proto__进行访问。
  • 构造函数: 可以通过new新建一个对象的函数。
  • 实例: 通过构造函数和new创建出来的对象,便是实例。 实例通过__proto__指向原型,通过constructor指向构造函数

JS有几种数据类型,其中基本数据类型有哪些?

基本数据类型:

Number,String,Boolean,null,undefined,symbol,bigint(后两个为ES6新增)

引用数据类型:

object,function(proto Function.prototype)

object:普通对象,数组对象,正则对象,日期对象,Math数学函数对象。

两种数据存储方式:

基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基 本类型值和执行代码的空间。

引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。

两种数据类型的区别:

  1. 堆比栈空间大,栈比堆运行速度快。
  2. 堆内存是无序存储,可以根据引用直接获取。
  3. 基础数据类型比较稳定,而且相对来说占用的内存小。
  4. 引用数据类型大小是动态的,而且是无限的。
null和undefined

null 表示一个对象是“没有值”的值,也就是值为“空”;

undefined 表示一个变量声明了没有初始化(赋值);

undefined不是一个有效的JSON,而null是;

undefined的类型(typeof)是undefined;

null的类型(typeof)是object

Javascript将未赋值的变量默认值设为undefined;

Javascript从来不会将变量设为null。它是用来让程序员表明某个用var声明的变量时没有值的

undefined :是一个表示"无"的原始值或者说表示"缺少值",就是此处应该有一个值,但是还没有定义。当尝试读取时会返回 undefined;

例如变量被声明了,但没有赋值时,就等于undefined

null : 是一个对象(空对象, 没有任何属性和方法);

在验证null时,一定要使用 === ,因为 == 无法分别 null 和 undefined。

js中typeof与instanceof用法,判断类型

typeof表示是对某个变量类型的检测,基本数据类型除了null都能正常的显示为对应的类型,引用类型除了函数会显示为'function',其它都显示为object

instanceof它主要是用于检测某个构造函数的原型对象在不在某个对象的原型链上用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

JS中会使用typeof 和 instanceof来判断一个变量是否为空或者是什么类型的。

1、typeof返回结果是该类型的字符串形式表示【6】(number、string、undefined、boolean、function、object)

注意

  • typeof对于原始类型来说,除了null都可以显示正确类型,null显示object
  • typeof对于对象来说,除了函数都会显示object
typeof "";  //string
typeof 1;   //number
typeof false; //boolean
typeof undefined; //undefined
typeof function(){}; //function
typeof {}; //object
typeof Symbol(); //symbol
typeof null; //object
typeof []; //object
typeof new Date(); //object
typeof new RegExp(); //object

2、 instanceof是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 在这里需要特别注意的是:instanceof 检测的是原型。

{} instanceof Object; //true
[] instanceof Array;  //true
[] instanceof Object; //true
"123" instanceof String; //false
new String(123) instanceof String; //true

但是instanceof可以判断出[]是Array的实例,同时也认为是Object的实例,Why????

instanceof 只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型。

​ 之后增加了**Array.isArray()**方法判断这个值是不是数组的。

总结一下:

1、typeof能够检测出了null之外的原型类型(String、Number、Boolean、Undefined),对于对象类型能判断出function、其他的都为Object

2、判断一个值是否为数组,使用Array.isArray()

3、如果需要判断一个值是否为null,最直接就是与null比较

`value === ``null``;  ``//true or false`

注意这里需要三等号操作符“===”,因为三等号操作符在进行比较的时候不会将变量强制转换为另一种类型。

由此可见,无论是typeof还是instanceof都不能准确判断出正确的类型。

封装一个准确判断数据类型的函数

function getType(obj){
  let type  = typeof obj;
  if(type != "object"){
    return type;
  }
  return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1');
}

类型转换 ===

‘52’+1 = ‘521’ 而不是我们需要的 53。

==会进行隐式类型转换,===完全匹配

强制(parseInt() 将字符串转换成整数 ,parseFloat()字符串转换成浮点数,Number()转换成数字)

隐式(== ,!!)

在JS中有哪些会被隐式转换为false

Undefined、null、关键字false、NaN、零、空字符串

将一个字符串转化为数字,将数字转化为字符串。

将字符串转换成数字,得用到parseInt函数。
parseInt(string) : 函数从string的开始解析,返回一个整数。

将字符串转换成数字,得用到String类的toString方法

var a = 'abc' + 'xyz'; //a的值为:abcxyz,字符串与字符串是连接
var a = 10 + 5; //a的值为:15,数字是加
var a = 'abc' + 10; //a的值为:abc10,字符串与数字,自动将10转换成字符串了
var a = 'abc' + 10 + 20 + 'cd'; //a的值为:abc1020cd
var a = 10 + 20 + 'abc' + 'cd'; //a的值为:30abccd,可以数字加的先数字加,然后再连接

var a=1;

console.log(a++); //答案:1

console.log(++a); //答案:3

null和undefined

undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。

undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果 。 这种原始状态会在以下 4 种场景中出现:

  1. 声明了一个变量,但没有赋值
  2. 访问对象上不存在的属性
  3. 函数定义了形参,但没有传递实参
  4. 使用 void 对表达式求值

因此,undefined 一般都来自于某个表达式最原始的状态值,不是人为操作的结果。当然,你也可以手动给一个变量赋值 undefined,但这样做没有意义,因为一个变量不赋值就是 undefined 。

null 的字面意思是:空值 。这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象

null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。

JSON、dom、bom

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。

JSON字符串转换为JSON对象 var obj = JSON.parse(str);

JSON对象转换为JSON字符串 var last=JSON.stringify(obj)

DOM:Document Object Model,文档对象模型。

是一组用来描述js代码怎样与html文档进行交互和访问的web标准,它定义了一系列对象、方法和属性,用于访问、操作和创建文档中的内容、结构、样式和行为。

BOM- browser object model(浏览器对象模型)。

为什么需要bom,因为我们需要用js来操作浏览器。

针对浏览器的操作有哪些呢?

1.控制浏览器窗口的大小、移动;

2.控制浏览器的访问地址

3.管理浏览器访问的历史页面

4.查看浏览器的版本信息,等等

window对象

是ECMAScript语言中的全局对象

Js访问浏览器的一个接口。(是一个对象,对象提供了一些属性和方法,便于进行相关操作)

js单线程,同步异步编程,事件循环 event loop,宏任务和微任务

js单线程

js是 浏览器脚本语言 ,叫 JavaScript 网景公司为了蹭java的热度, 最初的用途是为来实现用户与浏览器的交互。JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。

注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。

JS语言的执行环境是"单线程", 也就是我们写的所有js代码都是在一个线程(主线程)上执行。

  1. 就是指一次只能完成一件任务。
  2. 如果有多个任务,就必须排队,前一个任务完成,再执行后面一个任务,以此类推

同步异步编程

js执行任务的2种模式:同步任务异步任务

所有的同步任务都在主线程中排队执行,只有前一个任务执行完毕才会去执行下一个任务。异步任务则放在任务队列中排队,分为宏任务队列和微任务队列。

  1. 同步:让任务一件一件的去执行,两个任务不能同时执行。前面等后面执行完了才执行,需要排队等待。执行顺序和排列顺序是一致的。
  2. 异步:可以让多个任务同时执行。 实现非阻塞,通过事件循环实现异步。

虽然JS是单线程的但是浏览器的内核是多线程的,不同的异步操作由不同的浏览器内核模块调度执行。

同步可以保证顺序一致,但是容易导致阻塞;异步可以解决阻塞问题,但是会改变顺序性,根据不同的需要去写你的代码。

事件循环

程序中设置两个线程:一个负责程序本身的运行,称为"主线程";另一个负责主线程与其他进程(主要是各种I/O操作)的通信,被称为"Event Loop线程"(可以译为"消息线程")。

所有任务可以分成两种,一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

所有的同步任务都在主线程中排队执行,只有前一个任务执行完毕才会去执行下一个任务。异步任务则放在任务队列中排队,分为宏任务队列和微任务队列。

JavaScript 是单线程的,所有的任务都需要排队,任务又分为同步任务和异步任务。
所有的同步任务都在主线程中排队执行,只有前一个任务执行完毕才会去执行下一个任务。异步任务则放在任务队列中排队,分为宏任务队列和微任务队列。

只有主线程执行栈为空时,才会读取微任务队列中的任务到执行栈中执行,只有当微任务队列为空时,才会读取宏任务队列中的任务到执行栈中执行。

事件循环完整解释:

  • 首先,整体的脚步script(作为第一个宏任务)开始执行的时候,会把所有代码分为同步任务、异步任务两部分
  • 同步任务会直接进入主线程依次执行
  • 异步任务会再分为宏任务和微任务
  • 宏任务进入到Event T able中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue(事件队列)中
  • 微任务也会进入到另一个Event Table中,并在里面注册回调函数,每当指定的事件完成时,Event Table会将这个函数移到Event Queue中
  • 主线程内的任务执行完毕,主线程为空时,会检查微任务的Event Queue,如果有任务,就全部执行,如果没有就执行下一个宏任务
  • 上述过程会不断重复,这就是Event Loop,比较完整的事件循环

掘金呆呆:

  • 一开始整个脚本作为一个宏任务执行
  • 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
  • 当前宏任务执行完出队,检查微任务列表,有则依次执行,直到全部执行完
  • 执行浏览器UI线程的渲染工作
  • 检查是否有Web Worker任务,有则执行
  • 执行完本轮的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

setTimeout的运行机制:执行该语句时,是立即把当前定时器代码推入事件队列,当定时器在事件列表中满足设置的时间值时将传入的函数加入任务队列,之后的执行就交给任务队列负责。但是如果此时任务队列不为空,则需等待,所以执行定时器内代码的时间可能会大于设置的时间

事件循环

浏览器中, js引擎线程会循环从 任务队列 中读取事件并且执行, 这种运行机制称作 Event Loop (事件循环).

每个浏览器环境,至多有一个event loop。 一个event loop可以有1个或多个task queue(任务队列)

先执行同步的代码,然后js会跑去消息队列中执行异步的代码,异步完成后,再轮到回调函数,然后是去下个事件循环中执行setTimeout

它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。

从规范上来讲,setTimeout有一个4ms的最短时间,也就是说不管你设定多少,反正最少都要间隔4ms才运行里面的回调。而Promise的异步没有这个问题。Promise所在的那个异步队列优先级要高一些 Promise是异步的,是指他的then()和catch()方法,Promise本身还是同步的 Promise的任务会在当前事件循环末尾中执行,而setTimeout中的任务是在下一次事件循环执行

宏任务和微任务

1、宏任务:当前调用栈中执行的任务称为宏任务。( script全部代码 主代码快,定时器等等, setTimeout )。
2、微任务: 当前(此次事件循环中)宏任务执行完,在下一个宏任务开始之前需要执行的任务为微任务。(可以理解为回调事件,promise.then、await 等等)。

宏任务与微任务执行顺序:

  • 执行栈在执行完同步任务后,查看执行栈是否为空,如果执行栈为空,就会去检查微任务队列是否为空,如果为空的话,就执行宏任务,否则就一次性执行完所有微任务。
  • 每次单个宏任务执行完毕后,检查微任务队列是否为空,如果不为空的话,会按照先入先出的规则全部执行完微任务后,设置微任务队列为null,然后再执行宏任务,如此循环。

总结:同步—>微任务—>宏任务

  • settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行
  • promise本身是同步的立即执行函数(同步)
  • promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
  • async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把await表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行

setTimeout的运行机制:执行该语句时,是立即把当前定时器代码推入事件队列,当定时器在事件列表中满足设置的时间值时将传入的函数加入任务队列,之后的执行就交给任务队列负责。但是如果此时任务队列不为空,则需等待,所以执行定时器内代码的时间可能会大于设置的时间

定时器的执行顺序或机制?

**因为js是单线程的,浏览器遇到setTimeout或者setInterval会先执行完当前的代码块,在此之前会把定时器推入浏览器的待执行事件队列里面,等到浏览器执行完当前代码之后会看一下事件队列里面有没有任务,有的话才执行定时器的代码。**所以即使把定时器的时间设置为0还是会先执行当前的一些代码。

setTimeout、Promise、Async/Await 的区别

https://gongchenghuigch.github.io/2019/09/14/awat/

async 是“异步”的简写,而 await 可以认为是 async wait 的简写。所以应该很好理解 async 用于申明一个 function 是异步的,而 await 用于等待一个异步方法执行完成。

*深拷贝和浅拷贝

明确一点深拷贝和浅拷贝是针对对象属性为对象的,因为基本数据类型在进行赋值操作时(也就是拷贝)是直接将值赋给了新的变量,也就是该变量是原变量的一个副本,这个时候你修改两者中的任何一个的值都不会影响另一个,而对于对象或者引用数据来说在进行浅拷贝时,只是将对象的引用复制了一份,也就内存地址,即两个不同的变量指向了同一个内存地址,那么在改变任一个变量的值都是该变这个内存地址的所存储的值,所以两个变量的值都会改变。

基本类型–名值存储在栈内存中,你b=a复制时,栈内存会新开辟一个内存。

引用数据类型–名存在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值,进行拷贝时,其实复制的是a的引用地址,而并非堆里面的值。

浅拷贝:copy后的结果如果修改了,会影响之前的数据

深拷贝:copy后的数据与原数据具没有任何关系

  1. JSON.parse(JSON.stringify(copyObj))
    「优点」 使用简单
    「缺点」
  • 如果对象里的函数,正则,date无法被拷贝下来
  • 无法拷贝copyObj对象原型链上的属性和方法
  • 当数据的层次很深,会栈溢出

JSON.stringify()将js对象转化为json字符串

JSON.parse(怕死)将json字符串转化为对象

递归用于深拷贝,或者树状列表的打平。

完整深copy

<script>
    function deepClone(target){
        // 对特殊情况的处理
        if(target == null) return null;
        if(target instanceof Date) return new Date(target);
        if(target instanceof RegExp) return new RegExp(target);
        // ....

        // 递归的出口
        if(typeof target !== "object") return target;

        let cloneTarget = new target.constructor
        for(let key in target){
            if(target.hasOwnProperty(key)){
                // target[key]  可能还是引用数据类型
                cloneTarget[key] = deepClone(target[key])
            }
        }
        return cloneTarget;
    }
    let reg = new RegExp('abc');
    let newObj = deepClone(reg)
    console.log(newObj)
</script>
// 手写深拷贝函数
function deepClone(obj){
  if(obj == null){
    return null
  }
  if(obj instanceof RegExp){
    return new RegExp(obj)
  }
  if(obj instanceof Date){
    return new Date(obj)
  }
  var objClone = Array.isArray(obj) ? [] : {}
  for(let key in obj){
    if(obj.hasOwnProperty(key)){
    //如果还是对象,就递归
      if(obj[key] && typeof obj[key] === "object"){
        objClone[key] = deepClone(obj[key])
      }else{
        objClone[key] = obj[key]
      }
    }
  }
  return objClone
}

// 测试
var dog = {
  name: {
    chineseName:"狗"
  }
}
var newDog = deepClone(dog)
newDog.name.chineseName = "新狗"
console.log("newDog",newDog)//{ name: { chineseName:"新狗"}}
console.log("dog",dog)//{ name: { chineseName:"狗"}}

防抖节流

频繁地触发一些事件,造成浏览器的性能问题。
解决:防抖 节流 目的:限制事件的频繁触发。

防抖:在规定的时间内,只让最后一次生效,前面的不生效。**适合多次事件一次响应的情况。**触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

节流:,函数触发一次后,在规定时间,不会触发第二次,高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

// 手写防抖函数
function debounce(fn, wait){
  var timer = null;
  return function() {
    // 有定时器了,在规定时间内再触发就要清除前面的定时,重新计时
    if(timer !== null){
      clearTimeout(timer)
    }
    // 重新计时
    timer = setTimeout(fn, (wait));
  }
}

// 测试
function handle(){
  console.log(Math.random())
}
// 窗口大小改变,触发防抖,执行handle
window.addEventListener("resize",debounce(handle,1000))
// 手写节流函数
function throttle(fn, wait,...args){
  var pre = Date.now();
  return function() {
    // 函数可能会有入参
    var context = this
    var now = Date.now()
    if(now - pre >= wait){
      // 将执行函数的this指向当前作用域
      fn.apply(context,args)
      pre = Date.now()
    }
  }
}

// 测试
var name = "夏"
function handle(val){
  console.log(val+this.name)
}
// 滚动鼠标,触发防抖,执行handle
window.addEventListener("scroll", throttle(handle,1000,"龙"))

手写call、apply、bind 方法

callapply 都是为了解决改变 this 的指向。作用都是相同的,只是传参的方式不同。

除了第一个参数外,call 可以接收一个参数列表,apply 只接受一个参数数组。

// call,改变this指向、函数立即执行
Function.prototype.myCall = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments].slice(1);
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// apply,改变this指向、函数立即执行
//apply和call的区别:
//仅仅是传参的区别,作用一样。apply有参数时,以数组的形式进行传递。
Function.prototype.myApply = function () {
  context = context || window;
  let fn = Symbol();
  context[fn] = this;
  let args = [...arguments][1] || [];
  let ret = context[fn](...args);
  delete context[fn];
  return ret;
}

// bind,改变this指向,bind前面的函数不执行,返回一个绑定this之后的函数
Function.prototype.myBind = function (context) {
  context = context || window;
  let fn = this;
  let args = [...arguments].slice(1);
  return function () {
    let exeArgs = [...arguments]
    fn.apply(context, [...args, ...exeArgs])
  }
}

bind返回什么

bind() 方法会返回一个新函数, 又叫绑定函数, 当调用这个绑定函数时, 绑定函数会以创建它时传入 bind() 方法的第一个参数作为当前的上下文, 即this, 传入 bind() 方法的第二个及之后的参数加上绑定函数运行时自身的参数按照顺序作为原函数的参数来调用原函数.

ajax,axios

Ajax:Asynchronous javascript and xml,

异步的javascript和xml。

AJAX 是一种用于创建快速动态网页的技术,用于在Web页面中实现异步数据交互,实现页面局部刷新

Ajax优势:

  1. 可以实现异步通信效果,页面局部刷新,带来更好的用户体验

  2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用

  3. Ajax引擎在客户端运行,承担了一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载。

    JSON是一种轻量级的数据交换格式,看着像对象,本质是字符串

    优点:轻量级、易于人的阅读和编写,便于js解析,支持复合数据类型

Ajax的工作原理相当于在用户和服务器之间加了—个中间层(AJAX引擎),使用户操作与服务器响应异步化。 Ajax的原理简单来说通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。

AJAX最大的特点是什么。

Ajax可以实现动态不刷新(局部刷新
就是能在不更新整个页面的前提下维护数据。这使得Web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息。

中断ajax请求一种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()

AJAX都有哪些优点和缺点?

1、无刷新更新数据;最大的一点是页面无刷新,用户的体验非常好。
2、异步与服务器通信;使用异步方式与服务器通信,具有更加迅速的响应能力。
3、前端和后端负载平衡;可以把以前一些服务器负担的工作转嫁到客户端,利用客户端闲置的能力来处理,减轻服务器和带宽的负担,节约空间和宽带租用成本。并且减轻服务器的负担,ajax的原则是“按需取数据”,可以最大程度的减少冗余请求,和响应对服务器造成的负担。
4、基于标准化的并被广泛支持的技术,不需要下载插件或者小程序。

ajax的缺点
1、ajax不支持浏览器back按钮。
2、安全问题 AJAX暴露了与服务器交互的细节。
3、对搜索引擎的支持比较弱
4、破坏了程序的异常机制。
5、不容易调试。

1.ajax请求和原理
var xhr = new XMLHTTPRequest();
// 请求 method 和 URI
xhr.open('GET', url);
// 请求内容
xhr.send();
// 响应状态
xhr.status
// xhr 对象的事件响应
xhr.onreadystatechange = function() {}
xhr.readyState
// 响应内容
xhr.responseText

Axios,则是针对ajax进行的一个高度封装的库,vue中使用,非常小,功能比较完善。

JSON是一种轻量级的数据交换格式,看着像对象,本质是字符串

优点:轻量级、易于人的阅读和编写,便于js解析,支持复合数据类型

循环

遍历数组

forEach 循环 forEach 循环无法中途跳出,break 命令或 return 命令都不能奏效

forEach循环,循环数组中每一个元素并采取操作, 没有返回值, 可以不用知道数组长度

forEach 循环在所有元素调用完毕之前是不能停止的,它没有 break 语句,如果你必须要停止,可以尝试 try catch 语句,就是在要强制退出的时候,抛出一个 error 给 catch 捕捉到,然后在 catch 里面 return,这样就能中止循环了,如果你经常用这个方法,最好自定义一个这样的 forEach 函数在你的库里。

跳出整个循环
try{
    arr.forEach(item => {
        if(判断语句){
            判定结果
            throw Error()
        }
    })
} catch(e){    
    出错时执行的语句
}finally{
    循环结束执行的语句
}

跳出当前循环。
try{
    arr.forEach(item => {
        if(判断语句){
            判定结果
            return
        }
        其他执行语句
    })
} catch(e){    
    出错时执行的语句
}

map()方法

map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
注意:map 和 forEach 方法都是只能用来遍历数组,不能用来遍历普通对象。

filter() 方法

filter 方法是 Array 对象内置方法,它会返回通过过滤的元素,不改变原来的数组。

some() 方法

some() 方法用于检测数组中的元素是否满足指定条件(函数提供),返回 boolean 值,不改变原数组。

every() 方法

every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供),返回 boolean 值,不改变原数组。

reduce()方法

reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。

for循环,数组

const arr = [11,22,33,44,55,66,77,88]
for (let i = 0; i < arr.length; i++) {
            console.log(arr[i])
        }
for循环跳出整个循环
for(let i = 0; i< arr.length; i++){
    if(判断语句) {
        判定结果
        break;
    }
}

for跳出当前循环,进入下一个循环
for(let i = 0; i< arr.length; i++){
    if(判断语句) {
        判定结果
        continue;
    }
    其他执行语句
}

对象

Object.keys

Object.keys() 是 ES5 新增的一个对象方法,该方法返回对象自身属性名组成的数组,它会自动过滤掉原型链上的属性,然后可以通过数组的 forEach() 方法来遍历

`Object.keys(obj).forEach((key) => {`` ``console.log(obj[key]) ``// foo``})`
Object.values()

该方法返回对象自身属性值组成的数组,

for in 它还会得到对象原型链上的属性

for in 循环是最基础的遍历对象的方式,它还会得到对象原型链上的属性

`// 创建一个对象并指定其原型,bar 为原型上的属性``const obj = Object.create({`` ``bar: ``'bar'``})` `// foo 为对象自身的属性``obj.foo = ``'foo'` `for` `(let key ``in` `obj) {`` ``console.log(obj[key]) ``// foo, bar``}`

可以看到对象原型上的属性也被循环出来了

在这种情况下可以使用对象的 hasOwnProperty() 方法过滤掉原型链上的属性

hasOwnProperty执行时对象查找时,永远不会去查找原型。函数方法是返回一个布尔值,指出一个对象是否具有指定名称的属性。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。

`for` `(let key ``in` `obj) {`` ``if` `(obj.hasOwnProperty(key)) {`` ``console.log(obj[key]) ``// foo`` ``}``}`

for…in 循环主要是为了遍历对象而生,不适用于遍历数组

for…of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对

for of 可以遍历哪些对象

for…of…: 它是es6新增的一个遍历方法,但只限于迭代器(iterator), 所以普通的对象用for…of遍历
是会报错的。

可迭代的对象:包括Array, Map, Set, String, TypedArray, arguments对象等等

对象

对象方法

  1. charAt():返回在指定位置的字符
    var str = ‘nihaome wozhendexiangxeuhaoqianduan’
    var str2 = str.charAt(4);//字符串的下标是从1开始的,所以返回o

  2. conca():连接字符串
    console.log(str2.concat(str1));//把str1连接到str2上

  3. indexOf(): 检索字符串,返回的是字符在字符串的下标
    var num1 = str1.indexOf(‘o’); //检索字符串,返回的是字符在字符串的下标
    //字符串的下标是从0开始计数

  4. replace():替换匹配的字符串
    var str3 = str1.replace(str1.match(‘hao’),str2.match(‘lixi’));
    //找到str1的hao,找到str2的lixi,用str2替换掉str1

  5. search():检索与字符串匹配的子串,返回的是地址

  6. slice():提取字符串片段,并在新的字符串中返回被提取的部分

  7. split():把字符分割成数组

  8. toLowerCase():把字符串转换成小写

  9. toUpperCase():把字符串准换成大写

  10. substr():从起始索引号提取字符串中指定书目的字符

  11. subString():提取字符串中两个指定索引号之间的字符

Object.hasOwnProperty( ) 检查属性是否被继承

Object.isPrototypeOf( ) 一个对象是否是另一个对象的原型

indexof

stringObject.indexOf(searchvalue,fromindex)

searchValue:要被查找的字符串值。

fromindex : 可选的整数参数 。 合法取值是 0 到 stringObject.length - 1。 默认0;

如果没有提供确切地提供字符串,[searchValue 会被强制设置为"undefined"] 。

‘undefined’.indexOf()将会返回0,因为’undefined’在位置0处被找到,但是’undefine’.indexOf()将会返回 -1 ,因为字符串’undefined’未被找到。

如果fromIndex的值小于0,或者大于str.length,那么查找分别从0和str.length开始。(译者 注:fromIndex的值小于0,等同于为空情况;fromIndex的值大于或等于str.length,那么结果 会直接返回-1。)

返回值:

查找的字符串searchValue的第一次出现的索引,如果没有找到,则返回-1。

若被查找的字符串searchValue是一个空字符串,则返回fromIndex。如果fromIndex值为空,或者fromIndex值小于被查找的字符串的长度,返回值和以下的fromIndex值一样。

如果fromIndex值大于等于字符串的长度,将会直接返回字符串的长度(str.length)

特点:

  1. 严格区分大小写

  2. 在使用indexOf检索数组时,用‘===’去匹配,意味着会检查数据类型

Object.assign的理解

参考答案:

作用:Object.assign可以实现对象的合并。

语法:Object.assign(target, …sources)

解析

  1. Object.assign会将source里面的可枚举属性复制到target,如果和target的已有属性重名,则会覆盖。
  2. 后续的source会覆盖前面的source的同名属性。
  3. Object.assign复制的是属性值,如果属性值是一个引用类型,那么复制的其实是引用地址,就会存在引用共享的问题。

可枚举和不可枚举属性

https://www.cnblogs.com/moqiutao/p/7389146.html

在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。

*数组

判断数组

let arr = []

  1. instanceof

    arr instanceof Array

  2. proto

    arr.proto === Array.prototype

  3. constructor

    arr.constructor === Array

  4. Object.prototype.toString

    Object.prototype.toString.call(arr) === ‘[object Array]’

  5. Array.isArray

    Array.isArray(arr)

数组方法

https://www.cnblogs.com/sqh17/p/8529401.html

  1. arr.push() 从后面添加元素,返回值为添加完后的数组的长度

  2. arr.pop() 从后面删除元素,只能是一个,返回值是删除的元素

  3. arr.shift() 从前面删除元素,只能删除一个 返回值是删除的元素

  4. arr.unshift() 从前面添加元素, 返回值是添加完后的数组的长度

  5. arr.splice(i,n) 删除从i(索引值)开始之后的那个元素。返回值是删除的元素 。 n个数

  6. arr.concat() 连接两个数组 返回值为连接后的新数组

  7. str.split() 将字符串转化为数组 , (‘,’)分割多个字符串

  8. arr.sort() 将数组进行排序,返回值是排好的数组, 默认是按照最左边的数字进行排序,不是按照数字大小排序的, 按照类似二进制的那种排序。

  9. arr.reverse() 将数组反转,返回值是反转后的数组

  10. arr.slice( start,end ) 切去索引值start到索引值end的数组,不包含end索引的值,返回值是切出来的数组 .

  11. arr.forEach(callback) 遍历数组,无return 即使有return,也不会返回任何值,并且会影响原来的数组 .

  12. arr.map(callback) 映射数组(遍历数组),有return 返回一个新数组 。

  13. arr.filter(callback) 过滤数组,返回一个满足要求的数组 .

  14. arr.every(callback) 依据判断条件,数组的元素是否全满足,若满足则返回ture .

  15. arr.same(callback) 依据判断条件,数组的元素是否有一个满足,若有一个满足则返回ture

  16. arr.reduce(callback, initialValue) 迭代数组的所有项,累加器,数组中的每个值(从左到右)合并,最终计算为一个值.

    计算数组中每个元素出现的次数

    数组去重

    将多维数组转化为一维

  17. arr.reduceRight() 与arr.reduce()功能一样,不同的是,reduceRight()从数组的末尾向前将数组中的数组项做累加。

  18. arr.indexOf() 查找某个元素的索引值,若有重复的,则返回第一个查到的索引值若不存在,则返回 -1

  19. arr.lastIndexOf() 和arr.indexOf()的功能一样,不同的是从后往前查找

  20. Array.from() 将伪数组变成数组,就是只要有length的就可以转成数组。 —es6

  21. Array.of() 将一组值转换成数组,类似于声明数组 —es6

  22. arr.copyWithin() 在当前数组内部,将制定位置的数组复制到其他位置,会覆盖原数组项,返回当前数组

  23. arr.find(callback) 找到并返回第一个符合条件的数组成员

  24. arr.findIndex(callback) 找到并返回第一个符合条件的数组成员的索引值

  25. arr.fill(target, start, end) 使用给定的值,填充一个数组,ps:填充完后会改变原数组

  26. arr.includes() 判断数中是否包含给定的值

  27. arr.keys() 遍历数组的键名

  28. arr.values() 遍历数组键值

  29. arr.entries() 遍历数组的键名和键值

map 和 forEach 的区别

相同点:

  1. 都是循环遍历数组中的每一项
  2. 每次执行匿名函数都支持三个参数,参数分别为item(当前每一项),index(索引值),arr(原数组)
  3. 匿名函数中的this都是指向window
  4. 只能遍历数组

不同点:

  1. map()会分配内存空间存储新数组并返回,forEach()不会返回数据。
  2. forEach()允许callback更改原始数组的元素。map()返回新的数组。

数组去重

es6方法数组去重,第二种方法
function dedupe(array) {
  return Array.from(new Set(array));       //Array.from()能把set结构转换为数组
}

es6方法数组去重
arr=[...new Set(arr)];		//上面的简化

1、for循环嵌套,利用splice去重:

function unique (origin) {
  let arr = [].concat(origin);
  for (let i = 0; i < arr.length; i++) {
    for (let j = i + 1; j < arr.length; j++) {
      if (arr[i] == arr[j]) {
        arr.splice(j, 1);
        j--;
      }
    }
  }
  return arr;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];

2、新建数组,利用includes去重

新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
function unique (arr) {
  let res = []
  for (let i = 0; i < arr.length; i++) {
    if (!res.includes(arr[i])) {
      res.push(arr[i])
    }
  }
  return res;
}
var arr = [1,1,2,5,6,3,5,5,6,8,9,8];
// 速度最快, 占空间最多(空间换时间)
function unique2(array){
    var n = {}, r = [], type;
    for (var i = 0; i < array.length; i++) {
        type = typeof array[i];
        if (!n[array[i]]) {
            n[array[i]] = [type];
            r.push(array[i]);
        } else if (n[array[i]].indexOf(type) < 0) {
            n[array[i]].push(type);
            r.push(array[i]);
        }
    }
    return r;
}


// 最简单数组去重法
function unique1(array){
    var n = []; //一个新的临时数组
    for(var i = 0; i < array.length; i++){ //遍历当前数组
        if (n.indexOf(array[i]) == -1)
            n.push(array[i]);
    }
    return n;
}


//数组下标判断法
function unique3(array){
    var n = [array[0]]; //结果数组
    for(var i = 1; i < array.length; i++) { //从第二项开始遍历
        if (array.indexOf(array[i]) == i) 
            n.push(array[i]);
    }
    return n;
}

数组和伪数组的区别

伪数组(类数组):无法直接调用数组方法或期望length属性有什么特殊的行为,但仍可以对真正数组遍历方法来遍历它们。典型的是函数的argument参数。

  • 数组是一个特殊对象,与常规对象的区别:
    • 当由新元素添加到列表中时,自动更新length属性
    • 设置length属性,可以截断数组
    • 从Array.protoype中继承了方法
    • 属性为’Array’
  • 类数组是一个拥有length属性,并且他属性为非负整数的普通对象,类数组不能直接调用数组方法。

类数组转换为数组

  • 转换方法
    • 使用Array.from()
    • 使用Array.prototype.slice.call()
    • 使用Array.prototype.forEach()进行属性遍历并组成新的数组

*get和post

  • get传参通过地址栏的URL传递,可以看到传递的参数,请求的数据在URL后通过?连接,通过&进行参数分割;post传递不可见,参数存放在HTTP的包体内。
  • get请求可以被缓存,post不可以被缓存
  • get请求的记录会留在历史记录中,post请求不会留在历史记录
  • get后退不会有影响,post后退会重新进行提交
  • get请求只URL编码,post支持多种编码方式

http协议未规定两者传参限制,get传参限制来源那是浏览器或web服务器,不同的限制长度不同。

get和post在缓存方面的区别:

  • get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
  • post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

GET使用URL或Cookie传参,而POST将数据放在BODY中,这个是因为HTTP协议用法的约定。并非它们的本身区别。

GET方式提交的数据有长度限制,则POST的数据则可以非常大,这个是因为它们使用的操作系统和浏览器设置的不同引起的区别。也不是GET和POST本身的区别。

POST比GET安全,因为数据在地址栏上不可见,这个说法没毛病,但依然不是GET和POST本身的区别。

GET和POST最大的区别主要是GET请求是幂等性的,POST请求不是。(幂等性:对同一URL的多个请求应该返回同样的结果。)因为get请求是幂等的,在网络不好的隧道中会尝试重试。如果用get请求增数据,会有重复操作的风险,而这种重复操作可能会导致副作用

创建实例、函数、继承

1.字面量

let obj={'name':'张三'}

2.Object构造函数创建

let Obj=new Object()
Obj.name='张三'

3.使用工厂模式创建对象

function createPerson(name){
 var o = new Object();
 o.name = name;
 };
 return o; 
}
var person1 = createPerson('张三');

4.使用构造函数创建对象

function Person(name){
 this.name = name;
}
var person1 = new Person('张三');

函数:函数声明、函数表达式、函数对象

实现继承:构造继承、原型继承、实例继承

ES5的继承和ES6的继承有什么区别?

ES5的继承时通过prototype或构造函数机制来实现。ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this))。

ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super()方法),然后再用子类的构造函数修改this

export和export default的区别?

使用上的不同

export default  xxx
import xxx from './'
默认只能导出一个,导出的是整个文件

export xxx
import {xxx} from './'
可以导出多个,导出的文件中的某个函数

JS的四种设计模式

工厂模式:简单的工厂模式可以理解为解决多个相似的问题;

单例模式:只能被实例化(构造函数给实例添加属性与方法)一次

沙箱模式:将一些函数放到自执行函数里面,但要用闭包暴露接口,用变量接收暴露的接口,再调用里面的值,否则无法使用里面的值

发布者订阅模式:就例如如我们关注了某一个公众号,然后他对应的有新的消息就会给你推送,

列举出集中创建实例的方法

1.字面量

let obj={'name':'张三'}

2.Object构造函数创建

let Obj=new Object()
Obj.name='张三'

3.使用工厂模式创建对象

function createPerson(name){
 var o = new Object();
 o.name = name;
 };
 return o; 
}
var person1 = createPerson('张三');

4.使用构造函数创建对象

function Person(name){
 this.name = name;
}
var person1 = new Person('张三');

try/catch/finally

try/catch/finally 语句用于处理代码中可能出现的错误信息。

try语句允许我们定义在执行时进行错误测试的代码块。

catch 语句允许我们定义当 try 代码块发生错误时,所执行的代码块。

finally 语句在 try 和 catch 之后无论有无异常都会执行。

注意: catch 和 finally 语句都是可选的,但你在使用 try 语句时必须至少使用一个。

try {
  *tryCode - 尝试执行代码块
*}
catch(*err*) {
  *catchCode - 捕获错误的代码块
*}
finally {
  *finallyCode - 无论 try / catch 结果如何都会执行的代码块
*}

docoment,window,html,body

层级关系

window > document > html > body
复制代码
  • windowBOM的核心对象,它一方面用来获取或设置浏览器的属性和行为,另一方面作为一个全局对象。浏览器打开的窗口
  • document对象是一个跟文档相关的对象,拥有一些操作文档内容的功能。但是地位没有window高。
  • html元素对象和document元素对象是属于html文档的DOM对象,可以认为就是html源代码中那些标签所化成的对象。他们跟div、select什么对象没有根本区别。

(我是这样记的,整个浏览器中最大的肯定就是窗口window了,那么进来的我不管你是啥,就算你是document也得给我盘着)

*事件冒泡,事件捕获,阻止默认事件

在 冒泡 中,内部元素的事件会先被触发,然后再触发外部元素,顺序:内部==>外部

​ event.stopPropagation( )

在 捕获 中,外部元素的事件会先被触发,然后再触发内外部元素,顺序:外部==>内部

阻止默认事件e.preventDefault()

事件委托

事件监听

通用的事件侦听器函数

// event(事件)工具集,来源:https://github.com/markyun

markyun.Event = {
    // 页面加载完成后
    readyEvent : function(fn) {
        if (fn==null) {
            fn=document;
        }
        var oldonload = window.onload;
        if (typeof window.onload != 'function') {
            window.onload = fn;
        } else {
            window.onload = function() {
                oldonload();
                fn();
            };
        }
    },
    // 视能力分别使用dom0||dom2||IE方式 来绑定事件
    // 参数: 操作的元素,事件名称 ,事件处理程序
    addEvent : function(element, type, handler) {
        if (element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent('on' + type, function() {
                handler.call(element);
            });
        } else {
            element['on' + type] = handler;
        }
    },
    // 移除事件
    removeEvent : function(element, type, handler) {
        if (element.removeEnentListener) {
            element.removeEnentListener(type, handler, false);
        } else if (element.datachEvent) {
            element.detachEvent('on' + type, handler);
        } else {
            element['on' + type] = null;
        }
    }, 
    // 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
    stopPropagation : function(ev) {
        if (ev.stopPropagation) {
            ev.stopPropagation();
        } else {
            ev.cancelBubble = true;
        }
    },
    // 取消事件的默认行为
    preventDefault : function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    // 获取事件目标
    getTarget : function(event) {
        return event.target || event.srcElement;
    },
    // 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
    getEvent : function(e) {
        var ev = e || window.event;
        if (!ev) {
            var c = this.getEvent.caller;
            while (c) {
                ev = c.arguments[0];
                if (ev && Event == ev.constructor) {
                    break;
                }
                c = c.caller;
            }
        }
        return ev;
    }
}; 

historyh和hash路由

js原生路由跳转historyh

arguments相关的问题

参考答案:

arguments

在js中,我们在调用有参数的函数时,当往这个调用的有参函数传参时,js会把所传的参数全部存到一个叫arguments的对象里面。它是一个类数组数据

由来

Javascrip中每个函数都会有一个Arguments对象实例arguments,引用着函数的实参。它是寄生在js函数当中的,不能显式创建,arguments对象只有函数开始时才可用

作用

有了arguments这个对象之后,我们可以不用给函数预先设定形参了,可以动态地通过arguments为函数加入参数

ts、TS

es,js,ts三者有什么关系

js是一门语言,就好比人类语言有英语、俄语、日语,同意计算机也分为Java、PHP、Python,所以假定js就是你所熟悉的中文。

es是ECMAscript,是一种规范。 规定了js要实现的功能 。

ts是js的超集,具体是实现了使用面向对象的方式编写js代码,有具体的类型,泛型,和类的概念。 ts的本质其实是用js的闭包做了一层封装。

ts简介:

TypeScript是JavaScript的加强版,它给JavaScript添加了可选的静态类型和基于类的面向对象编程,它拓展了JavaScript的语法。

而且TypeScript不存在跟浏览器不兼容的问题,因为在编译时,它产生的都是JavaScript代码。

TypeScript 和 JavaScript 的区别是什么?

Typescript 是 JavaScript 的超集,可以被编译成 JavaScript 代码。 用 JavaScript 编写的合法代码,在 TypeScript 中依然有效。Typescript 是纯面向对象的编程语言,包含类和接口的概念。 程序员可以用它来编写面向对象的服务端或客户端程序,并将它们编译成 JavaScript 代码。

interfaces  接口
classes  类
enumerated types 枚举类型
generics 泛型
modules 模块

TS 是一种面向对象编程语言,而 JS 是一种脚本语言(尽管 JS 是基于对象的)。
TS 支持可选参数, JS 则不支持该特性。
TS 支持静态类型,JS 不支持。
TS 支持接口,JS 不支持接口。

TypeScript 和 JavaScript优点

TS 在开发时就能给出编译错误, 而 JS 错误则需要在运行时才能暴露。
作为强类型语言,你可以明确知道数据的类型。代码可读性极强,几乎每个人都能理解。

由于 TS 的先天优势,TS 越来越受欢迎。但是TS 最终不可能取代 JS,因为 JS 是 TS 的核心。

选择 TypeScript 还是 JavaScript 要由开发者自己去做决定。如果你喜欢类型安全的语言,那么推荐你选择 TS。 如果你已经用 JS 好久了,你可以选择走出舒适区学习 TS,也可以选择坚持自己的强项,继续使用 JS。

ts和js、静态类型、动态

  1. 是一种解释性脚本语言(代码不进行预编译)。

  2. 主要用来向HTML(标准通用标记语言下的一个应用)页面添加交互行为。

  3. 可以直接嵌入HTML页面,但写成单独的js文件有利于结构和行为的分离。

跨平台特性,在绝大多数浏览器的支持下,可以在多种平台下运行(如Windows、Linux、Mac、Android、iOS等)。

js是动态类型语言

静态类型语言 & 动态类型语言

静态类型语言:类型检查发生在编译阶段,因此除非修复错误,否则会一直编译失败

动态类型语言:只有在程序运行了一次的时候错误才会被发现,也就是在运行时,因此即使代码中包含了会 在运行时阻止脚本正常运行的错误类型,这段代码也可以通过编译

js静态类型检查的方法

Flow是Facebook开发和发布的一个开源的静态类型检查库,它允许你逐渐地向JavaScript代码中添加类型。

TypeScript静态类型是一个会编译为JavaScript的超集(尽管它看起来几乎像一种新的静态类型语言)

使用静态类型的优势

  • 可以尽早发现bug和错误
  • 减少了复杂的错误处理
  • 将数据和行为分离
  • 减少单元测试的数量
  • 提供了领域建模(domain modeling)工具
  • 帮助我们消除了一整类bug
  • 重构时更有信心

使用静态类型的劣势

  • 代码冗长
  • 需要花时间去掌握类型

什么是泛型?

  • 进去是指定类型,
  • 泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。
  • 可以把泛型理解为代表类型的参数
// 有关联的地方都改成 <T>
function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

// 使用的时候再指定类型
let result = createArray<string>(3, 'x');

// 也可以不指定类型,TS 会自动类型推导
let result2 = createArray(3, 'x');
console.log(result);

never 和 void 的区别?

  • void 表示没有任何类型(可以被赋值为 null 和 undefined)。
  • never 表示一个不包含值的类型,即表示永远不存在的值。
  • 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。

webpacke

https://juejin.cn/post/7108639298424930335

现代 JavaScript 应用程序的 静态模块打包工具

2.png

  1. webpack是一个打包工具,一个资源中转站。它接收一些文件,经过内部处理后,输出对应的静态资源
  2. 它从入口文件出发,找到入口依赖的模块,再递归寻找依赖的依赖,直到所有模块被加载进来
  3. 它把开发者使用方便但浏览器不能直接识别的资源(如less,scss,ES Next等),处理成浏览器可直接识别的资源(css,js等)

打个比方,webpack就像一位厨师,浏览器就像顾客。有一天顾客进饭店点了一盘辣椒炒肉,后厨如果端半斤生猪肉和辣椒给顾客,这单生意肯定要黄。厨师需要把生的食材处理成菜才可上桌。那问题就来了,这盘菜要怎么做?

首先明确要炒的是辣椒炒肉而不是辣子鸡丁,将辣椒炒肉作为入口。找到主要的食材,猪肉,辣椒。——确定入口文件,寻找入口依赖的模块

找到后厨师想了想“吃肉不吃蒜,等于没吃蒜”。于是他拿了一头大蒜,紧跟着又拿了点大葱。——寻找依赖的依赖

接着就是炒制工作了,按顺序放入食材,加入调味料,出锅装盘,这一盘菜就能上桌了。——内部处理后,打包成浏览器可直接识别的资源

entry入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

Output-输出:可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。

  • path: 文件输出的路径,必须是个绝对路径
  • filename: 文件输出的名称。可以在名称前指定一个输出路径,让文件输出在这个路径下。同时也可以使用占位符[name]指定不同的文件名称

loader是一个翻译,把webpack不能直接处理的资源,翻译成能直接处理的

加载css文件——css-loader,style-loader

处理图片使用file-loaderurl-loader

babel-loader:ES6+ 语法编写的代码转义为向下兼容的js代码,使低版本浏览器能正常运行程序。

浏览器

http状态码

1xx:(临时响应)表示临时响应并需要请求者继续执行操作的状态代码。

  • 100 (继续) 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。
  • 101 (切换协议) 请求者已要求服务器切换协议,服务器已确认并准备切换。

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

  • 200:(成功)正确的请求返回正确的结果。 通常,这表示服务器提供了请求的网页。
  • 201 (已创建) 请求成功并且服务器创建了新的资源。
  • 202 (已接受) 服务器已接受请求,但尚未处理。
  • 203——返回信息不确定或不完整
  • 204——请求收到,但返回信息为空
  • 205——服务器完成了请求,用户代理必须复位当前已经浏览过的文件
  • 206——服务器已经完成了部分用户的GET请求

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

  • 300:请求成功,但结果有多种选择。
  • 301:请求成功,但是资源被永久转移。
  • 303:使用 GET 来访问新的地址来获取资源。
  • 304: (未修改) 自从上次请求后,请求的资源并没有被修改过,建议使用功能缓存

4xx(请求错误)

  • 400 (错误请求) 客户端请求有语法错误,不能被服务器所理解
  • 401 (未授权) 请求要求身份验证。请求的时候没有带上 Token 等。
  • 403 (禁止) 禁止访问,服务器收到请求,但是拒绝提供服务。
  • 404 (未找到) 服务器找不到请求的网页,请求资源不存在。

5xx(服务器错误)

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

*http和https的区别?

http传输的数据都是未加密的,也就是明文的,网景公司设置了SSL协议来对http协议传输的数据进行加密处理,简单来说https协议是由http和ssl协议构建的可进行加密传输和身份认证的网络协议,比http协议的安全性更高。 主要的区别如下:

  • Https协议需要ca证书,费用较高。
  • http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
  • 使用不同的链接方式,端口也不同,一般而言,http协议的端口为80,https的端口为443
  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTPS并不是一个新的协议, 它在HTTPTCP的传输中建立了一个安全层,利用对称加密非对称加密结合数字证书认证的方式,让传输过程的安全性大大提高。

web 性能优化,减少页面加载时间

性能优化

资源优化:预加载、懒加载、资源压缩合并(图片、字体、代码)、利用缓存、减少请求、代码优化(减少操作dom、减少全局变量)

  1. 初始化页面加loding图,这其实没有优化加载时间,但是提升了用户体验
  2. 减少http请求,合理的利用缓存
  3. 图片预加载、预渲染
  4. 组件,路由懒加载;拆分页面,分担加载压力。
  5. 优化webpack打包机制。
  6. 资源压缩合并(图片转base64、字体、代码)
  7. 代码优化(减少操作dom、减少全局变量,多个变量声明合并)

1、压缩css、js文件

2、合并js、css文件,减少http请求

3、外部js、css文件放在最底下

4、减少dom操作,尽可能用变量替代不必要的dom操作

首屏优化

  1. 首屏内容最好做到静态缓存
  2. .图片懒加载
  3. .服务端渲染,首屏渲染速度更快(重点),无需等待js文件下载执行的过程
  4. .图片尺寸大小控制

客户端渲染:

页面的渲染工作都是由浏览器来完成的,服务器只是负责提供数据。
客户端渲染能尽早的把页面展示给用户,用户体验好
不容易被爬虫爬取数据,同时也无法被搜索引擎搜索到

  1. 局部刷新。无需每次都进行完整页面请求
  2. 富交互。使用 JS 实现各种酷炫效果
  3. 节约服务器成本。

服务器渲染:

页面渲染的工作都是由服务端来完成的,数据也是由服务端提供的,浏览器只负责展示页面内容
容易被爬虫爬取数据,同时能被搜索引擎搜索到,能在搜索引擎中向用户展示数据

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

  2. 服务端渲染不需要先下载一堆 js 和 css 后才能看到页面(首屏性能)

  3. 服务端渲染不用关心浏览器兼容性问题(随意浏览器发展,这个优点逐渐消失)

  4. 对于电量不给力的手机或平板,减少在客户端的电量消耗很重要

*存储Cookie、sessionStorage、localStorage的区别

我们经常需要对业务中的一些数据进行存储,通常可以分为 短暂性存储持久性储存

  • 短暂性的时候,我们只需要将数据存在内存中,只在运行时可用
  • 持久性存储,可以分为 浏览器端 与 服务器端

浏览器:

  • cookie: 通常用于存储用户身份,登录状态等 http 中自动携带, 体积上限为 4K, 可自行设置过期时间。 cookie的作用:是为了实现客户端与服务器之间状态的保持

    cookie优点: 1.可以解决HTTP无状态的问题,与服务器进行交互 缺点: 1.数量和长度限制,每个域名最多20条,每个cookie长度不能超过4kb 2.安全性问题。容易被人拦截 3.浪费带宽,每次请求新页面,cookie都会被发送过去

  • localStorage : 长久储存/, 体积限制为 5M

  • sessionStorage:窗口关闭删除, 体积限制为 4~5M

服务器:

  • 分布式缓存 redis
  • 数据库
  1. cookie并不适合存储,而且存在非常多的缺陷。
  2. Web Storage包括localStoragesessionStorage, 默认不会参与和服务器的通信。
  3. IndexedDB为运行在浏览器上的非关系型数据库,为大型数据的存储提供了接口。

三者异同:

cookie是服务器返回的,指定了expire time(有效期)的是持久cookie,没有指定的是会话cookie

共同点:都是保存在浏览器端,并且是同源的

  • Cookie:cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。而sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
  • sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持
  • localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie只在设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭。

1.cookie数据存放在客户的浏览器上,session数据放在服务器上。

2.session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE。

*TCP三次握手四次挥手

https://baijiahao.baidu.com/s?id=1693383134922615393&wfr=spider&for=pc

三次握手之所以是三次是保证client客户端和server服务器均让对方知道自己的接收和发送能力没问题而保证的最小次数。

  1. 第一次client客户端 =>服务器 server,只能server判断出client具备发送能力。 在发送连接请求后等待匹配的连接请求
  2. 第二次 server服务器 =>客户端 client, client就可以判断出server具备发送和接受能力。此时client还需让server知道自己接收能力没问题于是就有了第三次。 在收到和发送一个连接请求后等待对连接请求的确认
  3. 第三次 client客户端 =>服务器 server 双方均保证了自己的接收和发送能力没有问题。代表一个打开的连接,数据可以传送给用户

其中,为了保证后续的握手是为了应答上一个握手,每次握手都会带一个标识 seq,后续的ACK都会对这个seq进行加一来进行确认。

  1. 第一次客户端 =>服务器 , 发送请求,停止再发送数据,主动关闭 TCP 连接
  2. 第二次服务器 =>客户端 ,客户端收到了,发送数据,表示知道了。 此时的 TCP 处于半关闭状态,客户端到服务端的连接释放
  3. 第三次服务器 =>客户端 , 服务器也想断开连接,发送请求数据,等到客户端确认。
  4. 第四次客户端 =>服务器,确认请求

由于 TCP 的半关闭(half-close)特性,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。

通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手

举个例子:A 和 B 打电话,通话即将结束后,A 说 “我没啥要说的了”,B 回答 “我知道了”,于是 A 向 B 的连接释放了。但是 B 可能还会有要说的话,于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”,A 回答“知道了”,于是 B 向 A 的连接释放了,这样整个通话就结束了。

*一个url到页面展示的完整流程

网络篇

浏览器端发起 HTTP 请求流程

  1. 构建请求;

    浏览器会构建请求行,get请求。浏览器会开启一个线程来处理这个请求。浏览器根据请求的URL交给DNS域名解析

  2. 查找强缓存;

    先检查强缓存,如果命中直接使用,否则进入下一步。

  3. DNS解析;

    因为我们输入的是域名,而数据包是通过ip地址传给对方的,所以需要使用DNS(域名解析系统),将域名解析成IP地址,浏览器具有DNS数据缓存功能,解析过的域名下次直接走缓存。

  4. 建立 TCP 连接;

    建立 TCP连接经历了三个阶段:

    一是三次握手确认连接,二是数据包校验保证数据到达接收方,三是通过四次挥手断开连接。

    1. 通过三次握手(即总共发送3个数据包确认已经建立连接)建立客户端和服务器之间的连接。
    2. 进行数据传输。这里有一个重要的机制,就是接收方接收到数据包后必须要向发送方确认, 如果发送方没有接到这个确认的消息,就判定为数据包丢失,并重新发送该数据包。当然,发送的过程中还有一个优化策略,就是把大的数据包拆成一个个小包,依次传输到接收方,接收方按照这个小包的顺序把它们组装成完整数据包。
    3. 断开连接的阶段。数据传输完成,现在要断开连接了,通过四次挥手来断开连接。

    Chrome 在同一个域名下要求同时最多只能有 6 个 TCP 连接。TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

  5. 发送 HTTP 请求,服务器端处理 HTTP 请求流程;

    三次握手,TCP连接建立完毕,浏览器可以和服务器开始通信,即开始发送 HTTP 请求。请求包括:请求行(请求方法、URL、协议版本)、请求头(请求的附加信息,成对:分割)和请求体(只有在POST方法下存在,表单提交。)。

  6. 返回请求,网络响应

    网络响应具有三个部分:响应行(协议版本,状态码,状态码描述)、响应头(响应报文的附加信息,由 名/值 对组成)和响应体(回车符、换行符和响应返回数据)

  7. (解析渲染后,四次挥手断开连接)断开连接

解析渲染篇

  1. 根据 HTML 解析 DOM 树

    浏览器解析html源码,会根据html源码生成DOM树。在DOM树中,每一个HTML标签都会对应一个节点,每一个文本也都会有一个对应的文本节点。DOM树的根节点是documentElement,对应的是html标签。

  2. 根据 CSS解析 CSS规则树

    浏览器解析css代码,会根据优先级计算出最终的样式。浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。也会生成相对应的CSS规则树。 对CSS代码中非法的语法它会直接忽略掉。

  3. 结合 DOM 树和 CSS 规则树,生成渲染树。

    渲染树和DOM树有点像,但是是有区别的。DOM树完全和html标签一一对应,但是渲染树会精简css,忽略掉不需要渲染的元素,比如head、display:none的元素等。而且渲染树的每一个节点都会存储对应的css属性。

  4. 根据渲染树计算每一个节点的信息(布局)

    重绘:某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的重绘。

    回流:某个元素的尺寸发生了变化,则需重新计算渲染树,重新渲染

  5. 根据计算好的信息绘制页面

    绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上

页面渲染html的过程?

1、浏览器解析html源码,会根据html源码生成DOM树。在DOM树中,每一个HTML标签都会对应一个节点,每一个文本也都会有一个对应的文本节点。DOM树的根节点是documentElement,对应的是html标签。

2、浏览器解析css代码,会根据优先级计算出最终的样式。浏览器默认设置 < 用户设置 < 外链样式 < 内联样式 < html中的style。也会生成相对应的CSSOM树。 对CSS代码中非法的语法它会直接忽略掉。

3、html生成的树和css生成的树会结合成渲染树。渲染树和DOM树有点像,但是是有区别的。

DOM树完全和html标签一一对应,但是渲染树会忽略掉不需要渲染的元素,比如head、display:none的元素等。而且渲染树的每一个节点都会存储对应的css属性。

4、一旦渲染树创建好了,浏览器会根据渲染树把页面渲染到屏幕上。

以上四个步骤并不是一次性顺序完成的。如果DOM树或者CSSOM树被修改,以上过程会被重复执行。实际上,CSS和JavaScript往往会多次修改DOM或者CSSOM。

浏览器的渲染过程

  • 根据html构建 DOM
  • 根据css构建 CSSOM
  • 将 DOM 和 CSSOM 合并成 Render Tree渲染树
  • 根据 Render Tree 进行布局
  • 最后绘制

重绘和回流

当元素的样式发生变化时,浏览器需要触发更新,重新绘制元素。这个过程中,有两种类型的操作,即重绘与回流。

重绘(repaint):
当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要UI层面的重新像素绘制,因此 损耗较少

回流(reflow):
当元素的尺寸、结构或触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。

会触发回流的操作:

  • 添加或者删除可见的DOM元素;
  • 元素位置改变;
  • 元素尺寸改变——边距、填充、边框、宽度和高度
  • 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;
  • 页面渲染初始化;
  • 浏览器窗口尺寸改变——resize事件发生时;

回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。

如何减少回流: css

  • 避免使用table布局
  • 将动画效果应用到position属性为absolute或fixed的元素上

javascript

  • 避免频繁操作样式,可汇总后统一 一次修改
  • 尽量使用class进行样式修改
  • 减少dom的增删次数,可使用 字符串 或者 documentFragment 一次性插入
  • 极限优化时,修改样式可将其display: none后修改
  • 避免多次触发上面提到的那些会触发回流的方法,可以的话尽量用 变量存住

强缓存和协商缓存

浏览器判断缓存是否过期,未过期时,直接使用强缓存。当缓存已经过期时,使用协商缓存

强缓存:不需要发送HTTP请求

浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires。

协商缓存:

缓存过期,进入协商缓存,即发送 HTTP 请求,服务器通过请求头中的If-Modified-Since或者If-None-Match字段检查资源是否更新

  • 若资源更新,返回资源和200状态码

  • 否则,返回304,告诉浏览器直接从缓存获取资源

CSRF、XSS 区别

  • 原理不同,CSRF是利用网站A本身的漏洞,去请求网站A的api;XSS是向目标网站注入JS代码,然后执行JS里的代码。
  • CSRF需要用户先登录目标网站获取cookie,而XSS不需要登录
  • CSRF的目标是用户,XSS的目标是服务器
  • XSS是利用合法用户获取其信息,而CSRF是伪造成合法用户发起请求

CSRF(Cross-site request forgery)跨站请求伪造。

指的是黑客诱导用户点击链接,打开黑客的网站,然后黑客利用用户目前的登录状态发起跨站请求。

XSS攻击对比,CSRF 攻击并不需要将恶意代码注入用户当前页面的html文档中,而是跳转到新的页面,利用服务器的验证漏洞用户之前的登录状态来模拟用户进行操作。

攻击
用户登录 A 网站,并保留了登录凭证(cookie)
用户访问了 B 网站
B 网站向 A 网站发送一个请求,浏览器会默认携带 cookie
A 网站对请求进行验证,确认是用户的凭证
A 网站执行 B 网站的请求

防御

  • **验证来源站点:**通过 http 请求头中的origin或referer字段,确定请求的来源域名
  • 利用Cookie的SameSite属性,设置该属性,让浏览器完全禁止第三方请求携带Cookie
  • **CSRF Token:**第三方站点无法拿到这个token

XSS(Cross Site Scripting)跨站脚本攻击。

XSS 攻击是指浏览器中执行恶意脚本(无论是跨域还是同域),从而拿到用户的信息并进行操作。

XSS 攻击的实现有三种方式——存储型反射型文档型。三种XSS攻击的原理,共同点: 都是让恶意脚本直接能在浏览器中执行。

防范:

无论是在前端和服务端,都要对用户的输入进行转码或者过滤

利用CSP,即浏览器中的内容安全策略,它的核心思想就是服务器决定浏览器加载哪些资源。

设置 Cookie 的 HttpOnly 属性,防止XSS 攻击脚本来窃取Cookie

  • 一个信念: 不要相信用户的输入,对输入内容转码或者过滤,让其不可执行。
  • 两个利用: 利用 CSP,利用 Cookie 的 HttpOnly 属性。

简述HTTPS中间人攻击

https协议由 http + ssl 协议构成,具体的链接过程可参考SSL或TLS握手的概述

中间人攻击过程如下:

  1. 服务器向客户端发送公钥。
  2. 攻击者截获公钥,保留在自己手上。
  3. 然后攻击者自己生成一个【伪造的】公钥,发给客户端。
  4. 客户端收到伪造的公钥后,生成加密hash值发给服务器。
  5. 攻击者获得加密hash值,用自己的私钥解密获得真秘钥。
  6. 同时生成假的加密hash值,发给服务器。
  7. 服务器用私钥解密获得假秘钥。
  8. 服务器用加秘钥加密传输信息

防范方法:

  1. 服务端在发送浏览器的公钥中加入CA证书,浏览器可以验证CA证书的有效性

同源跨域,cors、代理、正向代理

资源跨域共享,CORS是一种新标准,支持同源通信,也支持跨域通信。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

**同源策略:**协议、域名、端口

浏览器具有同源策略,所谓同源策略就是,两个页面的协议,域名和端口都相同,则两个页面具有相同的源。

**跨域:**在浏览器端;使用js;在不同的域下进行数据传输或通信;

出现跨域的根本原因:浏览器的同源策略不允许非同源的 URL 之间进行资源的交互。

解决跨域:corsjsonp代理

解决跨域

  • 1.服务器设置响应头:access-control-allow-origin,放开跨域问题;
  • 2.利用 JSONP 处理,因为 JSONP 是通过创建 script 标签的方式发送请求,所以只能用于 get 方法;
  • 3.通过服务器的反向代理方式。

cors解决跨域是在服务端开发权限,代码是固定的。

jsonp原理:主要就是利用 script 标签的src属性没有跨域的限制,通过指向一个需要访问的地址,由服务端返回一个预先定义好的 Javascript 函数的调用,并且将服务器数据以该函数参数的形式传递过来,此方法需要前后端配合完成。本质不是ajax,本质是script标签发出的请求

Jsonp的实现是通过动态的添加script标签从而发送http请求来实现的。

缺点:只能进行GET请求

优点:兼容性好,在一些古老的浏览器中都可以运行

*服务端代理*

服务端代理 在浏览器客户端不能跨域访问,而服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就没有跨越问题。简单地说,就是浏览器不能跨域,后台服务器可以跨域。(一种是通过http-proxy-middleware插件设置后端代理;另一种是通过使用http模块发出请求)

正向代理: 用浏览器访问时,被残忍的block,于是你可以在国外搭建一台代理服务器,让代理帮我去请求google.com,代理把请求返回的相应结构再返回给我。

反向代理:反向代理服务器会帮我们把请求转发到真实的服务器那里去。Nginx就是性能非常好的反向代理服务器,用来做负载均衡。 正向代理的对象是客户端,反向代理的对象是服务端

内存泄漏

  1. 意外的全局变量;
  2. 闭包;
  3. 未被清空的定时器;
  4. 未被销毁的事件监听;
  5. DOM 引用;

网页验证码是干嘛的,是为了解决什么安全问题。

区分用户是计算机还是人的公共全自动程序。可以防止恶意破解密码、刷票、论坛灌水;

有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试。

vue

(1) Vue基本核心语法

(2) Vue-router

(3) Vuex

(4) MVVM实现原理

Vue页面渲染过程

  1. new Vue,执行初始化
  2. 挂载$mount方法,通过自定义Render方法、template、el等生成Render函数
  3. 通过Watcher监听数据的变化
  4. 当数据发生变化时,Render函数执行生成VNode对象
  5. 通过patch方法,对比新旧VNode对象,通过DOM Diff算法,添加、修改、删除真正的DOM元素

vue的渲染机制

vue响应式,双向绑定

Vue响应式:数据发生变化后,会重新对页面渲染,这就是Vue响应式

过程

  • 侦测数据的变化。如何检测数据变化。
  • 收集视图依赖了哪些数据
  • 数据变化时,自动“通知”需要更新的视图部分,并进行更新

它们对应专业俗语分别是:

  • 数据劫持 / 数据代理
  • 依赖收集
  • 发布订阅模式

1、使用Vue2Object.defineProperty和ES6的Vue3Proxy,进行数据劫持或数据代理。

2、收集依赖是为了知道那个地方依赖了我的数据,以及当数据更新时派发更新。

  1. Object.defineProperty只能劫持对象的属性,不能监听数组。也不能对 es6 新产生的 Map,Set 这些数据结构做出监听。也不能监听新增和删除操作等等。
  2. Proxy可以直接监听整个对象而非属性,可以监听数组的变化,具有多达13中拦截方法。

响应式的好处

对某些数据的修改就能自动更新视图,让开发者不用再去操作DOM,有更多的时间去思考业务逻辑。

双向绑定

使用 v-model 指令在表单元素上创建双向数据绑定, v-model 本质上不过是语法糖,采用数据劫持结合发布者-订阅者模式的方式。通过 Object.defineProperty() 来劫持各个属性的 settergetter,在数据变动时发布消息给订阅者,触发相应监听回调。

vue本身不是双向绑定的,它是单向数据流

vue实现的双向绑定是通过:数据劫持+发布订阅实现的。

vue3是用的proxy

Vue 组件 data 为什么必须是函数

new vue()实例中data可以是对象,但在vue组件中,组件是可以复用的。

因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了

ref获取dom

ref=“domName” 用法:this.$refs.domName

ref的作用

  1. 获取dom元素this.$refs.box

  2. 获取子组件中的datathis.$refs.box.msg

  3. 调用子组件中的方法this.$refs.box.open()

对 keep-alive 的了解

keep-aliveVue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

$nextTick的使用?

https://blog.csdn.net/zhouzuoluo/article/details/84752280

比如:在一个方法里面,修改了某个data的值,然后获取这个dom元素的值, 获取的是旧的值,获取不了新的值。

在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。

获取更新后的DOM的方法 。所以放在Vue.nextTick()回调函数中的执行的应该是会对DOM进行操作的 js代码;

**理解:**nextTick(),是将回调函数延迟在下一次dom更新数据后调用,简单的理解是:**当数据更新了,在dom中渲染后,自动执行该函数,**你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。

 methods:{
    testClick:function(){
      let that=this;
      that.testMsg="修改后的值";
      console.log(that.$refs.aa.innerText);   //that.$refs.aa获取指定DOM,输出:原始值
      that.$nextTick(function(){
        console.log(that.$refs.aa.innerText);  //输出:修改后的值
      });
    }

nextTick是在下次DOM更新循环之后执行延迟回调,在修改数据之后使用nextTick,则可以在回调中获取更新后的DOM。 场景:需要在视图更新之后,基于新的视图进行操作。

Vue的优点及缺点,单页面

首先Vue最核心的两个特点,响应式组件化。数据驱动 组件系统

响应式:这也就是vue.js最大的优点,通过MVVM思想实现数据的双向绑定,通过虚拟DOM让我们可以用数据来操作DOM,而不必去操作真实的DOM,提升了性能。且让开发者有更多的时间去思考业务逻辑。

组件化:把一个单页应用中的各个模块拆分到一个个组件当中,或者把一些公共的部分抽离出来做成一个可复用的组件。所以组件化带来的好处就是,提高了开发效率,方便重复使用,使项目的可维护性更强。

虚拟DOM,当然,这个不是vue中独有的。

缺点:基于对象配置文件的写法,也就是options写法,开发时不利于对一个属性的查找。另外一些缺点,在小项目中感觉不太出什么,vuex的魔法字符串,对ts的支持。兼容性上存在一些问题。

  • 不利于seo。
  • 导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)。
  • 初次加载时耗时多。

单页面

优点:

  • 良好的交互体验
  • 良好的前后端工作分离模式
  • 减轻服务器压力

缺点:

  • SEO难度较高
  • 前进、后退管理
  • 初次加载耗时多

v-show与v-if区别

共同点:都能控制元素的显示和隐藏;

不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。

  • v-show是css切换,v-if是完整的销毁和重新创建
  • 使用 频繁切换时用v-show,运行时较少改变时用v-if
  • v-if=‘false’ v-if是条件渲染,当false的时候不会渲染

v-if 与 v-for

答:当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。

深度监听、computed和watch

1、vue中的watch对象是默认不监听内部的值得变化的
2、配置了 deep:true是可以监听内部的值变化
vue自身默认是可以监听对象内部的值变化,但是watch是不可以的
使用watc的时候根据数据的具体结构,来决定是否采用深度监听

答:computed:本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图

computed用来监控自己定义的变量, 该变量不在data里面声明, 直接在computed里面定义, computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化,

当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:没有缓存性,更多的是观察的作用

watch主要用于监控vue实例的变化,它监控的变量当然必须在data里面声明才可以,它可以监控一个变量,也可以是一个对象

当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据

Vue中hash模式和history模式的区别

  • 最明显的是在显示上,hash模式的URL中会夹杂着#号,而history没有。

  • hash路由不需要对404页面配置,history需要对404进行配置。

  • Vue底层对它们的实现方式不同。hash模式是依靠onhashchange事件(监听location.hash的改变),而history模式是主要是依靠的HTML5 history中新增的两个方法,pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改。

  • 当真正需要通过URL向后端发送HTTP请求的时候,比如常见的用户手动输入URL后回车,或者是刷新(重启)浏览器,这时候history模式需要后端的支持。因为history模式下,前端的URL必须和实际向后端发送请求的URL一致,例如有一个URL是带有路径path的(例如www.lindaidai.wang/blogs/id),如果后端没有对这个路径做处理的话,就会返回404错误。所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出的一个404页面。

MVC和MVVM

  • MVC是后端的分层开发概念;
  • MVVM是前端视图层的概念,主要关注于 视图层分离MVVM把前端的视图层分为了三部分:Model,View,VM ViewModel

概括起来,MVVM是由MVC发展而来,通过在Model之上而在View之下增加一个非视觉的组件将来自Model的数据映射到View中。

MVC

view=》controller=》model》view

特点:

  1. View 传送指令到 Controller
  2. Controller 完成业务逻辑后,要求 Model 改变状态
  3. Model 将新的数据发送到 View,用户得到反馈

所有通信都是单向的

MVVM

View《=》ViewModel《=》Model

特点:

  1. 各部分之间的通信,都是双向的
  2. 采用双向绑定:View 的变动,自动反映在 ViewModel,反之亦然

Model层代表数据模型,可以在Model中定义数据修改和操作业务逻辑; view 代表UI组件。负责将数据模型转换成UI展现出来 ViewModel 是一个同步View和Model的对象

用户操作view层,view数据变化会同步到Model,Model数据变化会立即反应到view中。viewModel通过双向数据绑定把view层和Model层连接了起来

vue的diff算法,添加key值

  • 给列表结构的每个单元添加唯一的key属性,方便比较。
  • React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)
  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。。

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

*更准确*:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

*更快速*:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

评价vue

框架能够让我们跑的更快,但只有了解原生的JS才能让我们走的更远。

vue专注于MVVM中的viewModel层,通过双向数据绑定,把view层和Model层连接了起来。核心是用数据来驱动DOM。这种把directive和component混在一起的设计有一个非常大的问题,它导致了很多开发者滥用Directive(指令),出现了到处都是指令的情况。

优点: 1.不需要setState,直接修改数据就能刷新页面,而且不需要react的shouldComponentUpdate就能实现最高效的渲染路径。 2.渐进式的开发模式,模版方式->组件方式->路由整合->数据流整合->服务器渲染。上手的曲线更加平滑简单,而且不像react一上来就是组件全家桶 3.v-model给开发后台管理系统带来极大的便利,反观用react开发后台就是个杯具 4.html,css与js比react更优雅地结合在一个文件上。

缺点:指令太多,自带模板扩展不方便; 组件的属性传递没有react的直观和明显

vue的核心:数据绑定 和 视图组件。

Vue的数据驱动:数据改变驱动了视图的自动更新,传统的做法你得手动改变DOM来改变视图,vuejs只需要改变数据,就会自动改变视图,一个字:爽。再也不用你去操心DOM的更新了,这就是MVVM思想的实现。

视图组件化:把整一个网页的拆分成一个个区块,每个区块我们可以看作成一个组件。网页由多个组件拼接或者嵌套组成

优化vue

1.不要在模板里面写过多表达式

2.循环调用子组件时添加key

3.频繁切换的使用v-show,不频繁切换的使用v-if

4.尽量少用float,可以用flex

5.按需加载,可以用require或者import()按需加载需要的组件

6.路由懒加载

vue小知识点

*VueJS* *开发常见问题集锦*

https://segmentfault.com/a/1190000010230843

组件通信,父子传递

vue父组件向子组件传递数据?
答:通过props
子组件像父组件传递事件?
答:$emit方法

说出几种vue当中的指令和它的用法?
答:v-model双向数据绑定;
v-for循环;
v-if v-show 显示与隐藏;
v-on事件;v-once: 只绑定一次。

为什么使用key?
答:需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。
作用主要是为了高效的更新虚拟DOM。

v-modal的使用。
答:v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
v-bind绑定一个value属性;
v-on指令给当前元素绑定input事件。

请说出vue.cli项目中src目录每个文件夹和文件的用法?
答:assets文件夹是放静态资源;components是放组件;router是定义路由相关的配置; app.vue是一个应用主组件;main.js是入口文件。

分别简述computed和watch的使用场景
答:computed:本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图
    当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:没有缓存性,更多的是观察的作用
    当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据

渐进式框架的理解
答:主张最少;可以根据不同的需求选择不同的层级;
19.Vue中双向数据绑定是如何实现的?
答:vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。

v-if和v-for的优先级
答:当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。

vue的两个核心点
答:数据驱动、组件系统
数据驱动:ViewModel,保证数据和视图的一致性。
组件系统:应用类UI可以看作全部是由组件树构成的。

对比 jQuery ,Vue 有什么不同

jQuery 专注视图层,通过操作 DOM 去实现页面的一些逻辑渲染; Vue 专注于数据层,通过数据的双向绑定,最终表现在 DOM 层面,减少了 DOM 操作Vue 使用了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发。

Vue2中注册在router-link上事件无效解决方法
答: 使用@click.native。原因:router-link会阻止click事件,.native指直接监听一个原生事件。

vue初始化页面闪动问题
答:使用vue开发时,在vue初始化之前,由于div是不归vue管的,所以我们写的代码在还没有解析的情况下会容易出现花屏现象,看到类似于{{message}}的字样,虽然一般情况下这个时间很短暂,但是我们还是有必要让解决这个问题的。
首先:在css里加上[v-cloak] {
display: none;
}。
如果没有彻底解决问题,则在根元素加上style=“display: none;” :style=“{display: ‘block’}”

nextTick知道吗,实现原理是什么?

nextTick批量异步更新策略,一句话概括在下次DOM更新循环结束之后执行延迟回调

为了解决一个数据data在短时间内重复更新,数据重复更新导致视图重复更新,效率太低。所以在vue源码中定义了。它接收回调函数,多次调用接收多个回调函数存入任务队列中,当前栈任务执行完才会执行。

vue-router路由小知识点

1.mvvm 框架是什么?
答:vue是实现了双向数据绑定的mvvm框架,当视图改变更新模型层,当模型层改变更新视图层。在vue中,使用了双向绑定技术,就是View的变化能实时让Model发生变化,而Model的变化也能实时更新到View。

2.vue-router 是什么?它有哪些组件

答:vue用来写路由一个插件 官方的路由管理器 。router-link、router-view

3.active-class 是哪个组件的属性?

答:vue-router模块的router-link组件。children数组来定义子路由。 用来做选中样式的切换,当router-link标签被点击时将会应用这个样式

4.怎么定义 vue-router 的动态路由? 怎么获取传过来的值?

答:在router目录下的index.js文件中,对path属性加上/:id。 使用router对象的params.id。

动态路由的创建,主要是使用path属性过程中,使用动态路径参数,以冒号开头,如下:

{
  path: '/details/:id'
  name: 'Details'
  components: Details
}

当匹配到/details下的路由时,参数值会被设置到this.$route.params下,所以通过这个属性可以获取动态参数

console.log(this.$route.params.id)

5.vue-router 有哪几种导航钩子?

答:三种,
第一种:是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。

全局前置守卫

const router = new VueRouter({})
router.beforeEach((to, from, next) = {
  // to do somethings
})

to:Route,代表要进入的目标,它是一个路由对象。

from:Route,代表当前正要离开的路由,也是一个路由对象

next:Function,必须需要调用的方法,具体的执行效果则依赖next方法调用的参数

next():进入管道中的下一个钩子,如果全部的钩子执行完了,则导航的状态就是comfirmed(确认的)
next(false):终端当前的导航。如浏览器URL改变,那么URL会充值到from路由对应的地址。
next('/')||next({path:'/'}):跳转到一个不同的地址。当前导航终端,执行新的导航。

  • next 方法必须调用,否则钩子函数无法resolved

第二种:组件内的钩子
第三种:单独路由独享组件

6.$route 和 $router 的区别

答: r o u t e r 是 V u e R o u t e r 的实例,在 s c r i p t 标签中想要导航到不同的 U R L , 使用 router是VueRouter的实例,在script标签中想要导航到不同的URL,使用 routerVueRouter的实例,在script标签中想要导航到不同的URL,使用router.push方法。返回上一个历史history用$router.to(-1)。router为VueRouter的实例,是一个全局路由对象,包含了路由跳转的方法、钩子函数等。

$route为当前router跳转对象。里面可以获取当前路由的name,path,query,parmas等。

route 是路由信息对象||跳转的路由对象,每一个路由都会有一个route对象,是一个局部对象,包含path,params,hash,query,fullPath,matched,name等路由信息参数。

7.vue-router的两种模式

答:hash模式:即地址栏 URL 中的 # 符号;
history模式:window.history对象打印出来可以看到里边提供的方法和记录长度。利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)。

8.vue-router实现路由懒加载( 动态加载路由 )

答:三种方式
第一种:vue异步组件技术 ==== 异步加载,vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 .但是,这种情况下一个组件生成一个js文件。
第二种:路由懒加载(使用import)。
第三种:webpack提供的require.ensure(),vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

把不同路由对应的组件分割成不同的代码块,然后当路由被访问时才加载对应的组件即为路由的懒加载,可以加快项目的加载速度,提高效率

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      name: 'Home',
      component:() = import('../views/home')
		}
  ]
})

VUE项目路由权限

前后端分离的权限管理基本就以下两种方式:

\1. 后端生成当前用户相应的路由后由前端(用 Vue Router 提供的API)addRoutes 动态加载路由。

\2. 前端写好所有的路由,后端返回当前用户的角色,然后根据事先约定好的每个角色拥有哪些路由对角色的路由进行分配。

l 第一种,完全由后端控制路由,但这也意味着如果前端需要修改或者增减路由都需要经过后端大大的同意;

l 第二种,相对于第一种,前端相对会自由一些,但是如果角色权限发生了改变就需要前后端一起修改,而且如果某些(技术型)用户在前端修改了自己的角色权限就可以通过路由看到一些本不被允许看到的页面,虽然拿不到数据,但是有些页面还是不希望被不相关的人看到。

vuex小知识点

1.vuex是什么?怎么使用?哪种功能场景使用它?

Vuex 是一个专为 Vue.js 应用程序开发的状态管理插件。它采用集中式存储管理应用的所有组件的状态,而更改状态的唯一方法就是在mutaions里修改state,actions不能直接修改state

场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车

当项目遇到以下两种场景时

  • 多个组件依赖于同一状态时。
  • 来自不同组件的行为需要变更同一状态。

2.vuex有哪几种属性?
答:有五种,分别是 State、 Getter、Mutation 、Action、 Module
state => 基本数据(数据源存放地) 数据的输出
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步! 显式地提交 (commit) mutation
actions => 像一个装饰器,包裹mutations,使之可以异步。 数据的输入
modules => 模块化Vuex。 将store模块化—当store很大的时候,分成模块,方便管理

3.state对象

vuex 中最关键的是store对象,这是vuex的核心。可以说,vuex这个插件其实就是一个store对象,每个vue应用仅且仅有一个store对象

  1. store 中存储的状态是响应式的,当组件从store中读取状态时,如果store中的状态发生了改变,那么相应的组件也会得到更新;
  2. 不能直接改变store中的状态。改变store中的状态的唯一途径是提交(commit)mutations。这样使得我们可以方便地跟踪每一个状态的变化。

4.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
答:如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用。

5.语法糖辅助函数:
mapState-----mapActions----mapMutations----- mapGetters
当一个组件获取多种状态的时候,为了方便可以用语法糖辅助函数

mapState和mapGetter的使用只能在computed计算属性中,
mapMutations和mapActions使用的时候只能在methods中调用否则报错

6.Vuex中状态是对象时,使用时要注意什么?

答:对象是引用类型,复制后改变属性还是会影响原始数据,这样会改变state里面的状态,是不允许,所以先用深度克隆复制对象,再修改。

7.怎么在组件中批量使用Vuex的state状态?

答:使用mapState辅助函数, 利用对象展开运算符将state混入computed对象中

import {mapState} from ‘vuex’ export default{ computed:{ …mapState([‘price’,‘number’]) } }

react

(1) React基本核心语法

(2) React-router

(3) Redux

react优缺点?jsx的优缺点

react优缺点

优点:

  1. 可以通过函数式方法描述视图组件(好处:相同的输入会得到同样的渲染结果,不会有副作用;组件不会被实例化,整体渲染性能得到提升)

  2. 集成虚拟DOM(性能好)

  3. 单向数据流(好处是更容易追踪数据变化排查问题

  4. 一切都是component:代码更加模块化,重用代码更容易,可维护性高

  5. 大量拥抱 es6 新特性

  6. jsx

缺点:

  1. jsx的一个问题是,渲染函数常常包含大量逻辑,最终看着更像是程序片段,而不是视觉呈现。后期如果发生需求更改,维护起来工作量将是巨大的

  2. 大而全,上手有难度

jsx的优缺点

可以把 HTML 和css写到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。

允许使用熟悉的语法来定义HTML元素树 JSX 让小组件更加简单、明了、直观。 更加语义化且易懂的标签 JSX 本质是对JavaScript语法的一个扩展,看起来像是某种模板语言,但其实不是。但正因为形似HTML,描述UI就更直观了,也极大地方便了开发; 在React中babel会将JSX转换为React.createElement函数调用,然后将JSX转换为正确的JSON对象(VDOM 也是一个“树”形的结构) React/JSX乍看之下,觉得非常啰嗦,但使用JavaScript而不是模板语法来开发(模板语法比较有局限性),赋予了开发者许多编程能力。

虚拟dom、diff算法、key值

虚拟dom

虚拟 DOM (VDOM)是真实 DOM 在内存中的表示。UI 的表示形式保存在内存中,并与实际的 DOM 同步。这是一个发生在渲染函数被调用和元素在屏幕上显示之间的步骤,整个过程被称为调和

虚拟dom提高性能

  • 虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
  • 具体实现步骤如下:
    • 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中;
    • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;
    • 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了;
    • 把树形结构按照层级分解,只比较同级元素。

diff算法

  • 给列表结构的每个单元添加唯一的key属性,方便比较。

  • React 只会匹配相同 class 的 component(这里面的class指的是组件的名字)

  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.

  • 选择性子树渲染。开发人员可以重写shouldComponentUpdate提高diff的性能。

React中的render方法,返回一个DOM描述,结果仅仅是轻量级的js对象。Reactjs只在调用setState的时候会更新dom,而且还是先更新虚拟Virtual Dom,然后和实际DOM比较,最后再更新实际DOM。

React.js 厉害的地方并不是说它比 DOM 快(这句话本来就是错的),而是说不管你数据怎么变化,都可以以最小的代价来更新 DOM。用新的数据刷新一个虚拟的 DOM 树,然后新旧 DOM 树进行比较,找出差异,再更新到真正的 DOM 树上。

当我们修改了DOM树上一些节点对应绑定的state,React会立即将它标记为“脏状态”。在事件循环的最后才重新渲染所有的脏节点。在实际的代码中,会对新旧两棵树进行一个深度优先的遍历,这样每个节点都会有一个唯一的标记,每遍历到一个节点就把该节点和新的的树进行对比。如果有差异的话就记录到一个对象里面,最后把差异应用到真正的DOM树上。

算法实现 1 步骤一:用JS对象模拟DOM树 2 步骤二:比较两棵虚拟DOM树的差异 3 步骤三:把差异应用到真正的DOM树上 这就是所谓的 diff 算法

dom diff采用的是增量更新的方式,类似于打补丁。React 需要为数据添加 key 来保证虚拟 DOM diff 算法的效率。key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能。

为什么js对象模拟DOM会比js操作DOM来得快

为了解决频繁操作DOM导致Web应用效率下降的问题,React提出了“虚拟DOM”(virtual DOM)的概念。Virtual DOM是使用JavaScript对象模拟DOM的一种对象结构。DOM树中所有的信息都可以用JavaScript表述出来,

<ul>
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
可以用以下JavaScript对象来表示:
 
{
  tag: 'ul',
  children: [{
    tag: 'li', children: ['Item 1'],
    tag: 'li', children: ['Item 2'],
    tag: 'li', children: ['Item 3']
  }]
}

这样可以避免直接频繁地操作DOM,只需要在js对象模拟的虚拟DOM进行比对,再将更改的部分应用到真实的DOM树上

key值

key 是用来帮助 react 识别哪些内容被更改、添加或者删除。key 需要写在用数组渲染出来的元素内部,并且需要赋予其一个****稳定****的值。稳定在这里很重要,因为如果 key 值发生了变更,react 则会触发 UI 的重渲染。这是一个非常有用的特性。

*key 的唯一性*

在相邻的元素间,key 值必须是唯一的,如果出现了相同的 key,同样会抛出一个 Warning,告诉相邻组件间有重复的 key 值。并且只会渲染第一个重复 key 值中的元素,因为 react 会认为后续拥有相同 key 的都是同一个组件。

*key 值不可读*

虽然我们在组件上定义了 key,但是在其子组件中,我们并没有办法拿到 key 的值,因为 key 仅仅是给 react 内部使用的。如果我们需要使用到 key 值,可以通过其他方式传入,比如将 key 值赋给 id 等。、

  • 虚拟 DOM 的 key 的作用?
    • 简单说: key 是虚拟 DOM 对象的标识, 在更新显示时 key 起着极其重要的作用
    • 详细说: 当列表数组中的数据发生变化生成新的虚拟 DOM 后, React 进行新旧虚拟 DOM 的 diff 比较
      • key 没有变
        • item 数据没变, 直接使用原来的真实 DOM
        • item 数据变了, 对原来的真实 DOM 进行数据更新
      • key 变了
        • 销毁原来的真实 DOM, 根据 item 数据创建新的真实 DOM 显示(即使 item 数据没有变)
  • key 为 index 的问题
    • 添加/删除/排序 => 产生没有必要的真实 DOM 更新 ==> 界面效果没问题, 但效率低
    • 如果 item 界面还有输入框 => 产生错误的真实 DOM 更新 ==> 界面有问题
    • 注意: 如果不存在添加/删除/排序操作, 用 index 没有问题
  • 解决:
    • 使用 item 数据的标识数据作为 key, 比如 id 属性值

调用 setState 发生什么,异步吗

setState(updater, callback)这个方法是用来告诉react组件数据有更新,有可能需要重新渲染。它是****异步*的,react通常会*集齐一批需要更新的组件*,然后一次性更新来保证*渲染的性能;****

在react的生命周期和合成事件中,react仍然处于他的更新机制中,这时更新标识为true。按照上述过程,这时无论调用多少次setState,都会不会执行更新,而是将要更新的state存入_更新队列中,将要更新的组件存入一个数组中;当上一次更新机制执行完毕后会将更新标识设置为false。这时将一次性执行之前累积的setState。由执行机制看,setState本身并不是异步的,而是如果在调用setState时,如果react正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象;可以理解成在生命周期和合成事件中,setState是异步的,但是在原生事件中,setState是同步的;

  • setState 只在 React 合成事件和钩子函数中是“异步”的,在原生 DOM 事件和定时器中都是同步的。
  • 如果需要获取“异步”场景的 setState 的值 --> this.setState(partial, callback) 在 callback 中拿到最新的值
  • 如果要在“异步”场景保证同步更新多次 setState --> this.setState((prevState, props) => {return newState})

传入 setState 函数的第二个参数的作用是什么

1). 可以将一个函数作为setState()的第2个参数传递
   this.setState(
     { username: 'tylermcginnis33' },
     () => console.log('状态更新且界面也更新了')
   )
2). 函数会在界面更新后才调用, 此时可以得到最新的状态, 也可以访问到最新的界面

setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成

props和state区别

组件从概念上看就是一个函数,hooks就是为了让函数组件有生命周期和state,组件可以接受一个参数作为输入值,这个参数就是props, 主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,

一个组件使用到数据主要是,从外部传进来的参数props,和组件内部定义的state。

state不同于props的一点是,state是可以被改变的。不过,不可以直接通过this.state=的方式来修改,而需要通过this.setState()方法来修改state。

  1. state是组件自己管理数据,控制自己的状态,可变;
  2. props是外部传入的数据参数,不可变;
  3. 没有state的叫做无状态组件,有state的叫做有状态组件;
  4. 多用props,少用state。也就是多写无状态组件。

无状态函数组件、高阶组件、组件理解、类组件

无状态组件

无状态组件其实本质上就是一个函数,又叫函数组件。传入props即可,没有state,也没有生命周期方法。组件本身对应的就是render方法。

无状态组件不会创建对象,故比较省内存。没有生命周期方法,故流程比较简单。没有state**,也不会重复渲染。它本质上就是一个函数而已。

对于没有状态变化的组件,React建议我们使用无状态组件。总之,能用无状态组件的地方,就用无状态组件

高阶组件

高阶组件(HOC)是接受一个组件并返回一个新组件的函数。基本上,这是一个模式,是从 React 的组合特性中衍生出来的,称其为纯组件****,因为它们可以接受任何动态提供的子组件,但不会修改或复制输入组件中的任何行为。

  • 概念:高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件
  • 目的: 能让其他组件复用相同的逻辑
  • 当你发现两个组件有重复逻辑时,就使用 HOC 来解决。
  • 用法详见:https://juejin.im/post/5c972f985188252d7f2a3eb0
const EnhancedComponent = higherOrderComponent(WrappedComponent);
HOC 可以用于以下许多用例

代码重用,逻辑和引导抽象
渲染劫持
状态抽象和控制
Props 控制

组件理解

一个组件应该有以下特征:

\1. 可组合(Composeable):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有(own)它创建的子组件,通过这个特性,一个复杂的 2. UI 可以拆分成多个简单的 UI 组件;

\3. 可重用(Reusable):每个组件都是具有独立功能的,它可以被使用在多个 UI 场景;

\4. 可维护(Maintainable):每个小的组件仅仅包含自身的逻辑,更容易被理解和维护;

\5. 可测试(Testable):因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个 UI 进行测试容易的多。

类组件

1. ****类组件****可以使用其他特性,如状态 state 和生命周期钩子。

\2. 当组件只是接收 props 渲染到页面时,就是无状态组件,就属于函数组件,也被称为哑组件或展示组件。

函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

区别函数组件类组件
是否有 this没有
是否有生命周期没有
是否有状态 state没有

受控组件(controlled component)

  • 能在表单项输入框输入过程中进行实时数据收集的表单组件
  • 原理: 绑定onChange事件监听, 当输入改变时, 将最新的值, 更新到对应的state上

受控组件是 React 控制中的组件,并且是表单数据真实的唯一来源。

非受控组件是由 DOM 处理表单数据的地方,而不是在 React 组件中。

尽管非受控组件通常更易于实现,因为只需使用refs即可从 DOM 中获取值,但通常建议优先选择受控制的组件,而不是非受控制的组件。

这样做的主要原因是受控组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式。

在构造函数调用 super 并将props 作为参数传入的作用是啥?

在调用 super() 方法之前,子类构造函数无法使用this引用,ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。

*传递 props*

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    console.log(this.props);  // { name: 'sudheer',age: 30 }
  }
}

*没传递 props*

class MyComponent extends React.Component {
  constructor(props) {
    super();
    console.log(this.props); // undefined
    // 但是 Props 参数仍然可用
    console.log(props); // Prints { name: 'sudheer',age: 30 }
  }
  render() {
    // 构造函数外部不受影响
    console.log(this.props) // { name: 'sudheer',age: 30 }
  }
}

上面示例揭示了一点。props 的行为只有在构造函数中是不同的,在构造函数之外也是一样的。

react 生命周期函数

componentWillMount:在渲染之前执行,用于根组件中的 App 级配置

componentDidMount:在第一次渲染之后执行,可以在这里做AJAX请求,DOM 的操作或状态更新以及设置事件监听器

componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染

shouldComponentUpdate:确定是否更新组件 默认情况下,它返回true 如果确定在 state 或 props 更新后组件不需要在重新渲染,则可以返回false,这是一个提高性能的方法 。 这个方法用来判断是否需要调用render方法

componentWillUpdate:在shouldComponentUpdate返回 true 确定要更新组件之前件之前执行 componentDidUpdate:它主要用于更新DOM以响应props或state更改

componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器

最新的生命周期:

  • 初始化

    • constructor (初始化状态 - ref - 绑定 this 指向)
    • static getDerivedStateFromProps / componentWillMount(旧)
    • render(返回渲染页面需要的虚拟 dom 对象)
    • componentDidMount(发送 ajax 请求、设置定时器等一次性任务)
  • 更新

    • static getDerivedStateFromProps / componentWillReceiveProps(旧)
    • shouldComponentUpdate(性能优化,减少重新渲染次数)
    • componentWillUpdate(旧)
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate(更新时发请求)
  • 卸载

    • componentWillUnmount(收尾工作,如清除定时器、取消 ajax 请求)
  • 即将废弃

    • componentWillMount
    • componentWillUpdate
    • componentWillReceiveProps
  • 处理异常

    • static getDerivedStateFromError(error)
      • 通过更新状态 --> 使下一次渲染可以显降级 UI
    • componentDidCatch()
      • 记录错误信息

react 性能优化?

  • shouldComponentUpdate
    • 通过对比新旧 state 和 props 来决定是否重新渲染
  • PureComponent
    • 实现了新旧 state 和 props 的浅比较
  • 为了防止修改原对象,从而让浅比较比较不出来(确保前后数据一定不一样),可以使用 immuable.js

4. 虚拟 DOM diff 算法

  • 虚拟 DOM diff 算法主要就是对以下三种场景进行优化:

  • tree diff

    • 对树进行分层比较,两棵树只会对同一层次的节点进行比较。(因为 DOM 节点跨层级的移动操作少到可以忽略不计)
    • 如果父节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。
    • 注意:
      • React 官方建议不要进行 DOM 节点跨层级的操作,非常影响 React 性能。
      • 在开发组件时,保持稳定的 DOM 结构会有助于性能的提升。例如,可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
  • component diff

    • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree(tree diff)。
      • 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
    • 如果不是,直接替换整个组件下的所有子节点。
  • element diff

    • 对处于同一层级的节点进行对比。
    • 这时 React 建议:添加唯一 key 进行区分。虽然只是小小的改动,性能上却发生了翻天覆地的变化!
      • 如: A B C D --> B A D C
      • 添加 key 之前: 发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。
      • 添加 key 之后: B、D 不做任何操作,A、C 进行移动操作,即可。
    • 建议:在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。
  • 总结

    • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
    • React 通过分层求异的策略,对 tree diff 进行算法优化;
    • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
    • React 通过设置唯一 key 的策略,对 element diff 进行算法优化;
    • 建议,在开发组件时,保持稳定的 DOM 结构会有助于性能的提升;
    • 建议,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响 React 的渲染性能。

10. 组件间通信有哪些方式

  • props

    • 子 --> 父,父组件传函数数据给子组件,子组件调用修改父组件数据
    • 父 --> 子,父组件传非函数数据给子组件
    • 兄弟(一般不用),数据定义在公共的父组件中。
  • context

    • 适用于祖孙组件
    • const context = React.createContext()
    • context.Provider / context.Consumer
  • pubsub-js

    • 适用于兄弟组件、祖孙组件。
    • pubsub.subscribe(‘MSG’, (msg, data) => {})
    • pubsub.publish(‘MSG’, data)
    • 注意:
      • 先订阅再发布
      • 订阅只能一次,发布可以多次
  • redux

    • 管理多个组件共享的数据
  • web storage ==> localStorage/sessionStorage

    • 保存在浏览器本地,引入使用
    • 也可以跨页面通信

*React 中 refs

ref是React提供的用来操纵React组件实例或者DOM元素的接口。

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。

Refs 提供了一种访问在render方法中创建的 DOM 节点或者 React 元素的方法。在典型的数据流中,props 是父子组件交互的唯一方式,想要修改子组件,需要使用新的pros重新渲染它。凡事有例外,某些情况下咱们需要在典型数据流外,强制修改子代,这个时候可以使用 Refs。

咱们可以在组件添加一个 ref 属性来使用,该属性的值是一个回调函数,接收作为其第一个参数的底层 DOM 元素或组件的挂载实例。

请注意,input 元素有一个ref属性,它的值是一个函数。该函数接收输入的实际 DOM 元素,然后将其放在实例上,这样就可以在 handleSubmit 函数内部访问它。

经常被误解的只有在类组件中才能使用 refs,但是refs也可以通过利用 JS 中的闭包与函数组件一起使用。

React Hooks

hooks实现原理

Hooks优缺点

优点:

  1. 不同组件和生命周期之间的逻辑复用。代码可读性强,useEffect三个生命周期,代码放在一起。

  2. 不需要使用高阶组件进行逻辑抽离

React Hooks为函数组件而生,从而解决了类组件的几大问题:

  • this 指向容易错误。不需要去理解class中的this指向问题。
  • 分割在不同声明周期中的逻辑使得代码难以理解和维护。不同生命周期之间的逻辑复用。代码可读性强,useEffect三个生命周期,代码放在一起。
  • 代码复用成本高(高阶组件容易使代码量剧增)。不需要使用高阶组件进行逻辑抽离

缺点:

hooks的useEffect只包括了componentDidMount,componentDidUpdate和componentWillUnmount这三

个生命周期,对于其他的class类组件的生命周期则是不支持

让函数组件拥有状态数据和生命周期函数更容易复用代码

Hooks是 React 16.8 中的新添加内容。它们允许在不编写类的情况下使用state和其他 React 特性。使用 Hooks,可以从组件中提取有状态逻辑,这样就可以独立地测试和重用它。Hooks 允许咱们在不改变组件层次结构的情况下重用有状态逻辑,这样在许多组件之间或与社区共享 Hooks 变得很容易。

useState

useState Hook** 返回一个 state,以及更新 state 的函数。

useState通过在函数组件里调用它来给组件添加一些内部state。 React 会在重复渲染时保留这个 state。useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数。 可以在任何地方调用set方法修改值。

useEffect

useEffect有两个参数,第一个参数是回调函数,第二个参数是一个数组,这个数组接受当前函数中的state,若第二个参数状态变化时,则执行回调函数;

useEffect只对当前函数中的状态更新有效;

useEffect** 接收一个包含命令式、且可能有副作用代码的函数。

给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。 React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。

useContext

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。

useCallback

父组件将一个方法传递给子组件,若父组件的其他状态发生变化时,子组件也会跟着渲染多次,会造成性能浪费; usecallback是将父组件传给子组件的方法给缓存下来,只有当 usecallback中的第二个参数状态变化时,子组件才重新渲染;

返回一个 memoized 回调函数。

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

用来缓存函数,这个函数如果是由父组件传递给子组件,或者自定义hooks里面的函数【通常自定义hooks里面的函数,不会依赖于引用它的组件里面的数据】,这时候我们可以考虑缓存这个函数,好处:

1,不用每次重新声明新的函数,避免释放内存、分配内存的计算资源浪费
2,子组件不会因为这个函数的变动重新渲染。【和React.memo搭配使用】

useMeno

父组件将一个值传递给子组件,若父组件的其他值发生变化时,子组件也会跟着渲染多次,会造成性能浪费; useMemo是将父组件传递给子组件的值缓存起来,只有当 useMemo中的第二个参数状态变化时,子组件才重新渲染;

返回一个 memoized 值。

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

用来缓存数据,当 组件内部某一个渲染的数据,需要通过计算而来,这个计算是依赖与特定的state、props数据,我们就用useMemo来缓存这个数据,以至于我们在修改她们没有依赖的数据源的情况下,多次调用这个计算函数,浪费计算资源。

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。 一个常见的用例便是命令式地访问子组件:

useImperativeHandle 透传 Ref

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef 一起使用:

通过 useImperativeHandle 用于让父组件获取子组件内的dom

使用hooks进行的性能优化

  1. 减少重新 render 的次数。因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction。
  2. 减少计算的量。主要是减少重复计算,diff算法key

useCallback和useMemo

https://blog.csdn.net/leelxp/article/details/107822103

class组件性能优化的点:

  1. 调用 setState,就会触发组件的重新渲染,无论前后 state 是否相同

  2. 父组件更新,子组件也会自动更新

介于这俩点,我们一般使用 immutable 进行比较,在不相等的时候调用 setState, 在 shouldComponentUpdate 中判断前后的 props 和 state,如果没有变化,则返回 false 来阻止更新。

但是当hooks到来后,没有了shouldComponentUpdate函数。无法判断前后的props是否相同,从而是不是要从新渲染,useEffect函数并没有区分是mount还是update,这样一来,也就意味着,每次组件的更新都会执行其复杂的逻辑,性能的消耗将是非常大的。

所以,在hooks时代,就诞生了useCallback和useMemo这样的性能优化函数,其实,useCallback和useMemo和useEffect的参数一致,但是,useEffect可以处理副作用,而后俩者不可以。useCallback 和 useMemo 都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo 返回缓存的 变量,useCallback 返回缓存的 函数。

  1. 在子组件不需要父组件的值和函数的情况下,只需要使用 memo 函数包裹子组件即可。
  2. 如果有函数传递给子组件,使用 useCallback
  3. 如果有值传递给子组件,使用 useMemo
  4. useEffect、useMemo、useCallback 都是自带闭包的。也就是说,每一次组件的渲染,其都会捕获当前组件函数上下文中的状态(state, props),所以每一次这三种hooks的执行,反映的也都是当前的状态,你无法使用它们来捕获上一次的状态。对于这种情况,我们应该使用 ref 来访问。

hooks监听组件销毁

使用react的Effect hook

是根据此hook组件的参数来进行判断,我们在此组件的父组件上将this.props.history传到这个组件上

import React, { useEffect } from 'react';
function Test({ history }) {
  useEffect(() => {
    queryHandler();
    return componentWillUnmount;
  }, []);

  function componentWillUnmount() {
  	// 用遍量存放要到的那个组件的pathname
    const nextRoutePathName = history.location.pathname;
    // 判断路由是哪个地址,如果不是当前的路由地址说明是销毁了,那就执行事件,如果是那就不执行
    if (!String.prototype.startsWith.call(nextRoutePathName, '/admin/test')) {	
		// 你的操作
		alert('组件销毁!')
	}
  }
  
  return (
	<div>
		<button onClick={路由跳转(自己定义方法)}>路由跳转</button>
	</div>
  )
}
export default YetContent;

Redux、flux

flux 是 react 中的类似于 vuex 的公共状态管理方案,它是 Facebook 官方给出的应用架构,利用数据的单向流动的形式对公共状态进行管理。现已不推荐使用。但是为了能更好的理解 Redux 方案,还是有必要熟悉 flux 的工作流程滴。 Flux 是一种强制单向数据流的架构模式。

redux

  • 作用: 集中管理多个组件共享的状态
  • 特点: 单一数据源、纯函数、只读 state
  • 核心:
    • store
      • 用来集中存储数据的
    • action-creators ===> actions
      • 用来生成 action 对象 {type: xxx, data: xxx}
      • 分为:同步和异步,同步返回值是对象,异步返回值是函数
    • action-types
      • 用来定义 action 对象的 type
    • reducers
      • 根据之前的状态和 action 对象来生成新状态,并更新 store 对象的数据
      • 一种状态数据对应一个 reducer 函数
  • 工作流程
    • 组件调用 action-creators 生成 action 对象
    • 组件调用 store 对象的 dispatch 方法,分发 action 对象
    • 此时会自动触发 reducer 函数调用(遍历所有 reducer 函数直到匹配上)
    • reducer 函数一旦调用就会返回一个新的状态
    • 新状态会交给 store 对象管理,从而更新状态
    • 一旦状态更新,就会触发 store.subscribe 订阅的函数,从而重新渲染组件
    • 组件重新渲染就能获取最新的 store 对象的值了
  • 扩展概念:
    • UI 组件:负责界面展示,没有 redux 内容
    • 容器组件:负责数据操作,仅有 redux 内容,将其传给 UI 组件

redux是一个独立专门用于做状态管理的JS(不是react插件库)。集中式管理react应用中多个组件共享的状态

Redux 是一个 *数据管理中心*,可以把它理解为一个全局的 data store 实例。它通过一定的使用规则和限制,保证着数据的健壮性、可追溯和可预测性。它与 React 无关,可以独立运行于任何 JavaScript 环境中,从而也为同构应用提供了更好的数据同步通道。

数据如何通过 Redux 流动?

  1. 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。
  2. 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State
  3. State一旦有变化,Store就会调用监听函数,来更新View。

Redux遵循的三个原则是什么?

  1. 单一事实来源:整个应用的状态存储在单个 store 中的对象/状态树里。单一状态树可以更容易地跟踪随时间的变化,并调试或检查应用程序。
  2. 状态是只读的:改变状态的唯一方法是去触发一个动作。动作是描述变化的普通 JS 对象。就像 state 是数据的最小表示一样,该操作是对数据更改的最小表示。
  3. 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

你对“单一事实来源”有什么理解?

Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。

Redux 由以下组件组成:

  1. Action – 这是一个用来描述发生了什么事情的对象。
  2. Reducer – 这是一个确定状态将如何变化的地方。
  3. Store – 整个程序的状态/对象树保存在Store中。
  4. View – 只显示 Store 提供的数据。

如何在 Redux 中定义 Action?

React 中的 Action 必须具有 type 属性,该属性指示正在执行的 ACTION 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性。在 Redux 中,action 被名为 Action Creators 的函数所创建。以下是 Action 和Action Creator 的示例:

`function` `addTodo(text) {``    ``return` `{``        ``type: ADD_TODO,  ``         ``text``  ``}``}`

解释 Reducer 的作用

Reducers 是纯函数,它规定应用程序的状态怎样因响应 ACTION 而改变。Reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

Store 在 Redux 中的意义是什么?

Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

核心理念**

o *单一数据源*: 整个应用只有唯一的状态树,也就是所有 state 最终维护在一个根级 Store 中;

o *状态只读*: 为了保证状态的可控性,最好的方式就是监控状态的变化。那这里就两个必要条件:

§ Redux Store 中的数据无法被直接修改;

§ 严格控制修改的执行;

o *纯函数*: 规定只能通过一个纯函数 (Reducer) 来描述修改;

redux是用来对组件的状态数据进行集中式管理的框架
主要有三个核心部分,actionstorereducer
工作流程是 *
组件中调用
storedispatch()分发action

触发reducer执行,返回新的state
view*通过store提供的**getState()**获取最新的数据

1). 何为 redux*
* redux 的基本思想是应用的state保存在一个单一的store对象中。*
* 而改变应用state的唯一方式是在应用中触发actions,然后为这些actions编写reducers来修改state*
* 整个 state 转化是在 reducers 中完成,并且不应该有任何副作用*
**
2). Redux 中,何为 store
* Store 是一个 javascript 对象,它保存了整个应用的 state*。与此同时,Store 也承担以下职责:
* 允许通过 getState() 访问 state*
* 通过 dispatch(action) 改变 state*
* 通过 subscribe(listener) 注册 listeners*
**
3). 何为 action
* action**是一个纯 js**对象,必须有一个 type 属性表明正在执行的 action 的类型。*
* 实质上,action 是将数据从应用程序发送到 store 的有效载荷
**
4). 何为 reducer
* 一个reducer是一个纯函数,该函数以先前的state和一个action作为参数,并返回新的state

**
5). redux-thunk的作用是什么**
* redux-thunk**是一个允许你编写返回一个函数而不是一个 action对象的中间件。
* thunk**可以用来延迟 action 的派发
(dispatch),也就是处理异步* action 的派发(dispatch)*
**
6). 何为纯函数(pure function)**
* 一个纯函数是一个不依赖于且不改变其作用域之外的变量状态的函数,*
* 这也意味着一个纯函数对于同样的参数总是返回同样的结果。

React Router、路由、懒加载

  • Hash 模式
    • window 对象提供了 onhashchange 事件来监听 hash 值的改变,一旦 url 中的 hash 值发生改变,便会触发该事件。
  • History 模式
    • 使用 pushState 方法实现添加功能
    • 使用 replaceState 实现替换功能
    • 监听历史栈信息变化,变化时重新渲染

props.history对象
props.match对象
props.location对象
withRouter**函数*

React触发页面渲染的几种方式

初始化

初次加载组件的时候,会调用一次render函数

更新

  1. state发生改变
  2. props发生改变

强制更新

如果不是前两种,还希望组件发生更新,可以使用
this.forceUpdate()

vue和react

vue和react区别

相同点:

  • 都支持服务端渲染,通过虚拟dom和diff算法提高性能。
  • 数据驱动视图,组件化开发,通过props参数进行父子组件数据的传递。
  • 都有支持native的方案,React的React native,Vue的weex。
  • 本身都主要关注于UI, 如路由/状态管理/ajax请求都是由其它的插件来完成

不同点:

  • React严格上只针对MVC的view层,Vue则是MVVM模式
  • 数据绑定:Vue有实现了双向数据绑定,React数据流动是单向的
  • state对象在react应用中是不可变的,需要使用setState方法更新状态;在Vue中,state对象并不是必须的,数据由data属性在Vue对象中进行管理。
  • virtual DOM 不一样 vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。而对于React而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过shouldComponentUpdate这个生命周期方法来进行控制,
  • 组件写法不一样 React 推荐的做法是 JSX + inline style,也就是把 HTML 和 CSS 全都写进 JavaScript 了,即”all in js” 。Vue 推荐的是使用 webpack + vue-loader 的单文件组件格式,即html,css,js写在同一个文件;

客户端渲染:

页面的渲染工作都是由浏览器来完成的,服务器只是负责提供数据。
客户端渲染能尽早的把页面展示给用户,用户体验好
不容易被爬虫爬取数据,同时也无法被搜索引擎搜索到
服务器渲染:

页面渲染的工作都是由服务端来完成的,数据也是由服务端提供的,浏览器只负责展示页面内容
容易被爬虫爬取数据,同时能被搜索引擎搜索到,能在搜索引擎中向用户展示数据

react和vue的区别

1.监听数据变化的实现原理不同

Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。

React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。

2.数据流的不同

Vue1.0中可以实现两种双向绑定:父子组件之间,props可以双向绑定;组件与DOM之间可以通过v-model双向绑定。Vue2.x中去掉了第一种,也就是父子组件之间不能双向绑定了(但是提供了一个语法糖自动帮你通过事件的方式修改),并且Vue2.x已经不鼓励组件对自己的 props进行任何修改了。

React一直不支持双向绑定,提倡的是单向数据流,称之为onChange/setState()模式。不过由于我们一般都会用Vuex以及Redux等单向数据流的状态管理框架,因此很多时候我们感受不到这一点的区别了。

3.HoC和mixins

Vue组合不同功能的方式是通过mixin,Vue中组件是一个被包装的函数,并不简单的就是我们定义组件的时候传入的对象或者函数。比如我们定义的模板怎么被编译的?比如声明的props怎么接收到的?这些都是vue创建组件实例的时候隐式干的事。由于vue默默帮我们做了这么多事,所以我们自己如果直接把组件的声明包装一下,返回一个HoC,那么这个被包装的组件就无法正常工作了。

React组合不同功能的方式是通过HoC(高阶组件)。React最早也是使用mixins的,不过后来他们觉得这种方式对组件侵入太强会导致很多问题,就弃用了mixinx转而使用HoC。高阶组件本质就是高阶函数,React的组件是一个纯粹的函数,所以高阶函数对React来说非常简单。

4.组件通信的区别

Vue中有三种方式可以实现组件通信:父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;子组件通过事件向父组件发送消息;通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。

React中也有对应的三种方式:父组件通过props可以向子组件传递数据或者回调;可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。React 本身并不支持自定义事件,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。在React中我们都是使用回调函数的,这可能是他们二者最大的区别。

5.模板渲染方式的不同

在表层上,模板的语法不同,React是通过JSX渲染模板。而Vue是通过一种拓展的HTML语法进行渲染,但其实这只是表面现象,毕竟React并不必须依赖JSX。

在深层上,模板的原理不同,这才是他们的本质区别:React是在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的,更加纯粹更加原生。而Vue是在和组件JS代码分离的单独的模板中,通过指令来实现的,比如条件语句就需要 v-if 来实现对这一点,这样的做法显得有些独特,会把HTML弄得很乱。

举个例子,说明React的好处:react中render函数是支持闭包特性的,所以我们import的组件在render中可以直接调用。但是在Vue中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以我们import 一个组件完了之后,还需要在 components 中再声明下,这样显然是很奇怪但又不得不这样的做法。

6.渲染过程不同

Vue可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。

React在应用的状态被改变时,全部子组件都会重新渲染。通过shouldComponentUpdate这个生命周期方法可以进行控制,但Vue将此视为默认的优化。

如果应用中交互复杂,需要处理大量的UI变化,那么使用Virtual DOM是一个好主意。如果更新元素并不频繁,那么Virtual DOM并不一定适用,性能很可能还不如直接操控DOM。

7.框架本质不同

Vue本质是MVVM框架,由MVC发展而来;

React是前端组件化框架,由后端组件化发展而来。

8.Vuex和Redux的区别

从表面上来说,store注入和使用方式有一些区别。在Vuex中, s t o r e 被直接注入到了组件实例中,因此可以比较灵活的使用:使用 d i s p a t c h 、 c o m m i t 提交更新,通过 m a p S t a t e 或者直接通过 t h i s . store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this. store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatchcommit提交更新,通过mapState或者直接通过this.store来读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。

从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用React,小型项目用Vue的感觉。

*vue和react双向绑定原理

vue

vue本身不是双向绑定的,它是单向数据流

vue.js采用的是数据劫持结合发布和-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。 vue3是用的proxy。

读取obj就会调用get方法,赋值obj就会调用set方法

实现mvvm主要包含两个方面,数据变化更新视图,视图变化更新数据:

view更新data其实可以通过事件监听即可,比如input标签监听 ‘input’ 事件就可以实现了。

**data更新view,**数据更新视图的重点是如何知道数据变了,如何知道数据变了,就是通过Object.defineProperty( )对属性设置一个set函数,当数据改变了就会来触发这个函数,所以我们只要将一些需要更新的方法放在这里面就可以实现data更新view了。

我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。因此接下去我们执行以下3个步骤,实现数据的双向绑定:

第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和
getter 这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化

第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每
个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图

第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是:
1、在自身实例化时往消息订阅器Dep里面添加自己
2、自身必须有一个update()方法
3、待属性变动dep.notice()通知时,能调用自身的 update() 方法,并触发Compile中绑定的回
调,则功成身退。

react

vue 相比 react 并没有提供向 v-model 这样的指令来实现文本框的数据流双向绑定,因为react的设计思路就是单向数据流,所以我们需要借助 onChange 和 setState 来实现一个双向的数据流

  • 默认情况下,在React,如果页面表单元素绑定了state上的状态值,那么每当state上的状态值改变,就会把最新的状态值同步到页面上。状态变化>自动更新页面(单向数据流)。
  • 如果UI页面变化,文本框的内容变化了,想把最新的值同步到state上,React没有这样的自动同步机制。需要手动监听比如onChange事件,在onChange事件拿到最新的值,然后调用this.setState()。把它最新的值同步到state中。

双向数据绑定优缺点

只有 UI控件 才存在双向,非 UI控件 只有单向。 单向绑定的优点是可以带来单向数据流,这样的好处是流动方向可以跟踪,流动单一,没有状态, 这使得单向绑定能够避免状态管理在复杂度上升时产生的各种问题, 程序的调试会变得相对容易。单向数据流更利于状态的维护及优化,更利于组件之间的通信,更利于组件的复用

双向数据流的优点:

无需进行和单向数据绑定的那些CRUD(Create,Retrieve,Update,Delete)操作; 双向绑定在一些需要实时反应用户输入的场合会非常方便 用户在视图上的修改会自动同步到数据模型中去,数据模型中值的变化也会立刻同步到视图中去;

缺点:

双向数据流是自动管理状态的, 但是在实际应用中会有很多不得不手动处理状态变化的逻辑, 使得程序复杂度上升 无法追踪局部状态的变化 双向数据流,值和UI绑定,但由于各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到

Vue 虽然通过 v-model 支持双向绑定,但是如果引入了类似redux的vuex,就无法同时使用 v-model。

双绑跟单向绑定之间的差异只在于,双向绑定把数据变更的操作隐藏在框架内部,调用者并不会直接感知。

<input :value=“something” @input=“something = $event.target.value”>

也就是说,你只需要在组件中声明一个name为value的props,并且通过触发input事件传入一个值,就能修改这个value。

*vue和react的生命周期

vue

Vue 实例从创建到销毁的过程,就是生命周期。作用是:它的生命周期中有多个事件钩子,让我们控制Vue实例过程更加清晰方便。

分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

  • beforeCreate: 什么也做不了,vue实例的挂载元素el和数据对象data都是undefined,还没有初始化。
  • created: · vue实例的数据对象data有了,可以访问里面的数据和方法,未挂载到DOM,el还没有,我们可以在created这个勾子中,发起ajax请求
  • beforeMound: vue实例的el和data都初始化了,但是挂载之前为虚拟的dom节点
  • mounted: · vue实例挂载到真实DOM上,就可以通过DOM获取DOM节点,可以发起ajax请求。DOM渲染在这个周期中就已经完成,请求一般在这里,服务端渲染时在created。
  • beforeUpdate: 当data中的数据发生改变,数据改变,视图要重新刷新,就会调用此勾子函数,在这个函数中,拿的数据是新数据,但是,页面中的还是老数据
  • updated: 数据改变,虚拟DOM重新渲染,并且打补丁,此时页面中的老数据,会被替换成真实的数据
    你能在updated这个勾子函数中,更新数据吗?
    答:不能在updated这个勾子中更新数据,会导致死循环。
  • beforeDestory: vm实例销毁之前调用,实例还可以用,this能获取到实例
  • destroyed: vm实例销毁之后调用 没什么用

DOM渲染在哪个周期中就已经完成?

答:DOM 渲染在 mounted 中就已经完成了

第一次页面加载会触发哪几个钩子?
答:beforeCreate, created, beforeMount, mounted

父子组件第一次渲染

父组件:beforeCreate=>created=>beforeMount=>(子组件:beforeCreate=>created=>beforeMount=>mounted)=>mounted

react

初始化挂载:

  • constructor

    react数据的初始化,他接收2个参数,props和context。如果要使用这两个参数,就要使用super(),否则会造成this指向错误。 如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数 。

  • componentWillMount 挂载前

    组件即将渲染未渲染时触发。组件已经经历了constructor初始化数据,但还未渲染DOM。 在这里滥用setState可能会导致重复渲染

  • render

    把需要渲染的内容返回,且必须返回单个元素,不会操作真实的DOM。真实DOM的渲染工作,由ReactDOM.render承接

  • componentDidMount

    此时真实的DOM已经挂载在页面了,可在此执行真实的DOM的相关操作,此外,异步请求、数据初始化等操作也可以在此进行

useEffect,它跟 class 组件中的 componentDidMountcomponentDidUpdatecomponentWillUnmount 具有相同的用途,只不过被合并成了一个 API。 React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候。

更新阶段:

更新阶段主要由父组件更新 或 组件自身调用setState而触发更新

  • componentWillReceiveProps

    如果父组件导致组件重新渲染,即使props没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较 。

    它是由父组件的更新触发,而不是由props的变化触发

  • shouleComponentUpdate

    组件会根据它的返回值来决定是否执行该方法之后的生命周期,进而决定是否对组件进行重渲染,其默认值为true,即无条件重渲染

  • componentWillUpdate

    允许你做一些不涉及真实DOM的准备工作

  • componentDidUpdate

    组件在更新完毕后被触发,可处理DOM操作,此外常将它的执行作为子组件更新完毕的标志通知到父组件

卸载阶段(UnMounting):

组件在父组件中被移除组件设置了key属性,父组件在render的过程中,发现key值和上一次不一致 则需要将其移除,这就涉及到卸载阶段,其只涉及一个生命周期:componentWillUnmount

  • componentWillUnmount

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nz1ltkHh-1659280992490)(https://tuya-static-1254153901.cos.ap-shanghai.myqcloud.com/ghost/content/images/2021/02/3ccb9b5fbc27f560undefined)]

个生命周期

**constructor:**react数据的初始化

componentWillMount:在渲染之前执行,用于根组件中的 App 级配置

componentDidMount:在第一次渲染之后执行,可以在这里做AJAX请求,DOM 的操作或状态更新以及设置事件监听器

componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染

shouldComponentUpdate:确定是否更新组件 默认情况下,它返回true 如果确定在 state 或 props 更新后组件不需要在重新渲染,则可以返回false,这是一个提高性能的方法

componentWillUpdate:在shouldComponentUpdate返回 true 确定要更新组件之前件之前执行

componentDidUpdate:它主要用于更新DOM以响应props或state更改

componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器

*vue和react的虚拟dom

  • vue和react都采用了虚拟dom算法,以最小化更新真实DOM,从而减小不必要的性能损耗。 操作真实dom比虚拟DOM快
  • 按颗粒度分为tree diff, component diff, element diff. tree diff 比较同层级dom节点,进行增、删、移操作。如果遇到component, 就会重新tree diff流程。

react和vue的虚拟dom都是一样的, 都是用JS对象来模拟真实DOM,然后用虚拟DOM的diff来最小化更新真实DOM。除了极个别实现外,两者前半部分(用JS对象来模拟真实DOM)几乎是一样的。

  • react 会自顶向下全diff.
  • vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  1. react 函数式组件思想 当你 setstate 就会遍历 diff 当前组件所有的子节点子组件, 这种方式开销是很大。在react中,当状态发生改变时,组件树就会自顶向下的全diff, 重新render页面, 重新生成新的虚拟dom tree, 新旧dom tree进行比较, 进行patch打补丁方式,局部跟新dom. 所以react为了避免父组件跟新而引起不必要的子组件更新, 可以在shouldComponentUpdate做逻辑判断,减少没必要的render, 以及重新生成虚拟dom,做差量对比过程.
  2. vue组件响应式思想 采用代理监听数据,我在某个组件里修改数据,就会明确知道那个组件产生了变化,只用diff` 这个组件就可以了。 通过Object.defineProperty 把这些 data 属性 全部转为 getter/setter。同时watcher实例对象会在组件渲染时,将属性记录为dep, 当dep 项中的 setter被调用时,通知watch重新计算,使得关联组件更新。

Diff 算法借助元素的 Key 判断元素是新增、删除、修改,从而减少不必要的元素重渲染。

1.vue对比节点。当节点元素相同,但是classname不同,认为是不同类型的元素,删除重建,而react认为是同类型节点,只是修改节点属性。

2.vue的列表对比,采用的是两端到中间比对的方式,而react采用的是从左到右依次对比的方式。当一个集合只是把最后一个节点移到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移到第一个。总体上,vue的方式比较高效。

虚拟dom会提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存,避免了没有必要的dom操作,从而提高性能。

虚拟 DOM 的实现原理主要包括以下 3 部分:

  • 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
  • diff 算法 — 比较两棵虚拟 DOM 树的差异;
  • pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

*vue和react的状态管理

vuex

1.vuex是什么?怎么使用?哪种功能场景使用它?
答:vue框架中状态管理。在main.js引入store,注入。
新建了一个目录store.js,…… export 。
场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
2.vuex有哪几种属性?
答:有五种,分别是 State、 Getter、Mutation 、Action、 Module
state => 基本数据(数据源存放地)
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex
3.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
答:如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用。

redux

阮一峰redux

https://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html

  1. Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。 Redux 提供createStore这个函数,用来生成 Store
  2. state 对象包含所有数据。

Vuex和Redux的区别

从表面上来说,store注入和使用方式有一些区别。在Vuex中, s t o r e 被直接注入到了组件实例中,因此可以比较灵活的使用:使用 d i s p a t c h 、 c o m m i t 提交更新,通过 m a p S t a t e 或者直接通过 t h i s . store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this. store被直接注入到了组件实例中,因此可以比较灵活的使用:使用dispatchcommit提交更新,通过mapState或者直接通过this.store来读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只能进行dispatch,不能直接调用reducer进行修改。

从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用React,小型项目用Vue的感觉。

(1)Redux 和 Vuex区别

  • Vuex改进了Redux中的Action和Reducer函数,以mutations变化函数取代Reducer,无需switch,只需在对应的mutation函数里改变state值即可
  • Vuex由于Vue自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的State即可
  • Vuex数据流的顺序是∶View调用store.commit提交对应的请求到Store中对应的mutation函数->store改变(vue检测到数据变化自动渲染)

通俗点理解就是,vuex 弱化 dispatch,通过commit进行 store状态的一次更变;取消了action概念,不必传入特定的 action形式进行指定变更;弱化reducer,基于commit参数直接对数据进行转变,使得框架更加简易;

(2)共同思想

  • 单—的数据源
  • 变化可以预测

本质上∶ redux与vuex都是对mvvm思想的服务,将数据从视图中抽离的一种方案。

*vue和react的组件通信

vue

  1. 父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;
  2. 子组件通过事件向父组件发送消息;$emit方法
  3. 通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。
  4. loaclStorage和sessionStorage
  5. Vuex
  6. ref
  7. v-model

react

  • props

    • 子 --> 父,父组件传函数数据给子组件,子组件调用修改父组件数据
    • 父 --> 子,父组件传非函数数据给子组件
    • 兄弟(一般不用),数据定义在公共的父组件中。
  • context上下文

    • 适用于祖孙组件,层级嵌套太深。让子组件直接访问祖先的数据或函数,通过this.context.xx
    • const context = React.createContext()
    • context.Provider / context.Consumer
  • pubsub-js

    • 适用于兄弟组件、祖孙组件。
    • pubsub.subscribe(‘MSG’, (msg, data) => {})
    • pubsub.publish(‘MSG’, data)
    • 注意:
      • 先订阅再发布
      • 订阅只能一次,发布可以多次
  • redux

    • 管理多个组件共享的数据
  • web storage ==> localStorage/sessionStorage

    • 保存在浏览器本地,引入使用
    • 也可以跨页面通信

组件通信的区别

Vue中有三种方式可以实现组件通信:

  1. 父组件通过props向子组件传递数据或者回调,虽然可以传递回调,但是我们一般只传数据;
  2. 子组件通过事件向父组件发送消息;$emit方法
  3. 通过V2.2.0中新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。

React中也有对应的三种方式:

  1. 父组件通过props可以向子组件传递数据或者回调;
  2. 可以通过 context 进行跨层级的通信,这其实和 provide/inject 起到的作用差不多。
  3. **通过父组件向子组件传递函数,然后子组件中调用这些函数,利用回调函数实现数据传递 。**Vue中子组件向父组件传递消息有两种方式:事件和回调函数,但Vue更倾向于使用事件。在React中我们都是使用回调函数的,这可能是他们二者最大的区别。

1.兄弟组件不能直接相互传送数据,此时可以将数据挂载在父组件中,由两个组件共享

2.子组件向父组件通讯,可以通过父组件定义事件(回调函数),子组件调用该函数,通过实参的形式来改变父组件的数据来通信

3.非父子组件间的通信:可以使用全局事件来实现组件间的沟通,React中可以引入eventProxy模块,利用eventProxy.trigger()方法发布消息,eventProxy.on()方法监听并接收消息。

4.组件间层级太深,可以使用上下文方式,让子组件直接访问祖先的数据或函数,通过this.context.xx

*vue和react的HTML和jsx

JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。

jsx是javasctiptde 语法扩展。

1、JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化

2、JSX 是类型安全的,在编译过程中就能发现错误

3、使用 JSX 编写模板更简单快速

vue模板与JSX比较

1. 表达式(expressions)

JSX表达式用{}包裹,vue模板表达式用{{}}包裹,其余一致.

2.指令(directives)
  • v-if, v-else-if, v-else JSX直接用万能的三元表达式
  • v-show JSX在dom上添加如下代码,实现一样的功能
style={ifShow ?{} : { display: 'none' }}复制代码
3. 列表渲染(list rendering)

Vue模板列表渲染:v-for

React JSX: 直接在dom中插入map函数,里面直接用js来处理参数

4. 绑定(binding)

绑定其实就是组件的传参,通过绑定,将对应的参数传递到子组件。常见的绑定有:1、普通属性绑定;2、style属性绑定,3、class属性绑定; Vue的数据绑定:

React JSX: 呵呵,style不是属性啊?class不是属性啊?弄那么复杂干嘛?!什么?你们说我们style不支持数组?你将数组转换成对象就好了吗?你说我们class不支持对象以及数组? classnames

5. 事件处理(actions/events)

Vue模板:记住了,有以下事件处理

JSX:跟js事件名称一致,直接放置在对应的节点进行事件绑定就好

<!--vue-->  
<template>    
<button v-on:click="handleBtn">我是一个点击按钮</button>  
</template>   

<!--react-->  
<div>    
<button onClick={this.handleBtn}>我是一个点击按钮</button> 
</div>复制代码
总结

Vue模板与JSX的基本使用比较完了,Vue模板为你考虑了很多优化的东西,你只需要记住其中的规则就好,更大的降低了技术门槛。而JSX将更大的控制权留给了代码编写者。

jsx渲染原理

将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babel和webpack等工具将其转换为传统的JS。

1.基于babel-preset-react-app这个语法解析包,把jsx语法转换成一个名为 React.createElement() 的方法调用。

2.基于createElement把传递的参数处理为jsx对象 。):React在渲染解析的时候,会把所有的html标签都转换为(返回一个对象):

3.基于render把jsx对象按照动态创建dom元素的方式插入到指定的容器中即可。

面试

一万条数据

后端返回1w条数据,本身技术方案设计就不合理

虚拟列表:

只渲染可视区域 DOM

其他隐藏区域不显示,只用

撑起高度

监听容器滚动,随时创建和销毁 DOM

虚拟列表实现起来非常复杂,可借用第三方 lib

new做了什么

1.开辟了一个内存空间,内存空间存储一个空对象;

2.将空对象的原型prototype指向构造函数的原型

3.改变this指向,让this指向这个空对象;并执行函数体;

4.对构造函数有返回值的判断

function create(Con,...args){
    //1、创建一个空的对象
    let obj = {}; // let obj = Object.create({});
    //2、将空对象的原型prototype指向构造函数的原型
    Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
    //3、改变构造函数的上下文(this),并将剩余的参数传入
    let result = Con.apply(obj,args);
    //4、在构造函数有返回值的情况进行判断。
    return result instanceof Object?result:obj;
}

构造函数如果有返回值得话,就返回这个对象,没有返回对象的话就返回创建的这个obj

比较两个对象是否相等

比较2个对象是否相同
isObjEqual(a, b) {
      // 判断两个对象是否指向同一内存,指向同一内存返回true
      if (a === b) return true;
      // 获取两个对象键值数组
      let aProps = Object.getOwnPropertyNames(a);
      let bProps = Object.getOwnPropertyNames(b);
      // 判断两个对象键值数组长度是否一致,不一致返回false
      if (aProps.length !== bProps.length) return false;
      // 遍历对象的键值
      for (let prop in a) {
        // 判断a的键值,在b中是否存在,不存在,返回false
        if (b.hasOwnProperty(prop)) {
          // 判断a的键值是否为对象,是则递归,不是对象直接判断键值是否相等,不相等返回false
          if (typeof a[prop] === "object") {
            if (!this.isObjEqual(a[prop], b[prop])) return false;
          } else if (a[prop] !== b[prop]) {
            return false;
          }
        } else {
          return false;
        }
      }
      return true;
    },

组件化和模块化

*为什么要组件化开发*

有时候页面代码量太大,逻辑太多或者同一个功能组件在许多页面均有使用,维护起来相当复杂,这个时候,就需要组件化开发来进行功能拆分、组件封装,已达到组件通用性,增强代码可读性,维护成本也能大大降低

*组件化开发的优点*

很大程度上降低系统各个功能的耦合性,并且提高了功能内部的聚合性。这对前端工程化及降低代码的维护来说,是有很大的好处的,耦合性的降低,提高了系统的伸展性,降低了开发的复杂度,提升开发效率,降低开发成本

*组件化开发的原则*

· 专一

· 可配置性

· 标准性

· 复用性

· 可维护性

*模块化*

*为什么要模块化*

早期的javascript版本没有块级作用域、没有类、没有包、也没有模块,这样会带来一些问题,如复用、依赖、冲突、代码组织混乱等,随着前端的膨胀,模块化显得非常迫切

*模块化的好处*

· 避免变量污染,命名冲突

· 提高代码复用率

· 提高了可维护性

· 方便依赖关系管理

跨分页全选

算法

数组拍平

斐波那契数列

数组三个最大值

字符串出现次数最多

第一个字数为1的字符

字符串翻转:先转数字,再翻转,在转字符串

ver str = '123456'
str = str.split().reverse().join('')

数字12345678转为 12,345,678

排序

希尔排序(性能最好)

如果要从大到小排列,则 while(arr[n] > arr[n - interval] && n > 0) 。

// 希尔排序算法
function xier(arr){
 var interval = parseInt(arr.length / 2);//分组间隔设置
 while(interval > 0){
  for(var i = 0 ; i < arr.length ; i ++){
   var n = i;
   while(arr[n] < arr[n - interval] && n > 0){
    var temp = arr[n];
    arr[n] = arr[n - interval];
    arr[n - interval] = temp;
    n = n - interval;
   }
  }
  interval = parseInt(interval / 2);
 }
 return arr;
}
 
// Array
var arr = [10, 20, 40, 60, 60, 0, 30]
 
// 打印排序后的数组
console.log(xier(arr))//[0, 10, 20, 30, 40, 60, 60]

冒泡排序

function bubbleSort (arr) {
  for (let i = 0; i < arr.length; i++) {
    let flag = true;
    for (let j = 0; j < arr.length - i - 1; j++) {
      if (arr[j] > arr[j + 1]) {
        flag = false;
        let temp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = temp;
      }
    }
    if (flag) break;
  }
  return arr;
}

这个是优化过后的冒泡排序。用了一个flag来优化,它的意思是:如果某一次循环中没有交换过元素,那么意味着排序已经完成了。

冒泡排序总会执行(N-1)+(N-2)+(N-3)+…+2+1趟,但如果运行到当中某一趟时排序已经完成,或者输入的是一个有序数组,那么后边的比较就都是多余的,为了避免这种情况,我们增加一个flag,判断排序是否在中途就已经完成(也就是判断有无发生元素交换)

sort排序(普通数组 / 数组嵌套对象)

一堆数组排序

`// Array``var` `arr = [10, 20, 40, 60, 60, 0, 30]` `// 排序方法``arr.sort(``function``(a,b){` ` ``/*`` ``* return b-a; —> 降序排序`` ``* return a-b; —> 升序排列`` ``*/`` ``return` `a-b;``})``//括号里不写回调函数则默认按照字母逐位升序排列` `// 打印排序后的数组``console.log(arr)``//[0, 10, 20, 30, 40, 60, 60]`

对象数组排序(数组套对象)

`//对象数组排序``var` `arr = [`` ``{name:``'syy'``, age:0},`` ``{name:``'wxy'``, age:18},`` ``{name:``'slj'``, age:8},`` ``{name:``'wj'``, age:20}``];` `// 排序方法``function` `compare(property) {``//property:根据什么属性排序`` ``return` `function``(a,b){`` ``var` `value1 = a[property];`` ``var` `value2 = b[property];`` ``/*`` ``* value2 - value1; ——> 降序`` ``* value1 - value2; ——> 升序`` ``*/`` ``return` `value1 - value2;``//升序排序`` ``}``}` `// 打印排序后的数组``console.log(arr.sort(compare(``'age'``)))``/*``0: {name: "syy", age: 0}``1: {name: "slj", age: 8}``2: {name: "wxy", age: 18}``3: {name: "wj", age: 20}``*/`

三、桶排序

用js递归的方式写1到100求和?

function add(num1,num2){
	var num = num1+num2;
        if(num2+1>100){
	 return num;
	}else{
	  return add(num,num2+1)
        }
 }
var sum =add(1,2);        

手写一个发布订阅

`// 发布订阅中心, on-订阅, off取消订阅, emit发布, 内部需要一个单独事件中心caches进行存储;``interface CacheProps {`` ``[key: string]: Array<((data?: unknown) => void)>;``}` `class Observer {`` ``private caches: CacheProps = {}; ``// 事件中心`` ``on (eventName: string, fn: (data?: unknown) => void){ ``// eventName事件名-独一无二, fn订阅后执行的自定义行为``  ``this``.caches[eventName] = ``this``.caches[eventName] || [];``  ``this``.caches[eventName].push(fn);`` ``}` ` ``emit (eventName: string, data?: unknown) { ``// 发布 => 将订阅的事件进行统一执行``  ``if` `(``this``.caches[eventName]) {``   ``this``.caches[eventName].forEach((fn: (data?: unknown) => void) => fn(data));``  ``}`` ``}` ` ``off (eventName: string, fn?: (data?: unknown) => void) { ``// 取消订阅 => 若fn不传, 直接取消该事件所有订阅信息``  ``if` `(``this``.caches[eventName]) {``   ``const newCaches = fn ? ``this``.caches[eventName].filter(e => e !== fn) : [];``   ``this``.caches[eventName] = newCaches;``  ``}`` ``}` `}`
手写数组转树
// 例如将 input 转成output的形式
let input = [
    {
        id: 1, val: '学校', parentId: null
    }, {
        id: 2, val: '班级1', parentId: 1
    }, {
        id: 3, val: '班级2', parentId: 1
    }, {
        id: 4, val: '学生1', parentId: 2
    }, {
        id: 5, val: '学生2', parentId: 2
    }, {
        id: 6, val: '学生3', parentId: 3
    },
]

let output = {
    id: 1,
    val: '学校',
    children: [{
        id: 2,
        val: '班级1',
        children: [
            {
                id: 4,
                val: '学生1',
                children: []
            },
            {
                id: 5,
                val: '学生2',
                children: []
            }
        ]
    }, {
        id: 3,
        val: '班级2',
        children: [{
            id: 6,
            val: '学生3',
            children: []
        }]
    }]
}

异步打印判断

// 今日头条面试题

async function async1() {

  console.log('async1 start')

  await async2()

  console.log('async1 end')

}

async function async2() {

  console.log('async2')

}

console.log('script start')

setTimeout(function () {

  console.log('settimeout')

})

async1()

new Promise(function (resolve) {

  console.log('promise1')

  resolve()

}).then(function () {

  console.log('promise2')

})

console.log('script end')

script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout

delete判断

var company = {
    address: 'beijing'
}
var yideng = Object.create(company);
delete yideng.address
console.log(yideng.address);
// 写出执行结果,并解释原因
beijing

这里的 yideng 通过 prototype 继承了 company的 address。yideng自己并没有address属性。所以delete操作符的作用是无效的。

扩展
1.delete使用原则:delete 操作符用来删除一个对象的属性。
2.delete在删除一个不可配置的属性时在严格模式和非严格模式下的区别:
(1)在严格模式中,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常;
(2)非严格模式下返回 false。
3.delete能删除隐式声明的全局变量:这个全局变量其实是global对象(window)的属性
4.delete能删除的:
(1)可配置对象的属性(2)隐式声明的全局变量 (3)用户定义的属性 (4)在ECMAScript 6中,通过 const 或 let 声明指定的 “temporal dead zone” (TDZ) 对 delete 操作符也会起作用
delete不能删除的:
(2)显式声明的全局变量 (2)内置对象的内置属性 (3)一个对象从原型继承而来的属性
5.delete删除数组元素:
(1)当你删除一个数组元素时,数组的 length 属性并不会变小,数组元素变成undefined
(2)当用 delete 操作符删除一个数组元素时,被删除的元素已经完全不属于该数组。
(3)如果你想让一个数组元素的值变为 undefined 而不是删除它,可以使用 undefined 给其赋值而不是使用 delete 操作符。此时数组元素是在数组中的
6.delete 操作符与直接释放内存(只能通过解除引用来间接释放)没有关系。

模拟

你是怎么理解 什么是库?什么是框架?

  • 库:是工具箱 封装了大量的api ,jquery就是一个库,在写代码时,我们需要主动地去调用库里面的api
  • 框架:项目的半成品,定义了很多的规则,在合适的地方写合适的代码,等着框架去调用,被动的。

你是如何理解MVC和MVVM的

M:Model data选项
V:View 视图 模板
VM:vue框架帮我们实现的 不用我们写
M中的数据变了,V肯定要刷新
V中的通过Input框,M中的数据也会更新

MVC:理解的思想

vue响应式原理,和响应式缺陷?

响应式原理,就是遍历data中的所有属性,给所有的属性使用definedproty,定义它的get和set,遍历过程是递归的,性能不好,在vue3中,使用了proxy.
1)…

vue实例上的属性和方法?

vm. d a t a v m . data vm. datavm.options
vm. e l v m . el vm. elvm.nextTick
vm. m o u n t v m . mount vm. mountvm.watch

vue中常用的指令?

v-show和v-if区别?

1)v-if是用来控制一个元素是否创建和销毁
2)v-show控制display:none 之前是什么性别,加的还是什么性别

v-if和v-for的连用?

可以使用,不建议使用,v-for的优先级高,先把数据渲染出来了,再去判断是否创建和销毁

v-for上面加一个key,它有什么作用?

v-model:

v-model:
input :value=“” @input=“”
checkbox :checked=“” @change=“”

render template el: 生命周期那个图

1) render

2)template 

3) el 
4) $mounut

常见组件通信方式:

1)props
2)自定义事件
3) r e f 3 ) v u e x 5 ) ref 3)vuex 5) ref3vuex5parent $chinldren
6)事件总线

为什么要给组件上添加name属性:

1)递归组件
2)路由跳转时可以使用name

vue中的插槽分类?

Vue插槽是Vue中常见的一种组件间的相互通信方式,作用是让父组件可以向子组件指定位置插入html结构,适用于父组件===>子组件,在要接收数据的组件页面通过<slot>标签来表示,简单来说,就是通过此标签来起到占位的作用,而要插入的内容也会对应到标签所在的位置。

简单理解就是子组件中留下个“坑”,父组件可以使用指定内容来补“坑”。

1)匿名插槽
2)具名插槽
3)作用域插槽

Vue如何检测数组变化?

1)重写数组中的7个方法
push unshift…

生命周期钩子?

8个生命周期钩子

Vue.mixin的使用?

Vue的生命周期方法有哪些?一般在哪一步发起请求及原因?

beforeCreate 在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。
created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有 e l b e f o r e M o u n t 在挂载开始之前被调用:相关的 r e n d e r 函数首次被调用。 m o u n t e d e l 被新创建的 v m . el beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。 mounted el 被新创建的 vm. elbeforeMount在挂载开始之前被调用:相关的render函数首次被调用。mountedel被新创建的vm.el 替换,并挂载到实例上去之后调用该钩子。
beforeUpdate 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。
updated 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。
destroyed Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移
除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。

created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。(服务端渲染支持created方法)
mounted 实例已经挂载完成,可以进行一些DOM操作
beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。
updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。
destroyed 可以执行一些优化操作,清空定时器,解除绑定事件

Vue组件间传值的方式及之间的区别?

props和 e m i t 父组件向子组件传递数据是通过 p r o p 传递的,子组件传递数据给父组件是通过 emit 父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过 emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过emit触发事件来做到的
p a r e n t , parent, parent,children 获取当前组件的父组件和当前组件的子组件
a t t r s 和 attrs和 attrslisteners A->B->C。Vue 2.4 开始提供了 a t t r s 和 attrs和 attrslisteners来解决这个问题
父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量。
$refs 获取实例
envetBus 平级组件数据传递 这种情况下可以使用中央事件总线的方式
vuex状态管理

Vue中组件的data为什么是一个函数?

每次使用组件时都会对组件进行实例化操作,并且调用data函数返回一个对象作为组件的数据源。这样可以保证多个组件间数据互不影响

Vue.use是干什么的?

Vue.use是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法等。

vue-router有几种钩子函数?具体是什么及执行流程是怎样的?

路由钩子的执行流程, 钩子函数种类有:全局守卫、路由守卫、组件守卫
完整的导航解析流程:
①导航被触发。
②在失活的组件里调用 beforeRouteLeave 守卫。
③调用全局的 beforeEach 守卫。
④在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
⑤在路由配置里调用 beforeEnter。
⑥解析异步路由组件。
⑦在被激活的组件里调用 beforeRouteEnter。
⑧调用全局的 beforeResolve 守卫 (2.5+)。
⑨导航被确认。
⑩调用全局的 afterEach 钩子。
⑪触发 DOM 更新。
⑫调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

自定义指令?

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

谈一下你对vuex的个人理解?

vuex是专门为vue提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)

衍生的问题action和mutation的区别
核心方法: replaceState、subscribe、registerModule、namespace(modules)
------------------------------------------------------------

keep-alive平时在哪使用?

keep-alive主要是缓存,采用的是LRU算法。
------------------------------------------------------------

谈谈Vue3和Vue2的区别?

1)对TypeScript支持不友好(所有属性都放在了this对象上,难以推倒组件的数据类型)
2)大量的API挂载在Vue对象的原型上,难以实现TreeShaking。
3)架构层面对跨平台dom渲染开发支持不友好
4)CompositionAPI。受ReactHook启发
5)对虚拟DOM进行了重写、对模板的编译进行了优化操作…

vue-router两种模式的区别?

hash模式、history模式
hash模式:hash + hashChange 兼容性好但是不美观
history模式 : historyApi+popState 虽然美观,但是刷新会出现404需要后端进行配置
------------------------------------------------------------

vue中使用了哪些设计模式?

工厂模式
单例模式
发布-订阅模式
观察者模式
代理模式
中介者模式 => vuex
------------------------------------------------------------

vue相比react的优点?

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

对axios封装,都是怎么封装的?

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

vue项目,跨域都是怎么解决的?

开发时,如何解决跨域?
项目上线,如何解决跨域?

v-show和v-if区别?

v-for和v-if连用问题?

生命周期函数?

你是如何理解vuex?

vue-router路由模式,以及区别?

介绍你做过的项目?

1)这个项目是干什么用的
2)项目分工
3)项目周期
4)我负责的模块
5)这些模块我用到的什么技术

项目介绍流程:项目描述-项目分工(几个前端几个后端)-项目开发周期-我在项目中负责的模块是什么-我负责的这些模块主要的技术栈是什么-还包含哪些技术

vue是如何实现数据响应式式?

1)递归遍历所的有data,性能不好

vue中的组件通信方式?

1)父子通信
2)兄弟通信
3)没有关系的组件之间的通信
props和 e m i t 父组件向子组件传递数据是通过 p r o p 传递的,子组件传递数据给父组件是通过 emit 父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过 emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过emit触发事件来做到的
p a r e n t , parent, parent,children 获取当前组件的父组件和当前组件的子组件
a t t r s 和 attrs和 attrslisteners A->B->C。Vue 2.4 开始提供了 a t t r s 和 attrs和 attrslisteners来解决这个问题
父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量。
$refs 获取实例
envetBus 平级组件数据传递 这种情况下可以使用中央事件总线的方式
vuex状态管理

实例方法:

vm._uid (每个实例的唯一标识)
vm. d a t a = = = v m . d a t a ( 实例的数据源 ) v m . data === vm._data (实例的数据源) vm. data===vm.data(实例的数据源)vm.options (用户传入的属性)
vm. e l ( 当前组件的真实 d o m ) v m . el (当前组件的真实dom) vm. el(当前组件的真实dom)vm.nextTick (等待同步代码执行完毕)
vm. m o u n t ( 手动挂载实例 ) v m . mount (手动挂载实例) vm. mount(手动挂载实例)vm.watch (监控数据变化)
------------------------------------------------------------

v-model:

原理?? :value @input
在你把v-model写在什么地方了

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

你在使用uniapp时遇到的问题?

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

你写的项目上线了吗?

vue中的函数式组件?

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

vue移动端适配?

rem
------------------------------------------------------------

前后端分离开发的鉴权方案?

前后分离 jwt
服务端渲染 session
------------------------------------------------------------

不会的不要写在简历上

事件修饰符都有哪些?

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

你是如何理解vuex的?

1)对项目中的状态进行全局管理
2)缓存
3)不能对数据进行持久化

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

keep-alive有什么用?

keep-alive缓存DOM元素

computed和watch的使用场景?

computed:
    当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:
    当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据

vue是MVVM吗?

vue并不是一个真正的MVVM,因为vue可以操作DOM。
------------------------------------------------------------

vue如何获取DOM元素?

答:通过ref
------------------------------------------------------------

vue-loader?

答:翻译.vue文件
------------------------------------------------------------

computed和watch的使用场景?

computed:
    当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:
    当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据
------------------------------------------------------------

v-on可以监听多个方法吗?

答:可以,栗子:
------------------------------------------------------------

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

vue组件中data为什么必须是一个函数?

答:因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。
  组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。

SPA首屏加载慢如何解决?

1)安装动态懒加载所需插件;使用CDN资源。
2)SSR
------------------------------------------------------------

Vue里面router-link在电脑上有用,在安卓上没反应怎么解决?

答:Vue路由在Android机上有问题,babel问题,安装babel polypill插件解决。

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

axios的特点有哪些?

从浏览器中创建XMLHttpRequests;
node.js创建http请求;
支持Promise API;
拦截请求和响应;
转换请求数据和响应数据;
取消请求;
自动换成json。
axios中的发送字段的参数是data跟params两个,两者的区别在于params是跟请求地址一起发送的,data的作为一个请求体进行发送
params一般适用于get请求,data一般适用于post put 请求。
------------------------------------------------------------

vue更新数组时触发视图更新的方法?

答:push();pop();shift();unshift();splice(); sort();reverse()

v-show和v-if区别?

v-for和v-if连用问题?

生命周期函数?

你是如何理解vuex?

vue-router路由模式,以及区别?

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

vue路由传参数?

使用query方法传入的参数使用this. r o u t e . q u e r y 接受使用 p a r a m s 方式传入的参数使用 t h i s . route.query接受 使用params方式传入的参数使用this. route.query接受使用params方式传入的参数使用this.route.params接受
------------------------------------------------------------

vue子组件调用父组件的方法?

第一种方法是直接在子组件中通过this. p a r e n t . e v e n t 来调用父组件的方法第二种方法是在子组件里用 parent.event来调用父组件的方法 第二种方法是在子组件里用 parent.event来调用父组件的方法第二种方法是在子组件里用emit向父组件触发一个事件,父组件监听这个事件就行了。
------------------------------------------------------------

vue中的 ref 是什么?

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
------------------------------------------------------------

页面刷新vuex被清空解决办法?

1.localStorage 存储到本地再回去
2.重新获取接口获取数据
------------------------------------------------------------

动态绑定class:

:class=“box”
:class=“{box:true,box1:false}”
:class=“[box,box1]”

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

为什么vue的data必须是个函数?

同一个组件被复用多次,会创建多个实例。这些实例用的是同一个构造函数,如果 data 是一个对象的话。那么所有组件都共享了同一个对象。为了保证组件的数据独立性要求每个组件必须通过 data 函数返回一个对象作为组件的状态。

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

vue数据响应式原理?

通过 Object.defineProperty() 替换配置对象属性的 set、get 方法,实现“拦截”
watcher 在执行 getter 函数时触发数据的 get 方法,从而建立依赖关系
写入数据时触发 set 方法,从而借助 dep 发布通知,进而 watcher 进行更新

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

computed、watch和method的区别?

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

不同的生命周期过程中,分别可以干点什么?

beforeCreate:data,watcher,methods都不存在这个阶段。但是有一个对象存在,那就是$route,因此此阶段就可以根据路由信息进行重定向等操作。
created:实例已经创建完成,可以进行数据、资源请求;
mounted:实例挂载到了dom上,可以进行一些dom操作;
beforeUpdate:可以在钩子中进一步的更新状态,这时的更新不会触发附加的重渲染过程;
updated:更新已完成,这时最好不要再进行状态更新,不然可能导致死循环
beforeDestroyed:实例销毁前的钩子函数,可以做一些优化操作,比如清理定时器、解除绑定事件、解除监听

不同的生命周期过程中,分别可以干点什么?

beforeCreate:data,watcher,methods都不存在这个阶段。但是有一个对象存在,那就是$route,因此此阶段就可以根据路由信息进行重定向等操作。
created:实例已经创建完成,可以进行数据、资源请求;
mounted:实例挂载到了dom上,可以进行一些dom操作;
beforeUpdate:可以在钩子中进一步的更新状态,这时的更新不会触发附加的重渲染过程;
updated:更新已完成,这时最好不要再进行状态更新,不然可能导致死循环
beforeDestroyed:实例销毁前的钩子函数,可以做一些优化操作,比如清理定时器、解除绑定事件、解除监听
------------------------------------------------------------

ajax请求放在哪个生命周期?

放在created之后的都可以,一般放在created和mounted,前者的时机更早,
如果在页面挂载完之前请求完成的话就不会看到闪屏,
但无法操作dom,且异步请求很有可能在挂载完成后数据还没拿到;
后者可以操作dom,所以有必须依赖dom的情况的话,可以放在mounted的$nextTick里。
------------------------------------------------------------

beforeDestroy什么时候使用?

当前页面中使用了$on时,需要在组件销毁前解绑;
清除自己定义的定时器;解除事件绑定,比如mousemove、scroll之类的
------------------------------------------------------------

v-if和v-for为什么不能同时使用?

因为同时使用时,vue的内在逻辑是会先处理循环,
然后对循环的每一项做条件判断。如果循环的项非常多,那就会造成非常大的性能消耗。
------------------------------------------------------------

v-html会导致什么问题?

会导致xss注入攻击,
------------------------------------------------------------

组件间的通信:

父子 props/event children ref provide/inject
兄弟 bus vuex
跨级 bus vuex provide inject

项目都有哪些模块?

vue中的组件通信方式?

父子 props/event children ref provide/inject
兄弟 bus vuex
跨级 bus vuex provide inject

组件中写name选项又哪些好处及作用?

可以通过名字找到对应的组件 (递归组件)
可用通过name属性实现缓存功能 (keep-alive)
可以通过name来识别组件 (跨级组件通信时非常重要)
------------------------------------------------------------

Vue事件修饰符有哪些?

事件修饰符有:.capture、.once、.passive 、.stop、.self、.prevent
------------------------------------------------------------

vue中的ref?

1)ref放在组件中
2)ref放在标签中 获取DOM元素

你都做过哪些Vue的性能优化?

尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件在
更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载

学习地址

博客园:https://www.cnblogs.com/moqiutao/p/7389146.html

牛客网:https://www.nowcoder.com/tutorial/96/24304825a0c04ea9a53cdb09cb664834

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值