虚拟列表分为视觉可见区域和可滚动区域,
就是根据「容器可视区域」的「列表容积数量」,监听用户滑动或者滚动事件,截取「 长列表数据」中的部分数据渲染到页面上,使用空白占位填充容器「上下滚动区域内容」,模拟实现「原生滚动效果」。
1.计算当前可见区域起始数据的 startIndex
2.计算当前可见区域结束数据的 endIndex
3.计算当前可见区域的数据,并渲染到页面中
4.计算 startIndex 对应的数据在整个列表中的偏移位置 startOffset,并设置到列表上
HTML
web语义化的好处
- 去掉或者丢失样式的时候能够让页面呈现出清晰的结构
- 有利与SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息;爬虫依赖于标签来确定上下文和各个关键字的权重
- 方便其他设备解析(如屏幕阅读器、移动设备)以意义的方式来渲染页面
- 便于团队开发和维护,语义化更具可读性,可以减少差异化
html全局属性是什么
在html中,全局属性是指可以用来配置所有元素共有行为、可以用在任何一个元素身上的
属性。常用全局属性有:class、hidden、id、lang、style、title、dir、
contenteditable等等。
局部属性:有些元素能规定自己的属性,这种属性称为局部属性。 比如 link 元素,它具
有的局部属性有 href、 rel、 hreflang、 media、 type、 sizes 这六个。
@import和link的区别
加载顺序
link引入的css在加载页面时同时加载,而@import中的css要在页面加载完毕后加载
从属关系
link是HTML提供的标签
@import是css的语法规则,只能加载在style标签内和css文件中
兼容性
@import是css2.1时提出的,只能用于IE5+,而link不受兼容影响
DOM可控性
link支持js控制DOM改变样式,而@import不支持
echarts默认显示坐标轴
if (TrendDataOption.xAxis.data.length == 0) {
TrendDataOption.xAxis.data = ['00:00:00', '01:00:00', '02:00:00', '03:00:00', '04:00:00','05:00:00', '06:00:00'];
TrendDataOption.yAxis.min = 0;
TrendDataOption.yAxis.max = 100;
TrendDataOption.yAxis.axisLine.show = true;
}
TrendDataChart.setOption(TrendDataOption)
element二次封装
主要以父传子的形式实现,将需要复用的组件进行封装成子组件,子组件内主要负责页面的展示,Props接收父组件传来的值,将需要进行的逻辑操$emit给父组件。父组件引入子组件并向子组件内传入不同的值,使页面显示不同的文字描述,接收子组件传来的值编写函数来进行逻辑操作。
主界面封装:将element ui中的表格Table、分页Pagination、Input输入框、Select搜索框等放进一个组件内,在props里定义好要从父组件接收的值,在函数里定义好 $emit传给父组件的值。
弹窗封装:将element ui中的对话框Dialog、表单Form、上传Upload等放进一个组件内,在props里定义好要从父组件接收的值,在函数里定义好 $emit传给父组件的值
CSS
1、怪异盒模型和标准盒模型 box-sizing
-
当然盒模型包括两种:IE盒模型和w3c标准盒模型
-
IE盒模型 = 设置的width + margin。其中,width值包含padding和border。
-
标准盒模型 = width+padding+border+margin。
-
可以通过修改元素的box-sizeing属性来改变元素的盒模型:
box-sizeing: content-box表示标准盒模型(默认值)box-sizeing: border-box表示IE盒模型(怪异盒模型)
2、隐藏元素的方法有哪些
- display: none:渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。
- 看小学生···visibility: hidden:元素在页面中仍占据空间,但是不会响应绑定的监听事件。
- opacity: 0:将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。
- position: absolute:通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。
- z-index: 负值:来使其他元素遮盖住该元素,以此来实现隐藏。
- clip/clip-path :使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。
- transform: scale(0,0) :将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。
3、CSS选择器及其优先级
| 选择器 | 格式 | 优先级权重 |
|---|---|---|
| id选择器 | #id | 100 |
| 类选择器 | #classname | 10 |
| 属性选择器 | a[ref=“eee”] | 10 |
| 伪类选择器 | li:last-child | 10 |
| 标签选择器 | div | 1 |
| 伪元素选择器 | li:after | 1 |
| 相邻兄弟选择器 | h1+p | 0 |
| 子选择器 | ul>li | 0 |
| 后代选择器 | li a | 0 |
| 通配符选择器 | * | 0 |
对于选择器的优先级:
- 标签选择器、伪元素选择器:1
- 类选择器、伪类选择器、属性选择器:10
- id 选择器:100
- 内联样式:1000
注意事项:
- !important声明的样式的优先级最高;
- 如果优先级相同,则最后出现的样式生效;
- 继承得到的样式的优先级最低;
- 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为 0 ;
- 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
4、display的属性值及其作用
| 属性值 | 作用 |
|---|---|
| none | 元素不显示,并且会从文档流中移除。 |
| block | 块类型。默认宽度为父元素宽度,可设置宽高,换行显示。 |
| inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示。 |
| inline-block | 默认宽度为内容宽度,可以设置宽高,同行显示。 |
| list-item | 像块类型元素一样显示,并添加样式列表标记。 |
| table | 此元素会作为块级表格来显示。 |
| inherit | 规定应该从父元素继承display属性的值。 |
5、BFC:块级格式化上下文
官方解释:它决定了元素如何对其内容进行定位,以及与其它元素的关系和相互作用,当涉及到可视化布局时,BFC提供了一个环境,HTML在这个环境中按照一定的规则进行布局。
简单来说:BFC是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。
创建:浮动元素 display:inline-block position:absolute
应用: 1.分属于不同的BFC时,可以防止margin重叠 2.清除内部浮动 3.自适应多栏布局
6、定位position
定位元素会脱离文档流,按照指定方向发生移动,元素层级提升一级
-
absolute(:绝对定位,
- 相对于浏览器窗口的左上角来定位,会脱离文档流,不占位
-
relative:相对定位
- 相对于其原来的位置进行定位。原位置保留,占位
-
fixed:固定定位
- 相对于整个浏览器窗口来定位。元素的位置在屏幕滚动时不会改变,脱离文档流,不占位
- ⽐如回到顶部的按钮⼀般都是⽤此定位⽅式
-
static(四大忒可):默认值,没有定位
- 元素出现在正常的文档流中,会忽略 top, bottom, left, right 或者 z-index 声明,块级元素从上往下纵向排布,⾏级元素从左向右排列
-
inherit(因还尔特):规定从父元素继承position属性的值
7、flex 布局(弹性布局)
设为 Flex布局以后,⼦元素的float、clearvertical-align属性将失效。
-
容器属性(父元素)
-
flex-direction(的如埃克森) 该元素定了子元素的排列方式
row(默认值)(肉屋):从左到右row-reverse(肉屋瑞我死):从右到左column(犒劳木):从上到下column-reverse(犒劳木 瑞我死):从下到上
-
flex-wrap(挖破):放不下时是否换⾏
nowrap(默认)(诺娃破):不换⾏wrap:换⾏,第⼀⾏在上⽅wrap-reverse(挖破瑞我死):换⾏,第⼀⾏在下⽅
-
flex-flow :flex-direction属性和flex-wrap属性的简写形式
-
justify-content:设置项⽬在主轴上的对⻬⽅式( flex-direction 设置的⽅向就是主轴)
flex-start((四道特)默认值):左对⻬flex-end:右对⻬- center: 居中 (森特)
space-between(斯佩斯必吞):两端对⻬,项⽬之间的间隔都相等space-around(斯佩斯饿瑞的):项⽬两侧的间隔相等,项⽬之间的间隔⽐项⽬与边框的间隔⼤⼀倍
-
align-items 设置项⽬在交叉轴上的对⻬⽅式( flex-direction 设置的对应⽅向就是交叉轴)
flex-start:交叉轴的起点对⻬flex-end:交叉轴的终点对⻬center:交叉轴的中点对⻬baseline: (笨死莱恩)项⽬的第⼀⾏⽂字的基线对⻬stretch(死拽吃)⽬未设置⾼度或设为auto,将占满整个容器的⾼度
-
项目属性
-
order设置项目的排列顺序,数值越小,排列月靠前,默认为0; -
flex-grow(割肉):设置项目的放大比例,默认为0,即如果存在剩余空间也不放大,如果都为1,则均分; -
flex-shrink(斯瑞克): 设置项目缩小,默认为1,空间不足时项目缩小(0不收缩,1收缩) -
flex-basis(被谁死):默认为auto,计算主轴是否有多余空间 -
flex是flex-grow,flex-shrink和flex-basis简写,默认值为 0 1 auto,两个快捷值auto( 0 1 auto)|none(0 0 auto);
-
aligin-self:设置单个项目与其他项目不一样的对其方式,可覆盖aligin-item属性。默认值为auto,继承如元素的aligin-item属性,没有父元素,则等同于stretch
flex-start:交叉轴的起点对⻬flex-end:交叉轴的终点对⻬center:交叉轴的中点对⻬baseline: 项⽬的第⼀⾏⽂字的基线对⻬stretch(默认值):如果项⽬未设置⾼度或设为auto,将占满整个容器的⾼度
-
-
8、css继承属性:
不可继承的:display、margin、border、padding、background、height、width等
可继承::color、font、font-size、font-style、font-weight,text-decoration、text-transform
9、常见的标签
块级元素:
div、p、h1、h2......hn,ol、ul、dl、li、form、table
行级元素:
- a(锚点) b(加粗) i(斜体) span(常用内联容器,定义文本内区块) lable(input 元素定义标注(标记))
行内块
img 图片 input 输入框 多文本
10、水平垂直居中的5种方式
css居中
答:其实实现水平垂直剧中方法有很多:
第一种:定位:
第一种思路:通过给div设置绝对定位,并且left,right,top,bottom设置为0,margin:auto即可以水平垂直居中
第二种思路:通过给div设置绝对定位,left为50%,top为50%,再给div设置距左是自身的一半即:margin-left:自身宽度/2,margin-top:自身高度/2。
第三种思路:通过给div设置绝对定位,left为50%,top为50%,再给div设置跨左和跟上是自身的一半:transform:translate3d(-50%,-50%,0)
第四种:flex布局:
思路:有两个div,父级div和子级div,给父级div设置display:flex,并且设置父级div的水平居中justify-content:center,并且给父级div设置垂直居中align-items:center即可
11、rem的原理 媒体查询 vwvh
- rem原理: rem是指相对于根元素的字体大小的单位,即根据html元素的font-size来计算大小。
- 根元素默认的 font-size 都是 16px。
- 假如设计图是400px,转换rem就是400px/75px=5.33rem;
- 动态计算rem html.fontSize = 当前宽度/1920 * 100 为了方便计算,这里乘以100 该盒子在页面中实际表现宽高为:2rem * 1680/1920 px = 175 px,也就是 175 * 175 px
- 安装插件 cssrem 配置 px to rem
12、清除浮动:
①浮动父级元素
②在最后加一个空标签,在里边加clear:both
③BFC(BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素)
④ 超出隐藏 overflow:hidden
方法一:额外标签法
给谁清除浮动,就在其后额外添加一个空白标签 ,给其设置clear:both。
优点:通俗易懂,书写方便。
缺点:添加许多无意义的标签,结构化比较差。
方法二:父元素添加overflow:hidden
通过触发BFC方式,实现清除浮动
优点:代码简洁
缺点:内容增多的时候容易造成不会自动换行导致内容被隐藏掉,无法显示要溢出的元素。
方法三:使用after伪元素清除浮动
优点:符合闭合浮动思想,结构语义化正确。
缺点:ie6-7不支持伪元素:after,使用zoom:1触发hasLayout。
方法五:为父元素设置高度
缺点: 需要手动添加高度,如何后面的高度发生变化,还要再次修改高度,给后期的维
护带来麻烦。
优点: 简单粗暴直接有效,清除了浮动带来的影响。
13、常见的标签
块级元素:
div、p、h1、h2......hn,ol、ul、dl、li、form、table
行级元素:
- a(锚点) b(加粗) i(斜体) span(常用内联容器,定义文本内区块) lable(input 元素定义标注(标记))
行内块
img 图片 input 输入框 多文本
14、css3: 有哪些特性?
flex ,动画 ,css3选择器
CSS3实现圆角(border-radius),阴影(box-shadow),
对文字加特效(text-shadow、),线性渐变(gradient),旋转(transform)
transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);// 旋转,缩放,定位,倾斜
增加了更多的CSS选择器 多背景 rgba
在CSS3中唯一引入的伪元素是 ::selection
媒体查询,多栏布局
border-image
15.css兼容性
不同浏览器的标签默认的外补丁和内补丁不同 问题症状:随便写几个标签,不加样式控制的情况下,各自的margin 和padding差异较大。 碰到频率:100% 解决方案:CSS里 {margin:0;padding:0;} 备注:这个是最常见的也是最易解决的一个浏览器兼容性问题,几乎所有的CSS文件开头都会用通配符来设置各个标签的内外补丁是0。
标签最低高度设置min-height不兼容 问题症状:因为min-height本身就是一个不兼容的CSS属性,所以设置min-height时不能很好的被各个浏览器兼容 碰到几率:5% 解决方案:如果我们要设置一个标签的最小高度200px,需要进行的设置为:{min-height:200px; height:auto !important; height:200px; overflow:visible;} 备注:在B/S系统前端开时,有很多情况下我们又这种需求。当内容小于一个值(如300px)时。容器的高度为300px;当内容高度大于这个值时,容器高度被撑高,而不是出现滚动条。这时候我们就会面临这个兼容性问题。
设置较小高度标签(一般小于10px),在IE6,IE7,遨游中高度超出自己设置高度 问题症状:IE6、7和遨游里这个标签的高度不受控制,超出自己设置的高度 碰到频率:60% 解决方案:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。 备注:这种情况一般出现在我们设置小圆角背景的标签里。出现这个问题的原因是IE8之前的浏览器都会给标签一个最小默认的行高的高度。即使你的标签是空的,这个标签的高度还是会达到默认的行高。
上下margin重叠
描述:给上面的元素设置margin-bottom,给下面的元素设置margin-top,只能识别其中较大的那个值; 解决方案: ①、margin-top和margin-bottom 只设置其中一个值; ②、给其中一个元素再包裹一个盒子,并设置over-flow:hidden;
透明度设置,IE不识别opacity属性
- 解决方案:
- 标准写法:opacity:value;(取值范围0-1);
- 兼容IE浏览器 filter:alpha(opacity=value);(取值范围1-100);
图片默认有间隙----------
- 解决方案:
- ①、给img添加float属性;
- ②、给img添加display:block;
图片添加超链接时,在IE浏览器中会有蓝色的边框
- 解决方案:
- 给图片添加border:0或者border:none;
IE6(默认16px为最小)不识别较小高度的标签(一般为10px)
- 解决方案:
- ①、给标签添加overflow:hidden;
- ②、给标签添加font-size:0;
16css风格前缀
-moz- /* Firefox 和其他使用 Mozilla 浏览器引擎的浏览器 */
-webkit- /* Safari,Chrome 和其他使用了 Webkit 引擎的浏览器 */
-o- /* Opera */
-ms- /* IE 浏览器(但不总是 IE) */
1.Chrome(谷歌浏览器) 与 Safari(苹果浏览器) 内核:Webkit (中译无) 前缀:-webkit-
2.IE (IE浏览器) 内核:Trident (中译三叉戟) 前缀:-ms-
3.Firefox (火狐浏览器) 内核:Gecko(中译壁虎) 前缀:-moz-
4.Opera (欧朋浏览器) 内核:Presto(中译迅速) 前缀:-o-
17.css的var
:root{
--main-color:black
}
html,body{
width: 100%;
height: 100%;
background: var(--main-color,red);
}
<button id="btn">dianji</button>
btn.onclick=(e)=>{
document.querySelector('body').style.setProperty('--main-color','green')
}
18.行标签 块标签 行内块标签
一、块级标题
1、独占一行,不和其他元素待在同一行
2、可以设置宽高
常见的块级标签有:<h1-h6>、<ul>< li> 、<dl>< dd>< dt>、<p>、<div>、<table>< tr>< td>、<hr>、<fieldset> <legend>
二、行级标签
1、能和其他元素待在一行
2、不可以设置宽高
常见的行级标签:<a>、<span> 、<u>、<em>、<i>
三、行内块标签
1、能和其他元素待在一行
2、能设置宽高
常见的行内块标签:<img>、<input>、<textarea>、<select> 、<option>
CSS加载会造成阻塞吗
dom树生成 不会
阻塞dom渲染 会
阻塞js的执行 会
JS
1.原型、原型链(高频)
原型: 对象中固有的__proto__属性,该属性指向对象的prototype原型属性,prototype 就是原型,它是一个对象,我们也称它为原型对象
①所有引用类型都有一个__proto__(隐式原型)属性,属性值是一个普通的对象 ②所有函数都有一个prototype(原型)属性,属性值是一个普通的对象 ③所有引用类型的__proto__属性指向它构造函数的prototype
原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去, 原型与原型层层相链接的过程即为原型链,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的 __ _proto隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的proto****中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链
特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
原型 prototype =>函数特有的
原型链 __ptoto__ => [[prototype]]
常规的对象和数组都是没有原型的
从当前实例属性查找,找到了返回,否则顺着原型链一层一层向上找
知道null为止,如果null也没找到报错
当你试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,
那么它会去它的隐式原型 __proto__
(也就是它的构造函数的显式原型 prototype)中寻找。
2. 原型链的终点是什么?如何打印出原型链的终点?
由于Object是构造函数,原型链终点是Object.prototype.__proto__,而Object.prototype.__proto__=== null // true,所以,原型链的终点是null。原型链上的所有原型都是对象,所有的对象最终都是由Object构造的,而Object.prototype的下一级是Object.prototype.__proto__。
3、js数据类型,它们的区别?
-
JS中共有八种数据类型
- 基础:Undefined、Null、Boolean、Number、String、Symbol。
- 引用:Object,array
-
其中Symbel 和 BigInt是ES6新增的数据类型:
- Symbol 代表创建后独一无二并且不可变的数据类型,他主要是为了解决可能出现的全局变量冲突的问题;
- BigInt是一种数字类型的数据,他可以表示任意精度格式的整数,使用BigInt可以安全的存储和操作大整数,即使这个数已经超出了Number能够表示的安全整数范围。
-
这些数据可以分为
原始数据类型和引用数据类型:- 栈:基本数据类型(Undefined、Null、Boolean、Number、String)
- 堆:引用数据类型(对象、数组和函数)。
-
这两种类型的区别在于 存储位置的不同 :
- 基本数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
- 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会印象程序运行的性能;引用数据类型在栈中存储了指针,该指针执行堆中实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
4、数据类型检测的方式有哪些
(1)typeof 其中数组、对象、null都会被判断为object,其他判断都正确。
typeof可以测试出number、string、boolean、undefined及function, 对于null及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。
(2)instanceof 可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
obj instanceof Object ,可以左边放你要判断的内容,右边放类型来进行JS类型判断,只能用来判断复杂数据类型,因为instanceof 是用于检测构造函数(右边)的 prototype 属性是否出现在某个实例对象(左边)的原型链上
(3)Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:
5、 判断数组的方式有哪些
-
通过Object.prototype.toString.call()做判断 Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
-
通过原型链做判断
obj.proto === Array.prototype;
】
-
通过ES6的Array.isArray()做判断
Array.isArrray(obj);
-
通过instanceof做判断
obj instanceof Array
-
通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)
6、数组的方法
push()从后面添加元素,返回值为添加完后的数组的长度;
pop()从后面删除元素,只能是一个,返回值是删除的元素;
shift()从前面删除元素,只能是一个,返回值是删除的元素;
unshift()从前面添加元素,返回值是添加完后的数组的长度;
splice(i,n)删除从i(索引值)开始之后的那个元素,返回值是删除的元素;
split()将字符串转化为数组;
conact()连接两个数组,返回值为连接后的新数组;
sort()将数组进行排序,返回值是排好的数组,默认是按照最左边的数字进行排序,不是按照数字大小排序的;
reverse()将数组反转,返回值是反转后的数组;
slice(start,end)切去索引值start到索引值end的数组,不包含end索引的值,返回值是切出来的数组;
forEach(callback)遍历数组,无return即使有return,也不会返回任何值,并且会影响原来的数组;
map()映射数组(遍历数组),有return返回一个新数组;
filter()过滤数组,返回一个满足要求的数组
7、数组遍历方法
| 方法 | 是否改变原数组 | 特点 |
|---|---|---|
| forEach() | 否 | 数组方法,不改变原数组,没有返回值 |
| map() | 否 | 数组方法,不改变原数组,有返回值,可链式调用 |
| filter() | 否 | 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用 |
| for...of | 否 | for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 |
| every() 和 some() | 否 | 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. |
| find() 和 findIndex() | 否 | 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 |
| reduce() 和 reduceRight() | 否 | 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作 |
8、数组去重有哪些方法
一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
三、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
四、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
八、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
九、利用递归去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
9、forEach和map方法有什么区别
这方法都是用来遍历数组的,两者区别如下:
- forEach()方法会针对每一个元素执行提供的函数,对数据的操作会改变原数组,该方法没有返回值;
- map()方法不会改变原数组的值,返回一个新数组,新数组中的值为原数组调用函数处理之后的值;
10、闭包
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,创建的函数可以访问到当前函数的局部变量。
闭包有两个常用的用途;
-
闭包的第一个用途是使我们在函数外部能够访问到函数内部的变量。通过使用闭包,可以通过在外部调用闭包函数,从而在外部访问到函数内部的变量,可以使用这种方法来创建私有变量。
-
闭包的另一个用途是使已经运行结束的函数上下文中的变量对象继续留在内存中,因为闭包函数保留了这个变量对象的引用,所以这个变量对象不会被回收。
-
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
1. 函数的执行,导致函数被定义 2. 闭包和函数的定义有关 3. this指向和函数的执行方式有关 优点: 避免全局变量的污染,同时,局部变量没有被销毁,驻留在内存中,还可以被访问 缺点: 使用不当,会造成内存泄露 1、函数里面包含的子函数,子函数访问父函数的局部变量 2、通过return将子函数暴露在全局作用域,子函数就形成闭包 3、通过闭包,父函数的局部变量没有被销毁,可通过闭包去调用,但同时,这个局部变量也不会被全局变量 污染 闭包存在的意义 1.延长变量的生命周期 2.创建私有环境(变量)
11、柯里化
柯里化是把接受多个参数的函数,变换成接受一个参数的函数,并且返回的函数可以接受剩余的参数,并返回新结果。 科里化总归就是对于闭包的一种高级应用。
缺点:如果调用链特别长, 会导致内存得不到释放
用处:校验电话号码、校验邮箱、校验身份证号、校验密码等, 这时我们会封装一个通用函数 checkByRegExp ,接收两个参数,校验的正则对象和待校验的字符串
12、防抖和节流
-
防抖:
-
概念:n秒后执行该事件,若在n秒内重复触发,则重新计时;(闭包+setTimeout); -
使用场景:1.搜索框搜索输入,只需要用户最后一次输入完毕,再发送请求;
2.手机号、邮箱验证输入检测;
3.窗口大小
resize。只需要窗口调整完毕后,计算窗口大小,防止重新渲染。 -
实现方法:1.设置定时器;
2.设置一个闭包,返回一个方法;
3.如果重复进来,清空前面的定时器,再重新设置一遍。
-
-
节流:
-
概念:n秒内只运行一次,若再n秒内重复触发,只有一次执行;(setTimeout) -
使用场景:- 1.滚动加载,加载更多或者滚到底部监听;
- 2.搜索框,搜索功能。
-
实现方法:1.设置一个标记;
2.设置一个闭包,返回一个方法;
3.如果重复进去的时候,标记已经动了,那就组织程序进一步运行;
4.如果定时器执行完了,设置这个标记为没动,允许下一次执行。
高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
鼠标不断点击触发,mousedown(单位时间内只触发一次)
监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断
-
13、拷贝(深拷贝、浅拷贝)
浅拷贝:object.assign()/slice 如果拷贝源对象是基本类型,则拷贝的是值;如果拷贝源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用1戦神 复制的地址
深拷贝:对属性中所有引用类型的值,遍历到是基本类型的值为止。开辟新的内存空间
json.string(json.parse())缺点
obj里面有new Date(),深拷贝后,时间会变成字符串的形式。而不是时间对象;
obj里有RegExp、Error对象,则序列化的结果会变成空对象{};
obj里有function,undefined,则序列化的结果会把function或 undefined丢失
obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
14,JSON.parse(JSON.stringify())
- 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
- 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数
const completeDeepClone = (target) => {
// 补全代码
// undefined与null
if (target == null) return target
// Date
if (target instanceof Date) return new Date(target)
// 正则
if (target instanceof RegExp) return new RegExp(target)
// Error
if (target instanceof Error) return new Error(target.message)
// 函数
if (target instanceof Function) return function proxy(...args) {
return target.call(this, args)
}
// Symbol Number String Boolean
if (typeof target !== 'object') return target
// 处理过undefined与null,该处就可直接创建新对象了
let newObj = new target.constructor()
for (let key in target) {
if (target.hasOwnProperty(key)) {
// 只剩对象,数组,Set,Map类型,递归引用类型的key value即可
newObj[key] = completeDeepClone(target[key])
}
}
return newObj
}
三、优缺点 浅拷贝: object.assign()/slice 优点:存取速度比堆快,仅次于直接位于CPU中的寄存器,数据可以共享; 缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。 深拷贝: 堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(参数传递)。创建对象是为了反复利用,这个对象将被保存到运行时数据区。
-
手写递归方法
- 递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
15、作用域和作用域链
答:JS作用域也就是JS识别变量的范围,作用域链也就是JS查找变量的顺序
先说作用域,JS作用域主要包括全局作用域、局部作用域和ES6的块级作用域
全局作用域:也就是定义在window下的变量范围,在任何地方都可以访问,
局部作用域:是只在函数内部定义的变量范围
块级作用域:简单来说用let和const在任意的代码块中定义的变量都认为是块级作用域中的变量,例如在for循环中用let定义的变量,在if语句中用let定义的变量等等
注:尽量不要使用全局变量,因为容易导致全局的污染,命名冲突,对bug查找不利。
2️而所谓的作用域链就是由最内部的作用域往最外部,查找变量的过程.形成的链条就是作用域链
原型链往外层找,找到null
作用域链,从找到的地方外外层,一直找到window
如果该函数中没有这个变量,那么这次搜索的过程会随着代码执行环境创建的作用域链往外层逐层搜索,一直搜索到window对象为止,找不到就会抛出一个未定义的错误,而这种从内到外逐层查找的关系在js中我们称为作用域链
16.事件冒泡、捕获(委托)
事件捕获
事件捕获(event capturing)。与事件冒泡相反,事件会从最外层开始发生,直到最具体的元素。
上面的例子在事件捕获的概念下发生click事件的顺序应该是
document -> html -> body -> div -> p
在捕获的过程中,最外层(根)元素的事件先被触发,然后依次向内执行,直到触发最里面的元素(事件源)
- 事件冒泡指在在一个对象上触发某类事件,如果此对象绑定了事件,就会触发事件,如果没有,就会向这个对象的父级对象传播,最终父级对象触发了事件。p -> div -> body -> html -> document
- 事件委托本质上是利用了浏览器事件冒泡的机制。因为事件在冒泡过程中会上传到父节点,并且父节点可以通过事件对象获取到目标节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方式称为事件代理。
event.stopPropagation() 或者 ie下的方法 event.cancelBubble = true; //阻止事件冒泡
17.事件轮询机制微任务宏任务
JS是单线程,自上而下执行,遇到同步任务直接执行,遇到微任务添加到微任务队列,遇到宏任务添加在宏任务队列,等同步任务执行完毕,执行微任务中的任务,然后再执行宏任务中的队列。微任务: (promise、catch、then)宏任务:(setTimeout、setInterval)
微任务: (promise、catch、then)
宏任务:(setTimeout、setInterval)
微观任务队列 微任务:语言标准(ECMAScript)提供,如process.nextTick(node)、Promise、Object.observe、MutationObserver
宏观任务队列 宏任务:由宿主环境提供,比如setTimeout、setInterval、网络请求Ajax、用户I/O、script(整体代码)、UI rendering、setImmediate(node)
先执行微任务 宏任务拿出来 先执行微任务 微任务执行完之后再执行宏任务
执行完微任务之后 执行宏任务 先进先出
18、本地存储
- cookie:一般不超过4K(因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识)判断用户是否登录过网站,以便实现下次自动登录或记住密码;保存事件信息等
- storage存储为5M,仅在当前浏览器窗口关闭之前有效,关闭页面或者浏览器会被清除,sessionStorage:敏感账号一次性登录;单页面用的较多(sessionStorage 可以保证打开页面时 sessionStorage 的数据为空。
- 永久有效,窗口或者浏览器关闭也会一直保存,除非手动永久清除,因此用作持久数据,localStorage:常用于长期登录(判断用户是否已登录),适合长期保存在本地的数据。
- cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通 常经过加密),cookie还可以设置有效时间 cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间 来回传递, 每次ajax请求都会吧cookie传送到后台,cookie一半用做用户登陆,后台可以根据 cookie信息判断用户是否登陆状态 。
- sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
- 区别在于 存储大小: cookie数据大小不能超过4k。
- sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得 多,可以达到5M或更大。 有期时间: localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
- sessionStorage 数据在当前浏览器窗口关闭后自动删除。
- cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
19、for in、for of、forEach的区别
for in: 循环遍历的值都是数据结构的值,for in可以循环数组,但是更适合遍历对象;
for of: 是ES6中的新增语法,数组对象都可以遍历,遍历对象需要通过Object.keys();
forEach :对数组的每一个元素执行一次提供的函数(不能使用return、break等进行中断循环),不改变原数组,没有返回值undefined。
20、js继承
-
原型链继承
原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针
-
构造函数继承(借助 call)
父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
相比原型链继承方式,父类的引用属性不会被共享,只能继承父类的实例属性和方法,不能继承原型属性或者方法
-
组合继承
组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。
-
原型式继承
这里主要借助Object.create方法实现普通对象的继承,这种继承方式的缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
-
寄生式继承
寄生式继承在原型式继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法,缺点也很明显,跟原型式继承一样
-
寄生组合式继承
寄生组合式继承,借助解决普通对象的继承问题的Object.create 方法,在几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式
21、this指向
-
普通函数调用,此时 this 指向 window
-
构造函数调用, 此时 this 指向 实例对象
-
对象方法调用, 此时 this 指向 该方法所属的对象
-
通过事件绑定的方法, 此时 this 指向 绑定事件的对象
-
箭头函数没有自己的this,它的this是继承来的
call
第一各参数是this指向,后面可以添加多个参数,函数立即执行
var foo = {
count: 1
};
function bar() {
console.log(this.count);
}
bar.myCall(foo); // 1
Function.prototype.myCall = function(context) {
// 取得传入的对象(执行上下文),比如上文的foo对象,这里的context就相当于上文的foo
// 不传第一个参数,默认是window,
var context = context || window;
// 给context添加一个属性,这时的this指向调用myCall的函数,比如上文的bar函数
context.fn = this;//这里的context.fn就相当于上文的bar函数
// 通过展开运算符和解构赋值取出context后面的参数,上文的例子没有传入参数列表
var args = [...arguments].slice(1);
// 执行函数(相当于上文的bar(...args))
var result = context.fn(...args);
// 删除函数
delete context.fn;
return result;
};
apply
第一各参数是this指向,第二个是数组,数组内部是参数,函数立即执行
var foo = {
count: 1
};
function bar() {
console.log(this.count);
}
bar.myApply(foo); // 1
--------------------------------------------------------------------
Function.prototype.myApply = function(context) {
var context = context || window;
context.fn = this;
var result;
// 判断第二个参数是否存在,也就是context后面有没有一个数组
// 如果存在,则需要展开第二个参数
if (arguments[1]) {
result = context.fn(...arguments[1]);
} else {
result = context.fn();
}
delete context.fn;
return result;
}
bind
第一各参数是this指向,后面可以添加多个参数,返回一个新的函数还需要进行调用
call和apply都是改变上下文中的this并立即执行这个函数
bind方法可以让对应的函数想什么时候调就什么时候调用,并且可以将参数在执行的时候添加,这是它们的区别
call()、bind()、apply()的用法,改变this的指向,区别在于
f.call(obj, arg1, arg2…),
f.bind(obj, arg1, arg2,…)(),
f.apply(obj, [arg1, arg2, .])
const a = {
name: 'test'
}
function say() {
console.log(this.name);
}
Function.prototype.myBind = function (oThis) {
if (typeof this !== 'function') {
return;
}
var self = this
var args=[...arguments]
return function () {
console.log(args);
return self.apply(oThis,[...args]);
}
}
say.myBind(a,2)()
22、new操作符干了那些事情
- 创建了一个新的对象
- 将构造函数的作用域赋值给新的对象,
- 执行构造函数中的代码
- 返回新的对象
1.先创建一个空的对象{}
2.将{}.`__proto__`指向构造函数.`prototype`
3.将构造函数中的this指向这个创建的对象
4.如果构造函数没有返回值或返回值是基本数据类型,则将这个新创建的对象返回
5.如果构造函数的返回值是引用数据类型,则将这个对象返回。
23、重绘和回流(重排)的区别和关系?
- 重绘:当渲染树中的元素外观(如:颜色)发生改变,不影响布局时,产生 《重绘》
- 回流:当渲染树中的元素的布局(如:尺寸、位置、隐藏/状态状态)发生改变 时,产生重绘回流
如何最小化重绘(repaint)和回流(reflow)?
- 需要要对元素进行复杂的操作时,可以先隐藏(display:"none"),操作完成后再 显示
24、事件循环机制
-
所有的任务队列可以分为
-
同步任务
- 同步任务会直接放入到主线程的执行栈进行执行
-
异步任务
- 异步任务不进入执行栈,而是进入任务队列中
-
执行顺序
- 同步任务按顺序执行,执行完毕后,才会去读取队列中的可执行的异步任务,并将可执行的异步任务依次加入到执行栈中执行。如此反复循环
25.兼容性问题
1.ES6语法 问题:IE11 不支持箭头函数、class 语法等(报 SCRIPT1002: 语法错误),不支持 Set 和 Map 数据结构(不报错)及 Promise 对象,支持 let 和 const,IE10 及以下不支持任何 ES6 语法。
解决:如果要兼容IE浏览器的项目请使用 ES5 语法或者使用 Babel 进行转换。
2.操作 tr 标签 问题:IE9 及 IE9 以下版本浏览器,不能操作 tr 标签的 innerHTML 属性。
解决:可以操作 td 标签的 innerHTML 属性。
3.Ajax 问题:IE9 及 IE9 以下版本浏览器无法使用 Ajax 获取接口数据。
解决:在使用 Ajax 请求之前设置 jQuery.support.cors=true。
4.event 对象的 srcElement 属性 问题:IE8 及 IE8 以下版本浏览器 event 对象只有 srcElement 属性,没有 target 属性。
解决:obj = event.target?event.target:event.srcElement。
5.DOM 事件绑定 问题:IE8 及 IE8 以下版本浏览器是用 attachEvent() 方法,而其他浏览器是 addEventListener() 方法。
解决:判断 IE 浏览器版本,如果是 IE8 及以下 事件绑定则使用 attachEvent() 方法,注意 attachEvent() 方法的用法,第一个参数为“onclick” 而不是“click”。并且没有第三个参数。
6.获取键盘的按键
IE8以及以下版本不兼容which
var x = event.which || event.keyCode || var x = e.which || e.keyCode
26.observer
IntersectionObserver 类型约束
用 IntersectionObserver。
IntersectionObserver 可以监听一个元素和可视区域相交部分的比例,然后在可视比例达到某个阈值的时候触发回调。
const intersectionObserver = new IntersectionObserver(
function (entries) {
entries.forEach(item => {
console.log(item.target.children[0].dataset.img, item.intersectionRatio)
if(item.intersectionRatio>0&&item.intersectionRatio<=1){
item.target.children[0].src= item.target.children[0].dataset.img
}else{
item.target.children[0].src=''
}
})
}, {
threshold: [0, 1]
});
let a = [...both.current.children]
a.forEach((item) => {
intersectionObserver.observe(item)
})
元素 在可视范围达到一半(0)和全部(1)的时候分别触发了回调
mutationObserver
MutationObserver 可以监听对元素的属性的修改、对它的子节点的增删改。
文章水印被人通过 devtools 去掉了,那么就可以通过 MutationObserver 监听这个变化,然后重新加上,让水印去不掉。
当然,还有很多别的用途,这里只是介绍功能。
除了监听元素的可见性、属性和子节点的变化,还可以监听大小变化:
<div id="box"><button>光</button></div>
const mutationObserver = new MutationObserver((mutationsList) => {
console.log(mutationsList)
});
mutationObserver.observe(box, {
attributes: true, style改变时
childList: true 子节点改变
});
ResizeObserver
元素可以用 ResizeObserver 监听大小的改变,当 width、height 被修改时会触发回调。
可以拿到元素和它的位置、尺寸。
这样我们就实现了对元素的 resize 的监听。
除了元素的大小、可见性、属性子节点等变化的监听外,还支持对 performance 录制行为的监听
const box = document.querySelector('#box');
setTimeout(() => {
box.style.width = '200px';
}, 3000);
const resizeObserver = new ResizeObserver(entries => {
console.log('当前大小', entries)
});
resizeObserver.observe(box);
27.Bolb对象(二进制流)
bas64转bolb
var blob = new Blob([a], { a是bas64
type: 'image/png' 类型
})
Bolb转bas64
let reader = new FileReader();
let imgResult = "";
reader.readAsDataURL(file);
reader.onload = function () {
console.log(reader.result);
};
28.语音
let speech = new SpeechSynthesisUtterance()
speech.text = '测试文字'
speech.pitch = 1 // 设置话语的音调(0-2 默认1,值越大越尖锐,越低越低沉)
speech.rate = 0.9 // 设置说话的速度(0.1-10 默认1,值越大语速越快,越小语速越慢)
speech.volume = 10 // 设置说话的音量
speech.lang = 'zh-CN' // 设置播放语言
speechSynthesis.speak(speech)
谷歌需要放在事件中
29.箭头函数和普通函数
箭头函数是匿名函数,不能作为构造函数,不能使用new
箭头函数不绑定arguments,取而代之用rest参数...解决
箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
箭头函数没有原型属性
- 箭头函数的 this 永远指向其上下文的 this ,任何方法都改变不了其指向,如 call() ,bind() , apply()
- 普通函数的this指向调用它的那个对象
30.axios
//添加一个请求拦截器
axios.interceptors.request.use(config=>{
//在请求之前做一些事
return config;
},err=>{
//请求错误的时候做一些事
return Promise.reject(err);
});
//添加一个响应拦截器
axios.interceptors.response.use(response=>{
//对返回的数据做一些处理
reutrn response;
},err=>{
//对返回的错误做一些处理
return Promise.reject(err);
});
//移除拦截器
const myInterceptor = axios.interceptors.request.use(config=>{return cofig})
axios.interceptors.request.eject(myInterceptor);
//在一个axios实例中使用拦截器
var instance = axios.create();
instance.interceptors.request.use(function(){/*...*/});
31.AJAX
const xhr=new XMLHttpReqest()
超时设置
xhr.timeout=2000
超时回调
xhr.ontimeout=function(){}
网络异常
xhr.onerror=function(){}
xhr.open('GET',url)
xhr.send()
xhr.onreadystatechange=function(){
if(xhr.readyState===4){
if(xhr.status>=200&&xhr.status<=300){
console.og(xhr.response)
}
}
}
取消AJAX请求
xhr.abort()
重复请求取消
let isSending=false
if(isSending) xhr.abort() 有没请求有取消没有发送请求 xhr.send()
isSending=true
if(xhr.readyState===4){
isSending=false
}
32.js代码兼容解决
babel-loader @babel/core @babel/preset-env
es6 转 es5
缺点:只能处理基本的js兼容性问题,Promise等高级语法就不能转换
Promise coress.js解决
33.js判断promise
function isPromise(obj) {
return obj //有实际含义的变量才执行方法,变量null,undefined和''空串都为false
&& (typeof obj === 'object' || typeof obj === 'function') // 初始promise 或 promise.then返回的
&& typeof obj.then === 'function';
}
requestAnimationFrame
requestAnimationFrame 比起 setTimeout、setInterval的优势主要有两点: 1、requestAnimationFrame 会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率,一般来说,这个频率为每秒60帧。 2、在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的的cpu,gpu和内存使用量。
var e = document.getElementById("e");
var flag = true;
var left = 0;
//当前执行时间
var nowTime = 0;
//记录每次动画执行结束的时间
var lastTime = Date.now();
//我们自己定义的动画时间差值
var diffTime = 40;
function render() {
if(flag == true){
if(left>=100){
flag = false
}
e.style.left = ` ${left++}px`
}else{
if(left<=0){
flag = true
}
e.style.left = ` ${left--}px`
}
}
//requestAnimationFrame效果
(function animloop() {
//记录当前时间
nowTime = Date.now()
// 当前时间-上次执行时间如果大于diffTime,那么执行动画,并更新上次执行时间
if(nowTime-lastTime > diffTime){
lastTime = nowTime
render();
}
requestAnimationFrame(animloop);
//如果left等于50 停止动画
if(left == 50){
cancelAnimationFrame(rafId)
}
})()
34.面向对象和面向过程
面向对象 ( Object Oriented ) 是将现实问题构建关系,然后抽象成 类 ( class ) ,给类定义属性和方法后,再将类实例化成 实例 ( instance ) ,通过访问实例的属性和调用方法来进行使用。简写为:OOP
面向过程(Procedure Oriented)是一种以过程为中心的编程思想。 这些都是以什么正在发生为主要目标进行编程,不同于面向对象的是谁在受影响。 与面向对象明显的不同就是封装、继承、类。简写为POP。
35.清除字符串空格
let str=' adasd '
console.log(str);
console.log(str.trim()); 清除前后空格
console.log(str.trimStart()); 清除前空格
console.log(str.trimEnd()); 清除后空格
36.Object.entries()
const school={
name:123,
vueke:['前端','java','大数据']
}
Object.entries(school) [[name,132],[vueke,['前端','java','大数据']]]
37.Object.fromEntries
const result=Object.fromEntries([ ['name','张三'],
['age','132,456,789']
])
console.log(result); age: "132,456,789",name: "张三"
const m=new Map()
m.set('name','张三')
const result=Object.fromEntries(m)
console.log(result); {name: '张三'}
const arr=Object.entries({
name:'张三'
})
console.log(arr); ['name', '张三']
38.JS执行顺序
1.script主代码块放入执行栈,从上到下顺序执行
2.遇到宏任务,加入宏任务队列(如果是setTimeout,则到时间后加入)
3.遇到微任务,加入微任务队列
4.主代码执行完毕后,清空微任务队列
5.执行UI渲染(并不是每次都渲染,和浏览器都优化机制有关)
6.从宏任务队列中拿出一个任务放入执行栈执行
宏任务微任务
宏任务 setTimout ajax dom事件 在dom渲染之后触发
微任务 promise async/awit 在dom渲染之前触发
微任务比宏任务先执行
同步任务
微任务
dom渲染
宏任务
diff算法(key值的作用)
1.虚拟dom中key的作用
1.简单来说:key之虚拟dom对象的标识,在更新显示时key起着极其重要的作用
2.详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,
随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变,直接使用之前的真实DOM
(2).若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b.旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
2.川index作为key可能会引发的问题:
1。若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
2.如果结构中还包含输入类的DOM:
会产生错误DOM更新==>界面有问题。
3,注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
仅用于渲染列表用于展示,使用index作为key是没有问题的。
3,开发中如何选择key? :
1.最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
2.如果确定只是简单的展示数据,用index也是可以的。
get和post区别
1、GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。
URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码
之后再传输
POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。
上面的item=bandsaw就是实际的传输数据。
因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。
2、传输数据的大小
在HTTP规范中,没有对URL的长度和传输的数据大小进行限制。
但是在实际开发过程中,对于GET,特定的浏览器和服务器对URL的长度有限制。
因此,在使用GET请求时,传输数据会受到URL长度的限制。
对于POST,由于不是URL传值,理论上是不会受限制的,
但是实际上各个服务器会规定对POST提交数据大小进行限制,Apache、IIS都有各自的配置。
3、安全性
POST的安全性比GET的高。
这里的安全是指真正的安全,而不同于上面GET提到的安全方法中的安全,
上面提到的安全仅仅是不修改服务器的数据。比如,在进行登录操作,通过GET请求,
用户名和密码都会暴露再URL上,因为登录页面有可能被浏览器缓存以及其他人
查看浏览器的历史记录的原因,此时的用户名和密码就很容易被他人拿到了。
除此之外,GET请求提交的数据还可能会造成Cross-site request frogery攻击
get和psot使用场景
1、get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
2、在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;区别表现如下:
1. get是从服务器上获取数据,post是向服务器传送数据。
构造函数和普通函数的区别
调用方式的不同
1. 构造函数需要使用new运算符调用, 如果构造函数没有参数可以省略小括号,
比如new Object;
2. 普通函数的调用不需要new 运算符, 而且必须有小括号。比如: function(){};
this的指向问题
1. 构造函数的this会绑定到创建的对象实例上;
2. 普通函数的this则属于此函数的调用者;
命名方式
1. 构造函数名称通常首字母要大些;
1. 普通函数名称首字母要小写, 使用驼峰命名方式
图片预加载与懒加载
预加载实现思路
预加载是预先加载好后面需要用到的资源, 后面使用的时候直接去缓存里取。
举个栗子, 比如一个网站的开场动画, 这些动画是由很多图片组成的,
假如不预先加载好, 那就会造成动画不流畅产生闪动白屏。
图片是提高用户体验的一个很好方法。图片预先加载到浏览器中,
保证了图片快速、无缝地发布,使用户在浏览你网站内容时获得更好的用户体验。
实现预载的方法非常多,可以用CSS(background)、JS(Image)、HTML(<img />)都可以。
常用的是new Image();,设置其src来实现预载,再使用onload方法回调预载完成事件。
只要浏览器把图片下载到本地,同样的src就会使用缓存,这是最基本也是最实用的预载方
法。当Image下载完图片头后,会得到宽和高,因此可以在预载前得到图片的大小(我所知
的方法是用记时器轮循宽高变化)。一般实现预载的工具类,都实现一个Array来存需要预
载的URL,然后实现Finish、Error、SizeChange等常用事件,可以由用户选择是顺序预载
或假并发预载。Jquery的PreLoad可以用于预载。
图片懒加载
延迟加载图片或符合某些条件时才加载某些图片。
这样做的好处是减少不必要的访问数据库或延迟访问数据库的次数,
因为每次访问数据库都是比较耗时的即只有真正使用该对象的数据时才会创建。
懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
图片预加载与懒加载的区别:
第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟,
如果用户在加载前就离开了页面,那么就不会加载。
第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,
一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。
两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预载则会增加服务器前端压力。
ES6
es6新特性
-
答:ES6新增特性常用的主要有:let/const,箭头函数,模板字符串,解构赋值,模块的导入(import)和导出(export default/export),Promise,还有一些数组字符串的新方法,其实有很多,我平时常用的就这些
-
let、const、var的区别
(1)块级作用域 (2)变量提升 (3)重复声明 (4)暂时性死区
| 区别 | var | let | const |
|---|---|---|---|
| 是否有块级作用域 | × | ✔️ | ✔️ |
| 是否存在变量提升 | ✔️ | × | × |
| 是否添加全局属性 | ✔️ | × | × |
| 能否重复声明变量 | ✔️ | × | × |
| 是否存在暂时性死区 | × | ✔️ | ✔️ |
| 是否必须设置初始值 | × | × | ✔️ |
| 能否改变指针指向 | ✔️ | ✔️ | × |
-
数组的解构和赋值
-
展开运算符(...)
-
箭头函数与普通函数的区别一.外形不同:箭头函数使用箭头定义,普通函数中没有 二.箭头函数都是匿名函数 三.箭头函数不能用于构造函数,不能使用new 四.箭头函数中this的指向不同
-
数组静态方法:
- Array.from()把接收到的参数准换成数组,仅仅能转换类数组(集合,字符串),如果不是类数组,返回[]
- Array.isArray()一般情况我们判断数据类型使用的是typeOf 但是判断[],{},null的时候返回值都是object
-
数组实例方法:
方法 是否改变原数组 参数 特点 forEach() 否 值,下标,原数组 数组方法,不改变原数组,没有返回值 map() 否 值,下标,原数组 数组方法,不改变原数组,有返回值,可链式调用 filter() 否 数组方法,过滤数组,返回包含符合条件的元素的数组,可链式调用 for...of 否 for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 every() 和 some() 否 数组方法,some()只要有一个是true,便返回true;而every()只要有一个是false,便返回false. find() 和 findIndex() 否 数组方法,find()返回的是第一个符合条件的值;findIndex()返回的是第一个返回条件的值的索引值 reduce() 和 reduceRight() 否 数组方法,reduce()对数组正序操作;reduceRight()对数组逆序操作 -
对象静态方法:
- Object.assign:对象的合并(浅拷贝)
- Object.is()跟比较运算符 === 的效果一样,仅仅修复了比较NaN与NaN的行为
- Object.keys()用来遍历对象,该方法接收一个对象,返回值又该对象的key组成的数组
- Object.values()用来遍历对象,该方法接收一个对象,返回值又该对象的value组成的数组
2、Promise
使用promise能够有效的解决js异步回调地狱问题
promise有三种状态:pending,fulfilled,rejected。pending代表等待的状态,在此状态下,可能执行resolve()的方法,也可能执行reject()方法,fulfilld代表成功态,此状态下执行resolve()方法,rejected代表失败态,此状态下执行reject()方法,一旦成功了就不能失败,反过来也是一样
.all
promise.all可以把多个Promise实例包装成一个Promise实例- 接收一个数组作为参数
promise.all([p1,p2,p3]),返回值组成数组 - 全部成功才会成功,只要有一个失败就直接报错失败
场景 页面请求接口较多,做接口限制,上传加载
- then(链式调用);
- resolve: 快速获取一个成功状态的promise对象
- reject: 快速获取一个拒绝状态的promise对象
- race: 是将多个promise实例包装成一个实例第一个完成的 promise 的结果状态就是最终的结果状态
- .catch: promise错误是调用
.then(null, rejection)或.then(undefined, rejection)的别名,
用来捕获错误Promise 内部发生的错误(如果不catch会吞错)
catch()方法返回的还是一个 Promise 对象,因此后面还可以接着调用then()方法。
如果没有错误,会跳过catch,继续then,如果后面的then报错,与前面的catch无关
catch还可以捕获前一个catch的报错
Promise在项目中的实际使用
我们请求接口的时候,用到axios,它就是基于promise对AJAX的封装,里面return了一个promise实例,
请求成功之后,将返回结果传递给了resolve()
通过 cath捕获错误,返回一些code码
3、call,bind,applay 区别
-
1)、bind会产生新的函数,(把对象和函数绑定死后,产生新的函数)
-
2)、call和apply不会产生新的函数,只是在调用时,绑定一下而已。
-
3)、call和apply的区别,第一个参数都是要绑定的this,apply第二个参数是数组(是函数的所有参数),call把apply的第二个参数单列出来
-
.bind() .call() 和 .apply()在说区别之前还是先总结一下三者的相似之处:
1、都是用来改变函数的 this 对象的指向的。
2、第一个参数都是 this 要指向的对象。
3、都可以利用后续参数传参。
关于 call() 和 apply() 我的理解:
它们的作用是: 让函数在某个指定的对象下执行。
call() 和 apply()的第一个参数相同,就是指定的对象。这个对象就是该函数的执行上下文。
call()和apply()的区别就在于,两者之间的参数。
call()在第一个参数之后的 后续所有参数就是传入该函数的值。
apply() 只有两个 参数,第一个是对象,第二个是数组,这个数组就是该函数的参数。
bind() 方法和前两者不同在于: bind() 方法会返回执行上下文被改变的函数而不会立即执行,而前两者是直接执行该函数。他的参数和 call()相同。
bind 返回的仍然是一个函数,所以我们还可以在调用的时候再进行传参
4、promise all和race的区别:
all将多个实例组装成一个新的实例,成功的时候返回一个成功数组,失败的时候则返回最先被reject失败状态的值
Promise.race([p1, p2, p3])里面的结果哪个获取的快,就返回哪个结果,不管结果本身是成功还是失败
5、async await 和 promise的区别:
async await(语法糖generator)
① Promise是ES6,而async是ES7
③ Promise链式操作,自己catch异常。async则要在函数内catch,好在现在catch成本较低 ④ Promise有很多并行神器,比如Promise.all\Promise.race等。这些是async没法搞定的 ⑤ Promise是显式的异步,而 Async/await 让你的代码看起来是同步的,你依然需要注意异步 ⑥promise编写代码更复杂且可读性也稍差 ⑦ promise和async/await都是用来处理异步操作的
6、this指向
es5==》看调用
- 函数名() ===> window
- 定时器样式调用 ===》 window
- 函数作为事件处理函数 ===》 window
- 函数作为对象方法 ===》最后谁调用指向谁
- 函数作为数组元素 ===》 当前数组
- new ===》当前类的实例
-
普通函数中的this :
- 默认情况下没有直接调用,this指向window;
- 严格模式下,this指向undefined;
- 当使用call、apply、bind绑定的时候,this指向绑定对象。
-
箭头函数的this :
- 不需要function关键字来创建函数;
- 省略return关键字;
- 继承当前上下文的this关键字;
- 箭头函数和普通函数不同的是,箭头函数的this被强行绑定到上下文中,上下文中的this是什么箭头函数的this就是什么。
7、MVVM和MVC有什么区别
MVC
MVC是一种设计模式:
M(Model):模型层。是应用程序中用于处理应用程序数据逻辑的部分,模型对象负责在数据库中存取数据; V(View):视图层。是应用程序中处理数据显示的部分,视图是依据模型数据创建的; C(Controller):控制层。是应用程序中处理用户交互的部分,控制器接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。
MVVM
vue框架中MVVM的M就是后端的数据,V就是节点树,VM就是new出来的那个Vue({})对象
M(Model):模型层。就是业务逻辑相关的数据对象,通常从数据库映射而来,我们可以说是与数据库对应的model。 V(View):视图层。就是展现出来的用户界面。 VM(ViewModel):视图模型层。连接view和model的桥梁。因为,Model层中的数据往往是不能直接跟View中的控件一一对应上的,所以,需要再定义一个数据对象专门对应view上的控件。而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象。
MVVM的优势
1、mvc和mvvm都是一种设计思想。 主要就是mvc中Controller演变成mvvm中的viewModel。 mvvm主要解决了mvc中大量DOM操作使页面渲染性能降低,加载速度变慢的问题 。
2、MVVM与MVC最大的区别就是:它实现了View和Model的自动同步:当Model的属性改变时,我们不用再自己手动操作Dom元素来改变View的显示,它会自动变化。
3、整体看来,MVVM比MVC精简很多,我们不用再用选择器频繁地操作DOM
8.Symbol
1.Symbol的只是唯一的
2.Symbol值不能与其他数据进行运算
3.Symbol定义的对象属性不能使用for ... in循环遍历,但是可以使用Reaflect.ownKeys来获取对象的所有键名
创建方式
let s = Symbol()
console.log(s, typeof s);//Symbol() 'symbol'
let s2 = Symbol('你好')
console.log(s2, typeof s2);//Symbol(你好) 'symbol'
let s3 = Symbol('我不好')
console.log(s3 == s2); //false
let s4 = Symbol.for('字符串')
let s5 = Symbol.for('字符串')
console.log(s4, typeof s4); Symbol(字符串) 'symbol'
console.log(s4 == s5);//true
Symbol添加对象内置函数
let game = {
name: '俄罗斯方块',
};
let methods = {
up: Symbol(),
down: Symbol()
};
game[methods.up] = function () {
console.log("改变形状");
}
game[methods.down] = function () {
console.log("快速下降");
}
game[methods.down](); //调用成功
let youxi = {
name:'狼人杀',
[Symbol("say")]:function () {
console.log("发言")
},
[Symbol('zibao')]:function () {
console.log("我自爆狼身份")
}
}
// 方法一
const langrensha = Object.getOwnPropertySymbols(youxi);
console.log(langrensha)
youxi[langrensha[0]](); //调用成功
// 方法二
const langrensha2 = Reflect.ownKeys(youxi);
console.log(langrensha2);
youxi[langrensha2[2]](); //调用成功
Symbol的一些方法
const arr=[1,2,3]
const arr2=[7,8,9]
arr2[Symbol.isConcatSpreadable]=false //控制是否展开
console.log(arr.concat(arr2));
自己控制类型检测结果
class Person{
static[Symbol.hasInstance](param){
自己控制类型检测结果
console.log(param);
console.log('我被用来检测类型');
}
}
let o=[]
console.log(o instanceof Person);
9.迭代器
let arr=[1,2,3]
let interator=arr[Symbol.iterator]()
console.log(interator.next());{value: 1, done: false}
console.log(interator.next());{value: 2, done: false}
console.log(interator.next());{value: 3, done: false}
console.log(interator.next());{value: undefined, done:true}
let arr=[1,2,3]
let interator=arr[Symbol.iterator]()
console.log(interator.next());{value: 1, done: false}
console.log(interator.next());{value: 2, done: false}
console.log(interator.next());{value: 3, done: false}
console.log(interator.next());{value: undefined, done: true}
自定义
const banji = {
name: '张三',
stus: [1, 2, 3, 4, 5],
[Symbol.iterator]() {
//索引变量
let index = 0
let that = this
return {
next: function () {
if (index < that.stus.length) {
const result = { value: that.stus[index], done: false }
index++
return result
} else {
return { value: undefined, done: true }
}
}
}
}
}
for (let v of banji) {
console.log(v);
}
生成器
生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
生成器函数其实就是一个特殊的函数
VUE
多页面的开发,单页面的开发
多页面的开发:
弊端:不能实现局部dom元素的更新,只能实现全部(dom对象)更新(切换页面)
好处:页面独立,dom少(dom渲染的时候速度和效率最高)
元素冗余
单页面的开发:
弊端:有dom操作,维护不易
好处:dom都在一个页面内,开发的时候容易(轻松),需要通过js的操作实现dom切换(行为逻辑)
1.刷新方式
SPA:相关组件切换,页面局部刷新或更改
MPA:整页刷新
2.路由模式
SPA:可以使用hash,也可以使用history
MPA:普通链接跳转
3.用户体验
SPA:页面片段间时间的切换快,用户体验良好,当初次加载文件过多时,需要做相关调优。
MPA:页面切换加载缓慢,流畅度不够,用户体验比较差,尤其网速慢的时候
4.转场动画
SPA:容易实现转场动画
MPA:无法实现转场动画
5.数据传递
SPA:容易实现数据传递,方法有很多(通过路由带参数传值,Vuex传值等等)
MPA:依赖url传参,cookie,本地存储
6.搜索引擎优化(SEO)
SPA:需要单独方案,实现较为困难,不利于SEO检索,可利用服务器端渲染(SSR)优化
MPA:实现方法容易
7.使用范围
SPA:高要求的体验度,追求界面流畅的应用
MPA:适用于追求高度支持搜索引擎的应用
8.开发成本
SPA:较高,长需要借助专业的框架
MPA:较低,但也页面代码重复的多
9.维护成本
SPA:相对容易
MPA:相对复杂
10.结构
SPA:一个主页面+许多模块的组件
MPA:许多完整的页面
11.资源文件
SPA:组件公用的资源只需要加载一次
MPA:每个页面都需要自己加载公用的资源
1.vue和react区别:
不同点:1.vue使用模板语法,而react使用JSX语法
2.vue和react对数据的管理上有区别
3.渲染方式上,vue会追踪每一个依赖的变化,只更新变化的组件树,而react会重新渲染根组件
4.组合不同功能的方式 Vue:通过mixin React:通过HoC(高阶组件)
5.Vue:MVVM模式 React:严格上只针对MVC的view层
6.数据流向不同,react从诞生开始就推崇单向数据流,而vue是双向数据流
7.组件化通信的不同。react中我们通过使用回调函数来进行通信的,而Vue中子组件向父组件传递消息有两种方式:事件和回调函数
8.diff算法不同。react主要使用diff队列保存需要更新哪些DOM,得到patch树,再统一操作批量更新DOM。Vue 使用双向指针,边对比,边更新DOM
相同点:1.利用虚拟dom实现快速渲染 2. 组件化 3.都是数据驱动视图 4.都有管理状态,React有redux,Vue有自己的Vuex(自适应vue,量身定做)5.都有自己的构建工具:vue的vue-cli 6.都支持服务器端渲染
2、Vue指令
v-if(条件渲染):根据是否买足条件来决定是否对相应的dom、vue的自定义单文件组件(xxx.vue,后期以.vue代替单文件组件)进行渲染,已渲染的dom、.vue则决定是否删除
配合使用的v-else,v-else-if
v-show(条件展示):根据是否满足条件来决定相对应的dom、.vue的display的值
v-bind(绑定属性):可以将dom、.vue中的属性(attribute)与vue实例中的property保持一致(简写“:”)
v-for(循环):使dom、.vue直接在html页面上循环
v-on(添加事件监听器,简单理解为绑定事件):为dom、.vue添加一个事件监听器,通过此监听器可以跳用vue实例中的方法(简写“@”)
v-model(双向数据绑定):本身是指令,实际为v-bind:value和v-on:change的语法糖
v-text(设置标签的文本)
v-html(设置标签的innerHTML)
v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始 Mustache 标签。跳过大量没有指令的节点会加快编译。
v-once:只渲染元素和组件一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能
3、vue生命周期的理解?
数据初始化:
beforeCreate:
在实例初始化之后,数据观测和event/watcher事件配置之前被调用;
created:
实例已经创建完成之后被调用,数据观测,属性和方法的运算,watch/event事件回调。 挂载阶段:
beforeMount:
在挂载开始之前被调用,render函数首次被调用,此时依然获取不到具体的DOM节点,但是vue挂载的根节点已经创建(有data,有el);
mounted:
数据和DOM都已经被渲染出来了;
使用场景:
模板渲染成html后调用,通常是初始化页面完成后再对数据和DOM做一些操作,需要操作 DOM的方法可以放在这里。 数据更新阶段:
beforeUpdate:
数据更新时调用,发生在虚拟dom重新渲染;
updated:
更新结束后执行; 使用场景:需要对数据更新做统一处理的;如果需要区分不同的数据更新操作可以使用 $nextTick。 缓存启用时: activated:keep-alive组件激活时调用; deactivated:keep-alive组件停用时调用; 组件卸载时:
beforeDestroy:
实例销毁之前调用;
destroyed:
实例销毁后调用,调用后,vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 生命周期的作用: 给了用户在不同阶段添加自己的代码的机会. 渲染在那个生命周期阶段内完成: 在mounted周期中就已经完成.
页面第一次加载会触发:
beforecreate created beforemount mounted
| 阶段 | 方法名 | 方法名 |
|---|---|---|
| 初始化 | beforeCreate | created |
| 挂载 | beforeMount | mounted |
| 更新 | beforeUpdate | updated |
| 销毁 | beforeDestroy | destroyed |
4、vue组件通讯
第一种:父传子:主要通过props来实现的
具体实现:父组件通过import引入子组件,并注册,在子组件标签上添加要传递的属性,子组件通过props接收,接收有两种形式一是通过数组形式[‘要接收的属性’ ],二是通过对象形式{ }来接收,对象形式可以设置要传递的数据类型和默认值,而数组只是简单的接收
第二种:子传父:主要通过$emit来实现
具体实现:子组件通过通过绑定事件触发函数,在其中设置this.emit中有两个参数一是要派发的自定义事件,第二个参数是要传递的值
然后父组件中,在这个子组件身上@派发的自定义事件,绑定事件触发的methods中的方法接受的默认值,就是传递过来的参数
第三种:兄弟之间传值有两种方法:
方法一:通过event bus实现
具体实现:创建一个空的vue并暴露出去,这个作为公共的bus,即当作两个组件的桥梁,在两个兄弟组件中分别引入刚才创建的bus,在组件A中通过bus.on(‘自定义事件名‘,function(v) { //v即为要接收的值 })接收数据
方法二:通过vuex实现
具体实现:vuex是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括state,actions,mutations,getters和modules 5个要素,主要流程:组件通过dispatch到 actions,actions是异步操作,再actions中通过commit到mutations,mutations再通过逻辑操作改变state,从而同步到组件,更新其数据状态
第四种$attrs/$listeners
实现组件之间的跨代通信。
$attrs 可以获取父组件传进来但没有通过props接收的属性
$listeners 会展开父组件的所有监听的事件(click事件等)常用于更高层级的封装
方法五、provide/inject
这种方式就是Vue中的依赖注入 ,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
方法六、$parent / $children与 ref
使用$parent可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
使用$children可以让组件访问子组件的实例,但是,$children并不能保证顺序,并且访问的数据也不是响应式的。
1.父传子:在对应子级组件上先绑定属性值,props接收父级组件传参的值
2.子传父:需要在对应的子级组件内的方法内this.$emit('key', value);
需要在对应的父级组件上绑定自定义事件,事件名称就是emit发送的key
3.Bus.js (new Vue()) //
this.$emit('key', value)
this.$on('key',(res) => {})
this.$off('key')
4.Vuex
5.v-model/model
6.路由传参 this.$router.push({path: '', query: {}}),
this.$router.push({name: '', query: {}, params: {}}))
7.this.$refs (ref 需要绑定在组件上)
8.provide/inject(父子、爷孙)可以一直向下传递
9.this.$parent/this.$children(获取根节点this.$root)
5、Vuex有哪几种属性?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
-
state 基本数据(数据源存放地)
-
getters 从基本数据(state)派生的数据,相当于state的计算属性
-
mutations 提交更新数据的方法,必须是同步的(如果需要异步使用action)。
-
actions 艾克神=> 像一个装饰器,包裹mutations,使之可以异步。 action 通过 store.dispatch 触发
-
modules 毛丢=> 模块化Vuex
-
主要流程:组件通过dispatch到 actions,actions是异步操作,再actions中通过commit到mutations,mutations再通过逻辑操作改变state,从而同步到组件,更新其数据状态,而getters相当于组件的计算属性,他是对组件中获取到的数据做提前处理的.再说到辅助函数的作用.
dispatch:异步操作,写法: this.$store.dispatch('actions方法名',值) commit:同步操作,写法:this.$store.commit('mutations方法名',值)vuex辅助函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex' 1、mapState 和 mapGetters 因为state和getters返回的是属性,也就是具体的值,所以mapState和mapGetters应该放在计算属性computed中 computed:{ //通过辅助函数获取store中的state ...mapState(['userName']) //等价于:下面常规计算属性代码 /* userName (){ return this.$store.state.userName }*/ } 因为mutations和actions返回的是函数,所以应该放在组件的methods属性中 methods:{ //简写获取store中的mutations ...mapMutations(['tip']) //等价于 /* tip(){ this.$store.commit('tip'); }*/ }
6.计算属性Computed 和 Watch 的区别
Computed
-
它支持缓存,只有依赖的数据发生了变化,才会重新计算
-
不支持异步,当Computed中有异步操作时,无法监听数据的变化
-
computed的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data声明过,或者父组件传递过来的props中的数据进行计算的。
-
如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用computed
-
如果computed属性的属性值是函数,那么默认使用get方法,函数的返回值就是属性的属性值;在computed中,属性有一个get方法和一个set方法,当数据发生变化时,会调用set方法。
computed: { fullName:{ get(){//回调函数 当需要读取当前属性值是执行,根据相关数据计算并返回当前属性的值 return this.firstName + ' ' + this.lastName }, set(val){//监视当前属性值的变化,当属性值发生变化时执行,更新相关的属性数据 //val就是fullName的最新属性值 const names = val.split(' '); this.firstName = names[0]; this.lastName = names[1]; } } }
Watch:
提供了一个方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
-
它不支持缓存,数据变化时,它就会触发相应的操作
-
支持异步监听
-
监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
-
当一个属性发生变化时,就需要执行相应的操作
-
监听数据必须是data中声明的或者父组件传递过来的props中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
- immediate:组件加载立即触发回调函数
- deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep无法监听到数组和对象内部的变化。
watch:{
secondChange:{
handler(oldVal,newVal){
console.log(oldVal)
console.log(newVal)
},
deep:true
}
}
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch。
1、功能上:computed是计算属性,
watch是监听一个值的变化,然后执行对应的回调。
2、是否调用缓存:computed中的函数所依赖的属性没有发生变化,
那么调用当前的函数的时候会从缓存中读取,
而watch在每次监听的值发生变化的时候都会执行回调。
3、是否调用return:computed中的函数必须要用return返回,
watch中的函数不是必须要用return。
4、computed默认第一次加载的时候就开始监听;
watch默认第一次加载不做监听,如果需要第一次加载做监听,
添加immediate属性,设置为true(immediate:true)
5、使用场景:computed----当一个属性受多个属性影响的时候,
使用computed-----购物车商品结算。
watch–当一条数据影响多条数据的时候,使用watch-----搜索框.
7、vue导航守卫:
-
全局守卫:beforeEach((to,from,next)=>{to:进入到哪个路由去;from:从哪个路由离开的;next:函数,是否继续往下执行,是一个方法,它接受参数。这个方法必须调用要不就跳不过去了})
中断跳转:next(false) 跳转新路径 next({path:“/”})
2.全局后置钩子afterEach((to,from)=>{})
钩子不会接受 next函数也不会改变导航本身。这个可以看做保安的狗子,它不管你去哪里,也不会拦你,比较可爱。当然你也可以使用to和from对象。
3.组件内守卫:beforeRouteEnter((to,from,next)=>[]),beforeRouteUpdate,beforeRouteLeave
这三个守卫是写在组件里,beforeRouteEnter守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。其他两个都可以用this。
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
在渲染该组件的对应路由被 confirm 前调用
不!能!获取组件实例 `this`
因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
在当前路由改变,但是该组件被复用时调用
举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
导航离开该组件的对应路由时调用
可以访问组件实例 `this`
}
}
4.路由独享守卫:beforeEnter(to,from,next)
这个守卫是写在路由里面的,只有当进入这个路由时才会调用的,这些守卫与全局前置守卫的方法参数是一样的。
8、keep-alive
1.keep-alive:是vue自带的一个内置缓存组件,包裹动态组件时,会缓存不活动的组件实例,被缓存的组件不会被销毁。
原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。
2.里面有三个属性:
(1). include:包含的组件会被缓存;
(2). exclude:任何匹配的组件都不会被缓存;
(3). max:缓存组件的最大值;
注:exclude的优先级 > include的优先级
3.涉及到的生命周期是:activated路由组件被激活时触发。(当进入缓存的路由组件时触发) 和 deactivated 路由组件失活时触发。(当离开缓存的路由组件时触发)
9、nextTick的实现
-
nextTick是Vue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM; -
Vue在更新DOM时是异步执行的。只要侦听到数据变化,
Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用; -
比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
-
我也有简单了解
nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。 -
vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick就可以获取数据更新后最新DOM的变化
适用场景:
第一种:有时需要根据数据动态的为页面某些dom元素添加事件,这就要求在dom元素渲染完毕时去设置,但是created与mounted函数执行时一般dom并没有渲染完毕,所以就会出现获取不到,添加不了事件的问题,这回就要用到nextTick处理
10、slot插槽是什么?有什么作用?原理是什么?
slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot又分三类,默认插槽,具名插槽和作用域插槽。
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。
实现原理:当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
11、路由
声明式:router-link组件中to接收参数为字符串时,可以为路由的name、可以为路由的path;传递参数为对象时,可以在对象内设置path等
2、编程式:实例中通过router.replace(),所接收的参数形式同上
$router为:vue根实例挂载的路由实例(vue-router的实例)
$route为:当前路由的对象,包含了当前路由的属性
this.route.path) { this.$router.push({ path: item.path, query: { paths: "user", title: 12345 }, }); } params:只能以name跳转 query传递的信息会放在url后面
params
优势 : 刷新,参数依然存在
缺点 : 只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋。
query
优势:传参优雅,传递参数可传对象;
缺点:刷新地址栏,参数丢失
state
同query差不多,只是属性不一样,而且state传的参数是加密的,query传的参数是公开的,只需要把query改为state即可。
优势:传参优雅,传递参数可传对象
缺点:刷新地址栏,(hash方式会丢失参数,Browser模式不会丢失参数)
search
12、hash模式与History模式区别
- history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
- history的url没有'#',hash有
- 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
- HashRouter的原理:通过window.onhashchange方法获取新URL中hash值,再做进一步处理 HistoryRouter的原理:通过history.pushState 使用它做页面跳转不会触发页面刷新,使用window.onpopstate 监听浏览器的前进和后退,再做其他处理
加分回答
hash模式下url会带有#,需要url更优雅时,可以使用history模式。
需要兼容低版本的浏览器时,建议使用hash模式。
需要添加任意类型数据到记录时,可以使用history模式。
13、Key的作用
-
作用:主要是为了高效更新虚拟dom;
-
原理:vue在patch过程中通过key可以精准的判断出两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少dom操作量,提高性能;
-
应用:vue中使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分他们,否则vue只会替换其内部属性而不会触发过渡效果。
需要把keys设置为全局唯一吗 不需要,key是用来进行diff算法的时候进行同层比较, 准备的说key只需要在兄弟节点之间唯一, 一般情况key选取是后端定义的id. 万不得已的时候可以选择index(选择index是万不得已的选择, 因为选择了index后,一些操作会改变index的值, 违背了唯一不变,在进行diff算法的时候出现问题)
14、show和v-if的区别
v-show:是通过修改元素的display属性让其显示或者隐藏; v-if:是直接销毁和重建dom达到让元素显示和隐藏的效果; 应用场景: v-if需要操作dom元素,有更高的切换消耗,v-show只是修改元素的的CSS属性有更高的初始渲染消耗,如果需要非常频繁的切换,建议使用v-show较好,如果在运行时条件很少改变,则使用v-if较好。
15、Vue组件data为什么必须是个函数
Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态 变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返 回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也 是因为根实例只能有一个,不需要担心这种情况
16、真实dom和虚拟dom的区别
虚拟DOM
- 只是一层对真实
DOM的抽象,以JavaScript对象 (VNode节点) 作为基础的树,用对象的属性来描述节点,最终可以通过一系列操作使这棵树映射到真实环境上。 - 在
Javascript对象中,虚拟DOM表现为一个Object对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性。 - 创建虚拟
DOM就是为了更好将虚拟的节点渲染到页面视图中,所以虚拟DOM对象的节点与真实DOM的属性一一照应。
虚拟DOM和真实DOM的区别
- 虚拟DOM不会进行排版与重绘操作
- 虚拟DOM进行频繁修改,然后一次性比较并修改真实DOM中需要改的部分(注意!),最后并在真实DOM中进行排版与重绘,减少过多DOM节点排版与重绘损耗
- 真实DOM频繁排版与重绘的效率是相当低的
- 虚拟DOM有效降低大面积(真实DOM节点)的重绘与排版,因为最终与真实DOM比较差异,可以只渲染局部
真实DOM
- 真实
DOM, 意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实DOM结构
虚拟dom的优缺点
- 缺点:无法直接更新dom 首次渲染大量DOM时,由于多了一层虚拟DOM的计算,会比innerHTML插入慢。
- 优点:更新快,dom操作简单,很少内存消耗
17.单向数据流和双向数据流
单向数据流:单向,可以想象城一棵dom树自上而下渲染数据,上方数据改动会引发下面所有用到数据的改动,而下方数据改动不会影响上方(类似父子组件的概念)
双向数据流:类似vue的双向数据绑定,一个数据更改,会默认引发其他数据的更改
优缺点:
单向数据流:
优点 1、所有状态的改变可记录、可跟踪,源头易追溯。 2、所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。 3、一旦数据变化,就去更新页面(data-页面),但是没有(页面-data) 4、如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中。
缺点 1、HTML代码渲染完成,无法改变,有新数据,就需把旧HTML代码去掉,整合新数据和模板重新渲染。 2、代码量上升,数据流转过程变长,出现很多类似的样板代码。 3、同时由于对应状态独立管理的严格要求(单一的全局store),在处理局部状态较多的场景时(如用户输入交互较多的“富表单型”应用),会显得啰嗦及繁琐。 双向数据流:
优点
1、用户在视图上的修改会自动同步到数据模型中去,数据模型中值的变化也会立刻同步到视图中去。 2、无需进行和单向数据绑定的那些相关操作。 3、在表单交互较多的场景下,会简化大量业务无关的代码。
缺点 1、无法追踪局部状态的变化。 2、“暗箱操作”,增加了出错时debug的难度。 3、由于组件数据变化来源入口变得可能不止一个,数据流转方向易紊乱,若在缺乏“管制”手段,血崩。
18.权限登录
后台返回路由权限
前台通过addRouters()添加到路由表中
created() {
this.roles = JSON.parse(sessionStorage.getItem("roles"));
//根据后端返回的权限,返回该用户的路由配置
let currRoutes = this.allRoutes.filter(item => {
return this.roles.some(obj => obj.path == item.path);
});
this.$router.addRoutes(.);
}
19.自定义指令
全局自定义指令(用Vue.directive来注册)
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
-
防抖、图片懒加载、一键 Copy的功能、拖拽、页面水印、权限校验、输入框自动聚焦、相对时间转换、下拉菜单
局部指定指令(通过在组件内设置directives属性)
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
clic:{
bind(el,binding){
binding.value() 绑定的事件
}
}
<input v-focus></input>
自定义指令钩子函数
自定义指令的钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
20.父子生命周期顺序
同步引入时生命周期顺序为:父组件的beforeCreate、created、beforeMount --> 所有子组件的beforeMount、created、beforeMount --> 所有子组件的mounted --> 父组件的mounted
异步引入时生命周期顺序:父组件的beforeCreate、created、beforeMount、mounted --> 子组件的beforeCreate、created、beforeMount、mounted
Vue 常用的修饰符有哪些?
事件修饰符:
.prevent 阻止默认行为
.stop阻止事件冒泡
.capture 以捕获模式触发当前的事件处理函数
.once绑定的事件只触发一次
.self 只有在event.target是当前元素自身时触发事件处理函数
按键修饰符:
.enter 键盘按下enter时触发
.esc 键盘按下esc时触发
Vue3.0
1、Vue 团队于 2020 年 9 月 18 日晚 11 点半发布了Vue 3.0 版本
2、vue2和vue3双向数据绑定原理的区别?
-
vue2 的双向数据绑定是利用ES5 的一个 API Object.definePropert()对数据进行劫持
-
vue3 发生了改变,使用proxy替换Object.defineProerty,使用Proxy的优势:
- 可直接监听数组类型的数据变化 性能的提升 监听的目标为对象本身,不需要像Object. 一样遍历每个属性,有一定的性能提升 可直接实现对象属性的新增/删除
3、根节点的不同
// vue2
<template>
<div>
<h1></h1>
</div>
</template>
// vue3
<template>
<div>
<h1></h1>
</div>
<div>
<span></span>
</div>
</template>
4、vue3生命周期
beforeCreate -> setup() 开始创建组件之前,创建的是data和method
created -> setup()
beforeMount -> onBeforeMount 组件挂载到节点上之前执行的函数。
mounted -> onMounted 组件挂载完成后执行的函数
beforeUpdate -> onBeforeUpdate 组件更新之前执行的函数。
updated -> onUpdated 组件更新完成之后执行的函数。
beforeDestroy -> onBeforeUnmount 组件挂载到节点上之前执行的函数。
destroyed -> onUnmounted 组件卸载之前执行的函数。dszhuoyi
activated -> onActivated 组件卸载完成后执行的函数
deactivated -> onDeactivated
5、vue2和vue3的diff算法
-
vue2
- vue2 diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom。 vue2 diff算法会比较每一个vnode,而对于一些不参与更新的元素,进行比较是有点消耗性能的。
-
vue3
- vue3 diff算法在初始化的时候会给每个虚拟节点添加一个patchFlags,patchFlags就是优化的标识。 只会比较patchFlags发生变化的vnode,进行更新视图,对于没有变化的元素做静态标记,在渲染的时候直接复用。
6、v-if 和 v-for 在vue2和vue3的区别
- 在vue2中,不建议
v-if和v-for一起使用。因为先执行v-for,循环完再执行v-if,如果发现为false,刚创建的又要删掉,会造成性能浪费、页面卡顿。最好的方法时使用计算属性来处理数据。 - 在Vue3中,
v-if比v-for先执行 ,所以可以同时使用。
7、V3 ref() 函数
在setup函数中,可以使用ref函数,用于创建一个响应式数据,当数据发生改变时,Vue会自动更新视图
8、V3 reactive 函数
// 用来检测复杂数据类型的变化
<template>
<h1>{{ obj }}</h1>
<button @click="change_age">点我</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
let obj = reactive({ name: "Alice", age: 12 });
function change_age() {
++obj.age
}
return { obj,change_age}
}
}
</script>
react
1、React事件机制
React不是将事件绑定到真实DOM上,而是在document处监听了所有的事件,当事件发生并且冒泡到document处的时候,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件,而且冒泡到document上的事件也不是原生的浏览器事件,而是由react自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用event.preventDefault()方法,而不是调用event.stopProppagation()方法。
实现合成事件的目的如下:
- 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
- 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。
2.React的事件和普通的HTML事件有什么不同?
- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
- 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用
preventDefault()来阻止默认行为。
合成事件的优点:
- 兼容所有浏览器,更好的跨平台;
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
- 方便 react 统一管理和事务机制。
3.react组件通讯
-
父子组件之间的传值
- 在父组件中,给需要传递数据的子组件添加一个自定义属性,在子组件中通过props就可以获取父组件传递过来的数据
- 父组件通过props向子组件传入一个方法,子组件在通过调用该方法,将数据以参数的形式传给父组件,父组件可以在该方法中对传入的数据进行处理;
-
兄弟组件之间的传值
4.性能优化
shouldComponentUpdate,用来判断是否需要调用 render 方法重绘dom
- 利用shouldComponentUpdate函数,,避免不必要的渲染
- 给DOM元素加上唯一的key,尽量不要用index,因为如果新DOM中删了某一个节点,它会重新排列index,所以最好使用id值作key值。
- 能用const声明的就用const。
- DOM里少用箭头函数,当然其实要传参时也还是得用。再者,函数bind尽量写在constructor,避免每次render重新bind。
- 减少对真实DOM的操作。
- 如果是用webpack搭建环境的话,当一个包过大加载过慢时,可分打成多个包来优化。
5.react 生命周期函数
组件的挂载:
constructor 组件初始化数据
componentWillMount 代表组件已经初始化数据了 但还没有渲染DOM
render(渲染组件) 组件开始渲染和(更新)触发
componentDidMount dom节点已经生成 发送异步请求获取数据
更新
componentWillReceiveProps :当父组件更新子组件state时,该方法会被调用。
shouldComponentUpdate : 该方法决定组件state或props的改变是否需要重新渲染组件。
componentWillUpdate 更新之前
render再次渲染视图 会得到一份虚拟dom,并且和上一次的虚拟dom进行对比
(dom diff的到最小的差异),只重新渲染有差异的部分
componentDidUpdate 更新之后
组件的卸载
componentWillUnmount 卸载之前清除定时器
移除的生命周期
componentWillMount--- componentWillUpdate---- componentWillReceiveProps-----
实例期:
constructor
getDerivedStateFromProps
render
componentDidMount
运行期:
props 更新:
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
state 更新:
getDerivedStateFromProps
shouldComponentUpdate
render
getSnapshotBeforeUpdate
componentDidUpdate
挂载之前:
1. constructor构造 不算是一个生命周期 ,
2. componentWillMount:加载动画,加载骨架 屏 ,
3. render
4. componentDidMount:请求数据 一些dom操作
更新之前:
-
state改变 shouldComponentUpdate:是否更新
2. props改变 componentWillReceiveProps(prevProps,prevState) 3. componentWillUpdate(prevProps,prevState) 4. render 5. componentDidUpdate(prevProps,prevState)
卸载 阶段 :
- componetWillUnMount:清理定时器,中断请求,内存溢出异常
新生命周期:
1,getDeveiedStateFromProps取代了 componentWillMount ,componentWillReceiveProps;
static :静态方法不会挂载到实例上的 他会挂载到类上面
2, getSnapshotBeforeUpdate :取代了 componentWillUpdate: 更新的时候会记录 Snapshot 快照
有返回值 , copmponentDidUpdate的参数:就是 :getSnapshotBeforeUpdate的返回值
第一个阶段 constructor > componentWillMount > render > componentDidMount
第二个阶段 shouldComponentUpdate > componentWillUpdate > render
componentWillReceiveProps
第三个阶段 componentWillUnMount
6.函数式组件和类组件的区别
- 函数式组件不会被实例化。整体渲染性能的搭配提升
- 函数式组件没有状态
- 函数式组件没有访问生命周期的方法
- 类组件需要继承 Class,函数组件不需要
- 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
- 类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)
7.Hooks
-
hooks出现的本质原因是:
- 让函数组件也能做类组件的事,有自己的状态,可以处理一些副作用,能获取ref,也能做数据缓存
-
注意事项
- 只能在函数内部的最外层调用hook,不要在循环、条件判断或者子函数中调用
- 只能在react的函数组件中调用hook,不要在其他javaScript函数中调用
-
常用hooks
-
useState:状态钩子,用于为函数组件引入状态,它接受状态的初始值作为参数,它返回一个数组,其中数组第一项为一个变量,指向状态的当前值,类似this.state。第二项是一个函数,用来更新状态,类似setState。
-
useMemo:发生在render前,返回一个缓存的数据,且仅在依赖项改变后发生变化,可以避免多余的计算开销
-
useCallback:返回一个缓存的函数,添加依赖项可以避免函数的无意义计算,降低了子组件的渲染开销
-
useContext:可以做状态的分发
-
useEffect:两个参数,第一个是你要执行的异步操作,第二个是一个数组,用来给出Effect的依赖项,只要这个数组发生变化,useEffect()就会执行,当第二项省略不填时,useEffect会在每次组件渲染时执行
-
useRef:可以存储不需要引起页面渲染的数据;修改useRef值的唯一方法就是修改current,且修改后不会引起重新渲染
-
useCallback和useMemo的区别
参数相同,第一个参数为回调,第二个参数为要依赖的数据。
-
共同点:
- 仅仅依赖数据发生变化,才会重新计算结果,也就是起到缓存的作用。
-
不同点
- useMemo计算结果是return回来的值。主要用于缓存计算结果的值,应用场景:需要计算的状态
- useCallback计算结果是函数,主要用于缓存函数,应用场景:需要缓存的函数,函数式组件每次任何一个state的变化整个组件都会被重新渲染,此函数是没有必要被重新渲染的
-
-
useReducer
-
Action钩子,提供状态的管理,它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。
-
类似reudx,其原理是我们通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。Reducer的形式是(state,action) => newState
hooks的优缺点
-
优点:
- 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护。 例如,每个生命周期中常常会包含一些不相关的逻辑。一般我们都会在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。
- 组件树层级变浅。 在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现。
- 不用再去考虑 this 的指向问题。在类组件中,你必须去理解 JavaScript 中 this 的工作方式。
-
缺点:
- 对一些钩子函数不支持。当下 v16.8 的版本中,还无法实现
getSnapshotBeforeUpdate和componentDidCatch这两个在类组件中的生命周期函数。
- 对一些钩子函数不支持。当下 v16.8 的版本中,还无法实现
-
-
useEffect和useLayoutEffect的区别
useEffect是异步执行的,而`useLayoutEffect`是同步执行的。
useEffect的执行时机是浏览器完成渲染之后,
而 `useLayoutEffect` 的执行时机是浏览器把内容真正渲染到界面之前,
和 `componentDidMount` 等价。
useEffect
模拟 class 组件的 componentDidMount 和 componentDidUpdate
第一个参数执行函数,第二个参数不传
useEffect(() => {
console.log('DidMount 和 DidUpdate')
})
模拟 class 组件的 componentDidMount
第一个参数执行函数,第二个参数传空数组[]
useEffect(() => {
console.log('加载完了componentDidMount')
}, []) 第二个参数是 [] (不依赖于任何 state)
模拟 class 组件的 componentDidUpdate
第一个参数执行函数,第二个参数传state数组
useEffect(() => {
console.log('更新了')
}, [count, name]) 第二个参数就是依赖的 state
模拟 class 组件的 componentDidMount 和 componentWillUnmount
useEffect(() => {
let timerId = window.setInterval(() => {
console.log(Date.now())
}, 1000)
返回一个函数
模拟 componentWillUnmount 组件销毁的时候 停止计时器
return () => {
window.clearInterval(timerId)
}
}, [])
useMEmo和useCallback
在依赖数据发生变化的时候,才会调用传进去的回调函数去重新计算结果,起到一个缓存的作用
useMemo 缓存的结果是回调函数中return回来的值,主要用于缓存计算结果的值,
应用场景如需要计算的状态
useCallback 缓存的结果是函数,主要用于缓存函数,
应用场景如需要缓存的函数,因为函数式组件每次任何一个state发生变化,
会触发整个组件更新,一些函数是没有必要更新的,此时就应该缓存起来,
提高性能,减少对资源的浪费
8、hashrouter和historyrouter的区别
- history是利用浏览历史记录栈的API实现,hash是监听location对象hash值变化事件来实现
- history的url没有'#',hash有
- 相同的url,history会触发添加到浏览器历史记录栈中,hash不会触发,history需要后端配合,如果后端不配合刷新新页面会出现404,hash不需要。
- HashRouter的原理:通过window.onhashchange方法获取新URL中hash值,再做进一步处理 HistoryRouter的原理:通过history.pushState 使用它做页面跳转不会触发页面刷新,使用window.onpopstate 监听浏览器的前进和后退,再做其他处理
加分回答
hash模式下url会带有#,需要url更优雅时,可以使用history模式。
需要兼容低版本的浏览器时,建议使用hash模式。
需要添加任意类型数据到记录时,可以使用history模式。
9、state和props的区别
- props是传递给组件的(类似于函数的形参),而state是在组件内被组件自己管理的(类似于在一个函数内声明的变量)
- props是不可修改的,所有react组件必须像纯函数一样保护它们的props不被更改。由于props是传入的,并且他们不能更改,因此我们可以将任何仅使用props的react组件视为pureComponent,也就是说,在相同的输入下,它将始终呈现相同的输出
- state 是在组件中创建的,一般在 constructor 中初始化 state
- state 是多变的、可以修改
10、redux工作流程,使用步骤
工作流程
- store通过reducer创建了初始状态
- view通过store.getState()获取到了store中保存的state挂载在了自己的状态上
- 用户产生了操作,调用了actions 的方法
- actions的方法被调用,创建了带有标示性信息的action
- actions内部通过调用store.dispatch方法将标志性的action发送到了reducer中
- reducer接收到action并根据标识信息判断之后返回了新的state
- store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知view去重新获取state
使用步骤
- store通过reducer创建了初始状态
- view通过store.getState()获取到了公共状态
- view要改变状态,调用了actions 的方法,actions内部通过调用store.dispatch方法传递行为标 识给reducer中, reducer接收到action并根据行为标识改变状态,返回新的状态。
- store中的状态被reducer更改为新的状态,store.subscribe方法里的回调函数会执行,此时就可 以通知view去重新获取state。
- 优点
- Redux轻量,生态丰富,可以结合流行的`redux-thunk`、`redux-saga`等进行使用
- Redux的写法比较固定,团队应用中风格比较稳定,提高协作中的可维护性
- 因为Redux中的reducer更新时,每次return的都是不可变对象,所以时间旅行操作相对容易
- 缺点
- 每一次的dispatch都会从根reducer到子reducer嵌套递归的执行,所以效率相对较低;
- Redux核心是不可变对象,在Reducer中的操作都要比较小心,注意不能修改到state的属性
- Redux中写法固定,模板代码较多
redux执行流程
1.store通过reducer创建初始状态
2.view通过store.getState()获取公共状态
3.view改变状态,需要调用action里面的方法,action里面被调用的方法会通过
store.dispatch()方法传递行为标识给reducer
4.reducer接收到action并根据传递过来来的行为标识来改变状态
5.store中改变的状态会被view重新获取
State:Redux中的数据
Reducer:这是Redux的核心,内部处理接受到action后到返回新的state的逻辑;
reducer可以进行嵌套,一个store只有一个根reducer
Action:一般会写成actionCreator函数的形式,这个函数返回的就是action对象,
这个对象至少会一个type属性,用于标识当前的动作
Store: 以上三部分组成的就是一个Store,一般来说一个应用仅存在一个Store,
它可以进行读取应用的state,监听state的变化,发起一个action等操作
store:用来存储数据的
reducer:管理数据的(事件处理函数)
actionCreators:创建action,是action的创建者,交由reducer来处理
view:用来使用数据,在这里,一般用reducer组件来充当
11、reudx中间件
什么是中间件
- 本质上是一个函数,reudx允许我们通过中间件的方式,扩展和增强redux应用程序。
中间件的好处
- actions都是直接被reducer函数处理的,加入中间件以后,在触发了一个action之后这个action会优先被中间件处理,当中间件处理完这个action以后,中间件会把这个action传递给reducer,让reducer继续处理
中间有哪些,它们的作用是什么
-
redux-thunk
- redux默认不支持异步数据,当执行一些异步操纵时,通过该插件进行处理。
- 可以多次触发dispatch方法,处理异步,延迟触发dispatch方法
-
redux-logger
- 打印redux日记,查看之前的state,action调用方法,现在的state
-
redux-promise
- 使dispatch方法可以接受Promise对象作为参数,处理异步
-
redux-saga
- 用于处理异步操作的中间件,可以用来替换redux-thunk
注册中间件
import { createStore applayMiddleware } from 'redux';
import logger from "redux-logger";
createStore(reducer,applyMiddleware(logger))
12、函数组件和类组件区别
- 类组件有生命周期,函数组件没有
- 类组件需要继承 Class,函数组件不需要
- 类组件可以获取实例化的 this,并且基于 this 做各种操作,函数组件不行
- 类组件内部可以定义并维护 state, 函数组件为无状态组件(可以通过hooks实现)
- 函数组件相比较类组件,优点是更轻量与灵活,便于逻辑的拆分复用。
1、函数组件是一个纯函数,它接收一个props对象返回一个react元素;而类组件需要去继承React.Component 并且创建render函数返回react元素。2、函数组件没有生命周期和状态state,而类组件有。
-
两者最明显的不同就是在语法上,函数组件是一个纯函数,它接收一个
props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素,这将会要更多的代码,虽然它们实现的效果相同。 -
因为函数组件是一个纯函数,你不能在组件中使用
setState(),这也是为什么把函数组件称作为无状态组件。如果你需要在你的组件中使用
state,你可以选择创建一个类组件或者将state提升到你的父组件中,然后通过props对象传递到子组件。- 在
react16.8版本中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。因此,2、3两点就不是它们的区别点。从这个改版中我们可以看出作者更加看重函数组件,而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。
- 在
13、说说React Jsx转换成真实DOM过程?
react组件通过JSX的映射 ,组件更新,JSX通过babel最终转化成React.createElement- 转译工具 : babel-preset-react-app
14、说说对受控组件和非受控组件的理解?应用场景?
-
受控:
-
表单中的值与状态绑定
-
-
非受控:
-
表单中的值不与状态绑定,要获取到value值,需要使用ref操作
-
-
区别:
-
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由
React组件负责处理,如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少
-
15、高阶组件(Hoc)
高阶组件是重用组件逻辑的高级方法,是一种源于 React 的组件模式。 HOC 是自定义组件,在它之内包 含另一个组件。它们可以接受子组件提供的任何动态,但不会修改或复制其输入组件中的任何行为。你可以认 为 HOC 是“纯(Pure)”组件。
作用:
- 代码重用,逻辑和引导抽象
- 渲染劫持
- 状态抽象和控制
- props控制
特点
- 高阶组件本身就是个纯函数(高阶函数),接收组件或者dom元素为参数,并且不改变原组件,render 时只渲染这组件,并没有侵入式修改这个组件, 每个组件都可以复用高阶组件的逻辑代码
使用场景
高阶组件本质是高阶函数,传递一个组件作为参数,给组件添加逻辑代码,返回一个组件
使用场景:需要复用组件逻辑,继承皮肤的时候,例如:全局守卫
16、纯函数
如果函数的调用参数相同,则永远返回相同的结果。它不依赖于程序执行期间函数外部任何状态或数据 的变化,必须只依赖于其输入参数。 该函数不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)。
17.react17和18的区别
react18版本不支持ie11,如果需要兼容需要退回到react17版本。
React18版本引入了一个新的root API,新的root API还支持new concurrent renderer(并发模式的渲染),允许进入concurrent mode(并发模式)
在react18版本之前只有在react事件处理函数中,才会自动执行批量处理,其他情况会多次更新。在react18版本之后,任何情况都会自动执行批量处理,多次更新始终合并为一次。
18.hooks使用规则
在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook,
确保总是在你的 React 函数的最顶层调用他们
只在 React 函数中调用 Hook
不要在普通的 JavaScript 函数中调用 Hook
1,不需要关注this指向问题
2,底层api比较轻量,改造成本小(不会影响原来的组件层次结构)
3,每个hook都是个独立的函数,多个生命周期hook可以相互独立不干扰
如果是类组件中的在相同的周期函数中做多件事情,需要在同一周期内执行
做不到相互独立
4,组件中的状态和UI变得清晰和隔离
19.路由传参
<Route path='/path/:id/:n' component={Path}/>
1-1:<link to="/path/2/wyy">xxx</Link>
1-2:<button onClick={()=>{
this.props.history.push('/path/10/wyy');---
}}>跳转到用户信息</button>
读取参数用:this.props.match.params
优势:刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋
---2.search
<Route path='/path/patn1' component={Path}/>
2-1:<link to="/path/patn1?id=1">xxx</Link> 无法拼接
2-2: Link 的to属性值写成对象
to={{
pathname:'/system/system1', 路径
search:'a=10&b=20', //问号传参,会在url中显示
}}
2-3: 编程式导航 传参
<button onClick={()=>{
this.props.history.push({
pathname:'/customer/list',
search:'a=10&b=20'
})
}}>跳转到用户列表</button>
读取参数用: this.props.location.search
优势:刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋
---3, state
<Route path='/path/patn1' component={Path}/>
2-1: Link 的to属性值写成对象
to={{
pathname:'/customer/list',
state:{n:10,m=20}
}}
2-2: 编程式导航 传参
<button onClick={()=>{
this.props.history.push({
pathname:'/customer/list',
state:{n:10,m=20}
})
}}>跳转到用户列表</button>
读取参数用: this.props.location.state
优势:传参优雅,传递参数可传对象
缺点:刷新地址栏,参数丢失
20.react的innerHTML
dangerouslySetInnerHTML
render() {
const val = `
测试第一行</br>
测试第二行</br>
测试第三行</br>
测试第四行</br>
`
return (
<div dangerouslySetInnerHTML={{__html: `<div>${val}</div>`}} />
)
}
21.react的事件和原生事件
React的事件并没有绑定到具体的dom节点上,而是绑定在了document上,然后由统一的事件监听器去监听事件的触发
react合成事件
react合成事件指的是react用js模拟了一个Dom事件流,
原生事件就是js的原生事件,如通过document.addEventListener来设置的监听事件。
React有自己的一套事件机制,它重新封装了绝大部分的原生事件。
合成事件采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。
React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事
件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运
行。
合成事件的一些特点总结:
- React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应
的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事
件)
- React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的
callback
- React 通过对象池的形式管理合成事件对象的创建和销毁,减少了垃圾的生成和新对
象内存的分配,提高了性能
git基础指令**
-
git add . :将需要进行版本管理的文件放入暂存区;1
-
git commit -m '上传的命令' :上传到本地;2
-
git push origin 分支名称 :上传到指定分支的远程仓库;3
-
git checkout master :切换到master分支;4
-
git pull origin(对远程分支进行的操作) master :下拉分支;
-
git merge 仓库名 :合并主分支与分分支;
-
git remote :查看仓库名;
-
git remote add origin https://... :添加远程仓库。
-
88.gitee
创建分支
git checkout 分支名 切换分支
git checkout -b 分支名 创建并切换分支
git branch 分支名 查看分支
git branch -d 分支名 删除分支名(-D强制删除)
回退版本系列
git status 查看状态(红色为未add 绿色为commit之后)
git log 查看日志 可查看到commit描述 以及独一的hash值
撤销add 之后的操作 但还没有commit -m?
git rm -r --cached xx //撤销xx文件
git rm -r --cached . //全部撤销
撤销commit -m之后操作?
先git log
git reset --hard hash值
git reset --hard HEAD^^ (回退到上一个版本 上两个版本呢 再往后加^)
撤销commit -m之后 我又不想撤销了 怎么办?
git reflog 可以查看提交记录的hash值
执行这个即可git reset --hard hash值
注意:add . 但 没有 commit 我们执行git reset --hard 之后这次的add暂存就再也找不回来了 所以我们要commit 让git记录一下
撤销此次合并
git reset --merge
提交步骤
git add .
git commit -m '描述'
git push origin 分支名
合并
git checkout dev
git pull (更新所有分支)
git merge origin/子分支名
git push origin dev
成员等待合并之后 执行拉取操作
(rebase?命令 待学习)
rebase是合并分支 并且 合并commit记录
拉取
git checkout dev
git pull origin dev
git checkout 自己的分支
git merge dev
1.回退版本
首先出现这种情况,一般都是老大进行操作,让老大回退版本。如果是自己回退版本,我会先进行两个操作,第一步 git reflog,我能看到我提交的版本历史,并且针对提交的每一个历史记录都会生成一个哈希值,针对我要调回的版本哈希值进行回退 git reset --hard + hash值
2.解决git 代码上传冲突 他想问你的是代码上传冲突
如果在同一个分支开发,遇到冲突 git stash 保存本地代码到你上一次提交的节点,然后 git pull 因为冲突可能使你拉不下来,然后 git stash pop,将保存的代码获取到本地,在冲突的 model 里面解决冲突,最好是保留双方,因为你要判断最新的代码是哪一份,是否符合当前,运行看有没有报错,漏掉哪一些代码。
浏览器
1.大文件上传:
大致流程如下:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块;
- 初始化一个分片上传任务,返回本次分片上传唯一标识;
- 按照一定的策略(串行或并行)发送各个分片数据块;
- 发送完成后,服务端根据判断数据上传是否完整,如果完整,则进行数据块合成得到原始文件
2.断点续传
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
一般实现方式有两种:
- 服务器端返回,告知从哪开始
- 浏览器端自行处理
需要考虑:
- 切片上传失败怎么办
- 上传过程中刷新页面怎么办
- 如何进行并行上传
- 切片什么时候按数量切,什么时候按大小切
- 如何结合 Web Work 处理大文件上传
- 如何实现秒传
3.跨域
跨域的原因
跨域是是因为浏览器的同源策略限制,是浏览器的一种安全机制,服务端之间是不存在跨域的。
所谓同源指的是两个页面具有相同的协议、主机和端口,三者有任一不相同即会产生跨域。
解决方案:(后台处理)
- JsonP是通过Web页面所有拥有src属性的标签都拥有跨域能力的属性,使客户端通过像调用脚本一样的方式,调用跨域服务器生成的js格式文件来获取数据。
(缺点:只可以使用get请求 优点:支持古老的浏览器如IE). - proxy
postMessage(message, targetOrigin, [transfer])(HTML5新增API 用于多窗口消息、页面内嵌iframe消息传递),通过onmessage监听 传递过来的数据Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。Node中间件代理Nginx反向代理- 各种嵌套
iframe的方式,不常用。 - 日常工作中用的最对的跨域方案是后台的CORS和Nginx反向代理
jnon优缺点
1.优点
1.1它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制,JSONP可以跨越同源策略; 1.2它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持 1.3在请求完毕后可以通过调用callback的方式回传结果。将回调方法的权限给了调用方。这个就相当于将controller层和view层终于分开了。我提供的jsonp服务只提供纯服务的数据,至于提供服务以 后的页面渲染和后续view操作都由调用者来自己定义就好了。如果有两个页面需要渲染同一份数据,你们只需要有不同的渲染逻辑就可以了,逻辑都可以使用同 一个jsonp服务。
2.缺点
2.1它只支持GET请求而不支持POST等其它类型的HTTP请求 2.2它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。 2.3 jsonp在调用失败的时候不会返回各种HTTP状态码。 2.4缺点是安全性。万一假如提供jsonp的服务存在页面注入漏洞,即它返回的javascript的内容被人控制的。那么结果是什么?所有调用这个 jsonp的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用jsonp的时候必须要保证使用的jsonp服务必须是安全可信的。
4.proxy
vue中在config目录下找到index.js文件
在index.js中找到dev代码块
修改proxyTable:{}内的数据
里面写'^api':''表示rewrite重写。
配置好之后可以直接写/api加上接口名称发送请求
5.get和post区别:
①get会在url地址栏显示,相对来说不安全,post会在请求体里携带过去,发送给后台; ②get传送的数量较小,主要受URL长度限制,post传送数量较大,一般被默认为不受限制
get方式的安全性较Post方式要差些
对参数的数据类型,GET只接受ASCII字符,而POST没有限制
-
Get:- Get查询字符串(键值对)被附加在URL地址后面一起发送到服务器;
- 能够被缓存;
- Get请求会保存在浏览器的浏览记录中;
- 以Get请求的URL能够保存为浏览器书签。
-
Post:- Post查询字符串被放在Post信息中的一个单独地方和HTTP请求一起发送到服务器;
- 不能被缓存下来;
- Post请求不会保存在浏览器历史记录中;
- 以Post请求的URL无法保存为浏览器书签
POST 和GET本质都是一样一样的。
2.POST和GET都是HTTP请求的基本方法。
3.区别主要有以下几个:
3-1 GET请求在浏览器刷新或者回退的时候是无害的。POST的话数据会被重新提交。
3-2 GET可以被书签收藏,POST不行
3-3 GET可以存在缓存中。POST不行
3-4 GET 会将数据存在浏览器的历史中,POST不会
3-5 GET 编码格式只能用ASCII码,POST没有限制
3-6 GET 数据类型urlencode,POST是URLENCODE,form-data
3-7 可见性 参数在URL用户可以看见,POST的参数在REQUSET BODY中不会被用户看见
3-8 安全性 GET相对不安全 POST相对安全些
3-9 长度 参数一般限制2048(和WEB服务器相关),参数无限制。
4.GET 和POST在请求的时候
4-1 GET 是将数据中的hearder 和 data 一起发送给服务端,返回200code
4-2 POST 是先将hearder发给服务器返回100continue,再发送data给到服务器,返回200
4-3 GET 就发送了一个TCP数据包给服务器而POST发送了两次TCP数据包给服务器
4-4 GET和POST是已经有定义好的说明的,最好不要混用。
5. GET和POST本质上是一样一样的,GET可以加Request Body ,POST也可以在URL中添加参数。实现是可以的。
1、get方式的安全性较Post方式要差些,包含机密信息的话,建议用Post数据提交方式;
2、在做数据查询时,建议用Get方式;而在做数据添加、修改或删除时,建议用Post方式;区别表现如下:
1. get是从服务器上获取数据,post是向服务器传送数据。
6.浏览器渲染机制
1、构建DOM树(parse): 渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(Content Tree/DOM Tree);
2、构建渲染树(construct): 解析对应的CSS样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中可见的指令(如****),构建渲染树(Rendering Tree/Frame Tree);
3、布局渲染树(reflow/layout): 从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标;
4、绘制渲染树(paint/repaint): 遍历渲染树,使用UI后端层来绘制每个节点
7.JS设计模式有哪些(单例模式观察者模式等)**
答:JS设计模式有很多,但我知道的有单例模式,观察者模式
单例模式:就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象
观察者模式: 观察者的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
总的来说,观察者模式所做的工作就是在解耦,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响到另一边的变化
8.js线程原理EventLoop
JS`是单线程的,为了防止一个函数执行时间过长阻塞后面的代码,所以会先将同步代码压入执行栈中,依次执行,将异步代码推入异步队列,异步队列又分为宏任务队列和微任务队列,因为宏任务队列的执行时间较长,所以微任务队列要优先于宏任务队列。微任务队列的代表就是,`Promise.then`,`MutationObserver`,宏任务的话就是`setImmediate setTimeout setInterval
9.微任务宏任务
微观任务队列 微任务:语言标准(ECMAScript)提供,如process.nextTick(node)、Promise、Object.observe、MutationObserver
宏观任务队列 宏任务:由宿主环境提供,比如setTimeout、setInterval、网络请求Ajax、用户I/O、script(整体代码)、UI rendering、setImmediate(node)
先执行微任务 宏任务拿出来 先执行微任务 微任务执行完之后再执行宏任务
执行完微任务之后 执行宏任务 先进先出
10.重绘重排
重绘:
重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
重排:
当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
重排必定会引发重绘,但重绘不一定会引发重排
11.svg与canvas的区别
(1)SVG: SVG可缩放矢量图形(Scalable Vector Graphics)是基于可扩展标记语言XML描述的2D图形的语言,SVG基于XML就意味着SVG DOM中的每个元素都是可用的,可以为某个元素附加Javascript事件处理器。在 SVG 中,每个被绘制的图形均被视为对象。如果 SVG 对象的属性发生变化,那么浏览器能够自动重现图形。
其特点如下:
- 不依赖分辨率
- 支持事件处理器
- 最适合带有大型渲染区域的应用程序(比如谷歌地图)
- 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快)
- 不适合游戏应用
(2)Canvas: Canvas是画布,通过Javascript来绘制2D图形,是逐像素进行渲染的。其位置发生改变,就会重新进行绘制。
其特点如下:
- 依赖分辨率
- 不支持事件处理器
- 弱的文本渲染能力
- 能够以 .png 或 .jpg 格式保存结果图像
- 最适合图像密集型的游戏,其中的许多对象会被频繁重绘
12.强缓存和协商缓存
缓存
将一些静态资源存储在本地磁盘当中,这样下次请求资源的时候,浏览器直接从本地缓存中读取文件,不需要再次发送请求。这样可以减少了网络延迟,加快了页面响应速度,增强了用户体验;减少了网络带宽消耗;减轻了服务器的压力。
浏览器缓存:浏览器通常会将常用资源缓存在你的个人电脑的磁盘和内存中
强缓存:
当浏览器去请求某个文件的时候,服务端就在respone header里面设置时间,如果告诉客户端时间没过期,下次请求就不需要重新请求服务器,直接拿缓存,直接返回200状态码;所以强缓存需要服务器端配置。缓存的时间、缓存类型都由服务端控制
协商缓存 :
客户端每次请求资源时都会看是否过期;只有在过期才会去询问服务器。所以,强缓存就是为了给客户端自给自足用的。而当某天,客户端请求该资源时发现其过期了,这是就会去请求服务器了,而这时候去请求服务器的这过程就可以设置协商缓存。这时候,协商缓存就是需要客户端和服务器两端进行交互的。
13.部署
-
jenkins
-
在实际开发中,我们经常要一边开发一边测试,当然这里说的测试并不是程序员对自己代码的单元测试,而是同组程序员将代码提交后,由测试人员测试;
或者前后端分离后,经常会修改接口,然后重新部署;
这些情况都会涉及到频繁的打包部署;
手动打包常规步骤:
1.提交代码
2.问一下同组小伙伴有没有要提交的代码
3.拉取代码并打包(war包,或者jar包)
4.上传到Linux服务器
5.查看当前程序是否在运行
6.关闭当前程序
7.启动新的jar包
8.观察日志看是否启动成功
9.如果有同事说,自己还有代码没有提交......再次重复1到8的步骤!!!!!(一上午没了)
那么,有一种工具能够实现,将代码提交到git后就自动打包部署勒,答案是肯定的:Jenkins
当然除了Jenkins以外,也还有其他的工具可以实现自动化部署,如Hudson等
只是Jenkins相对来说,使用得更广泛。
-
-
gitlab
- 提高前端的开发效率和开发测试之间的协调效率
14.状态码
1xx:是预留给协议的
3xx:是表示重定向的
301 永久重定向:浏览器需要重新发送一个HTTP请求,返回新的url和301码
302:代表暂时性转移,也可以是拦截时跳转
区别
临时的旧url只是暂时用别的代替,永久重定向是旧的url不再使用,被新的代替
304 禁止包含消息体,原来缓存的文档还可以继续使用
4xx:是客户端错误的
400:错误请求,语法或参数有错误 401:未授权==》是请求用户的token这个接口的时候 ,没有加header就会401
在http request 中的header Authorization中发送给服务端
403:没有权限访问此站(禁止,拒绝请求)
遇到过 403,https写成了http 准确应该是403.4===》必须在要查看的网页的地址中使用"https
404:Not Found。没有找到该页面。
5xx:是服务器端错误的
502:网关错误 比如春节抢票:大量请求,服务器无法及时处理==》502
504:网关超时
一般都是和nginx的配置有关,比如nginx设置了超时时间,当在超时时间内,nginx没有收到php-fpm的响应,它就会给客户端返回504
遇到502或504 通常第一件事就是重启服务
15.环境
前三种常见:开发环境(development)、集成环境(integration)、测试环境(testing)、QA验证,模拟环境(staging)、生产环境(production)
开发环境(dev):开发环境是程序猿们专门用于开发的服务器,配置可以比较随意
测试环境 (test):一般是克隆一份生产环境的配置,一个程序在测试环境工作不正常,那么肯定不能把它发布到生产机上
生产环境(prod):是指 正式提供对外服务的,一般会关掉错误报告,打开错误日志。通常说的真实环境
通常一个web项目都需要一个staging环境,一来给客户做演示,二来可以作为production server的一个“预演”,正式发布新功能前能及早发现问题(特别是gem的依赖问题,环境问题等)
16、浏览器的垃圾回收机制
1、垃圾回收的概念 垃圾回收:Javascript代码运行时,需要分配内存空间来存储变量和值。当变量不在参与运行时,就需要系统收回被占用的内存空间,这就是垃圾回收。 回收机制:
javascript具有自动垃圾回收机制,会定期对哪些不在使用的变量,对象所占用的内存进行释放,原理就是找到不在使用的变量,然后释放掉其占用的内存。 javascript中存在两个变量:全局变量和局部变量。全局变量的生命周期会持续到页面卸载;而在局部变量声明在函数中,它的生命周期从函数执行开始,直到函数执行结束,在这个过程中,局部变量会在堆或栈中存储它们的值,当函数执行结束后,这些局部变量不在被使用,它们所占用的空间就会被释放。 不过,当局部变量被外部函数使用时,其中一种情况就是闭包,在函数执行结束后,函数外部的变量依然指向函数内部的局部变量,此时局部变量依然在被使用,所以不会回收。 2、垃圾回收的方式 浏览器通常使用的垃圾回收方法有两种:标记清除,引用计数
3、哪些操作会造成内存泄漏 不合理的使用闭包,从而导致某些变量一直被留在内存当中。 设置了setInterval定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留在内存中,而无法回收。 获取一个DOM元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它无法被回收。 由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。
内存泄漏
寻找内存泄漏工具
performance monitor 工具,在开发者工具里找到更多的按钮,在里面打开此功能面板,这是一个可以实时监控 cpu,内存等使用情况的工具,会比上面只能抓取一段时间内工具更直观一点
分析内存泄漏的原因,还是需要借助开发者工具的 Memory 功能,这个功能可以抓取内存快照,也可以抓取一段时间内,内存分配的情况,还可以抓取一段时间内触发内存分配的各函数情况。
一: 标记清除
这种算法的思想是给当前不使用的值加上标记,然后再回收其内存
算法流程:
1.浏览器再运行的时候会给存储再内存中的所有变量都加上标记
2.去掉环境中的变量以及被环境中引用的变量的标记
3.如果还有变量有标记,就会被视为准备删除的变量
4.垃圾回收机制完成内存的清除工作,销毁那些带标记的变量,并回收他们所占用的内存空间
二:引用计数
这种算法的思想是跟踪记录所有值被引用的次数。javaScript 引擎目前都不再使用这种算法,但再IE 中访问非原生JavaScriopt 对象(如DOM元素)时,这种算法任然可能会导致问题
当代码中存在循环引用现象时,引用计数算法就会导致问题
解除变量的引用不仅可以帮于消除循环引用现象(一个变量使用完之后赋值为null),而且对垃圾收集也有好处。为了确保有效的回收内存,应该及时解除不再使用的全局对象、全局对象属性以及循环引用变量的引用
算法流程:
1.声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值引用次数就是1
2.同一个值又被赋值另一个变量,这个引用类型的值引用次数加1
3.当包含这个引用类型值得变量又被赋值另一个值了,那么这个引用类型的值的引用次数减一
4.当引用次数变成0时, 说明这个值需要解除引用
5.当垃圾回收机制下次运行时,它就会释放引用次数为0 的值所占用的内存
17.http和https
都是一种请求工具,HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全;https:是以安全为目标的HTTP通道,简单讲是HTTP的安全版
HTTPS和HTTP的区别主要如下:
http的连接很简单,是无状态的,端口是:80,http可以输送超文本传输协议,信息是明文传输;
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全,端口是:443
SSL加密
- 对称加密:采用协商的密钥对数据加密
- 非对称加密:实现身份认证和密钥协商
- 摘要算法:验证信息的完整性
- 数字签名:身份验证
18.七层网络模型
应用层
应用层位于 OSI 参考模型的第七层,其作用是通过应用程序间的交互来完成特定的网络应用
该层协议定义了应用进程之间的交互规则,通过不同的应用层协议为不同的网络应用提供服务。例如域名系统 DNS,支持万维网应用的 HTTP 协议,电子邮件系统采用的 SMTP协议等
在应用层交互的数据单元我们称之为报文
表示层
表示层的作用是使通信的应用程序能够解释交换数据的含义,其位于 OSI参考模型的第六层,向上为应用层提供服务,向下接收来自会话层的服务
该层提供的服务主要包括数据压缩,数据加密以及数据描述,使应用程序不必担心在各台计算机中表示和存储的内部格式差异
会话层
会话层就是负责建立、管理和终止表示层实体之间的通信会话
该层提供了数据交换的定界和同步功能,包括了建立检查点和恢复方案的方法
传输层
传输层的主要任务是为两台主机进程之间的通信提供服务,处理数据包错误、数据包次序,以及其他一些关键传输问题
传输层向高层屏蔽了下层数据通信的细节。因此,它是计算机通信体系结构中关键的一层
其中,主要的传输层协议是TCP和UDP
网络层
两台计算机之间传送数据时其通信链路往往不止一条,所传输的信息甚至可能经过很多通信子网
网络层的主要任务就是选择合适的网间路由和交换节点,确保数据按时成功传送
在发送数据时,网络层把传输层产生的报文或用户数据报封装成分组和包,向下传输到数据链路层
在网络层使用的协议是无连接的网际协议(Internet Protocol)和许多路由协议,因此我们通常把该层简单地称为 IP 层
数据链路层
数据链路层通常也叫做链路层,在物理层和网络层之间。两台主机之间的数据传输,总是在一段一段的链路上传送的,这就需要使用专门的链路层协议
在两个相邻节点之间传送数据时,数据链路层将网络层交下来的 IP数据报组装成帧,在两个相邻节点间的链路上传送帧
每一帧的数据可以分成:报头head和数据data两部分:
- head 标明数据发送者、接受者、数据类型,如 MAC地址
- data 存储了计算机之间交互的数据
通过控制信息我们可以知道一个帧的起止比特位置,此外,也能使接收端检测出所收到的帧有无差错,如果发现差错,数据链路层能够简单的丢弃掉这个帧,以避免继续占用网络资源
物理层
作为OSI 参考模型中最低的一层,物理层的作用是实现计算机节点之间比特流的透明传送
该层的主要任务是确定与传输媒体的接口的一些特性(机械特性、电气特性、功能特性,过程特性)
该层主要是和硬件有关,与软件关系不大
19.udp和tcp
| TCP | UDP | |
|---|---|---|
| 可靠性 | 可靠 | 不可靠 |
| 连接性 | 面向连接 | 无连接 |
| 报文 | 面向字节流 | 面向报文 |
| 效率 | 传输效率低 | 传输效率高 |
| 双共性 | 全双工 | 一对一、一对多、多对一、多对多 |
| 流量控制 | 滑动窗口 | 无 |
| 拥塞控制 | 慢开始、拥塞避免、快重传、快恢复 | 无 |
| 传输效率 | 慢 | 快 |
-
TCP 是面向连接的协议,建立连接3次握手、断开连接四次挥手,UDP是面向无连接,数据传输前后不连接连接,发送端只负责将数据发送到网络,接收端从消息队列读取
-
TCP 提供可靠的服务,传输过程采用流量控制、编号与确认、计时器等手段确保数据无差错,不丢失。UDP 则尽可能传递数据,但不保证传递交付给对方
-
TCP 面向字节流,将应用层报文看成一串无结构的字节流,分解为多个TCP报文段传输后,在目的站重新装配。UDP协议面向报文,不拆分应用层报文,只保留报文边界,一次发送一个报文,接收方去除报文首部后,原封不动将报文交给上层应用
-
TCP 只能点对点全双工通信。UDP 支持一对一、一对多、多对一和多对多的交互通信
1、UDP:(如发短信) UDP就像是手机发短信一样,不需要对方和你事先是否连接,不管对方目前在干什么,状态是怎样的,都不关心。直接就填好短信(数据)接收的电话号码(ip地址),然后发送,收没收到也不管,这样的好处就是传送很快,不用双方处于连接状态,就可以发送。(QQ发消息就是如此)但是,缺点也明显,就是不可靠,容易丢失数据,有时候你的qq会接收不到对方发送过来的消息。 2、TCP(打电话) TCP就像是打电话一样,需要事先拨号,当拨号成功后,电话双方就建立起连接了,连接成功后才可以发送数据,就是给两个电脑之间事先建立起连接通道,然后再传输数据(QQ视屏就是如此,需要对方确认连接)如果传输数据过程中数据丢失的话,要重新发送传输,直到对方接收完为止。这样可靠性就强,但是传输效率就不高
20.浏览器常驻的线程:
1.js 引擎线程(解释和执行js 代码, JS内核-V8引擎 ,js引擎用来解释执行js代码 )
2.GUI 线程(绘制用户界面, 与js 主线程是互斥的);
3.http 网络请求线程(处理用户的get, post 等请求等, 返回结果后讲回调函数推入到任务队列);
4.定时器触发线程(setTimeout, setInterval 等待时间结束后把执行函数推入到任务队列中);
5.浏览器事件处理线程(将click, mouse 等交互事件发生后将这些事件放入到事件队列中)。
21代码预编译
javaScript从编译到执行的过程,大致可分为四步:
-
函数声明,整体提升
-
变量声明,声明提升
1. 词法分析 2. 语法分析:检查代码是否存在错误,若有错误,引擎会抛出语法错误。同时会构建一颗抽象语法树(`AST`)。 3. 预编译 4. 解释执行
函数预编译四部曲
1. 预编译开始,会建立`AO(Activation Object)`对象
2. 找形参和变量声明,使其作为`AO`的属性名,值赋予`undefined`
3. 实参和形参相统一(将实参值赋值给形参)
4. 找函数声明,函数名作为`AO`属性名,值赋予函数体
全局的预编译执行三部曲
1. 创建Global Object(以下简写为GO对象);
2. 找形参和变量声明,将变量声明和形参作为GO的属性名,值为undefined;
3. 在全局里找函数声明,将函数名作为GO对象的属性名,值赋予函数体。
js执行时会生成一个临时的对象(AO)
1 AO(activeObject) 存储所有的方法和局部变量,全局变量不会存储在这里
2 寻找var变量声明
3 形参实参赋值
4 寻找函数声明
5 执行代码
22事件轮询event loop
调用栈
web APIS 异步的
callback Queue 回调队列
event loop
js是单线程的
所有js代码都是在主线程执行的
同步任务进入主线程即会执行
异步任务则会进入浏览器的管理模块 (有DOM事件管理模块、ajax请求管理模块、定时器管理模块等)
管理模块一直监视异步任务是否满足条件.如果满足条件则会将对应的回调放入回调队列中(callback queue)
主线程的同步任务执行完后会通过event loop(事件轮询机制)询问callback queue:
如果回调队列中有可执行的回调函数,则将会回调钩到主线程上执行。
如果没有则loop
1.所有的同步任务都是在主线程上执行,形成一个执行栈
2.主线程之外,还存在一个任务队列,只要存在异步任务,就会在任务队列里放置一个事件
3.一旦执行栈里边同步任务代码执行完毕,主线程就会去读取“任务队列",看任务队列有哪些对应的异步任务,结束等待状态,进入执行栈,开始执行
主线程不断重复上面3个步骤
主线程执行完毕以后,从事件队列中去读取任务队列的过程,我们称之为事件循环(Event Loop)
webpack
npm install webpack webpack-cli webpack-dev-server -D、
npx webpack serve //启动命令
npx webpack --config webpack.config.vue.js webpack.config.js改了配置文件的名字
webpack是基于模块的,打包上线,代码转换,压缩代码,代码校验。加快打开速度
pack基本配置
webpack有自己的配置文件webpack.config.js 文件
module.exports = {
mode:'production', //生产模式,默认为生产会执行webpack自带优化 development`开发环境
entry:'./src/main', // 指定入口文件,默认是一般是src/index.js
//如果多入口,对象形式,键名为文件名
// 出口文件,默认值为 ./dist
output: {
// 出口文件的名字
filename:'bundle.js', 【ban都】
// 绝对路径
path:path.resolve(__dirname,'build') //不能用es6
},
plugins:[ 插件
new HtmlWebpackPlugin ({ //自动引入打包输出所有的资源
template:'./public/index.html',
title:'',
dilename:'index.html',
progess:true,//进度条
compress:true,//压缩
}),
],
devServer:{
port: 3000,
open: true,
contentBase: path.resolve(__dirname,'public'),
quiet: true
},
//设置webpack如何处理不同类型的模块
module: {
//配置规则数组。在创建模块时,匹配对应的规则并应用loader处理模块
//loader处理那些非js文件,weboack只能处理js于json
rules: [
{
test: /.css$/i , //匹配模块文件路径
use: [ MiniCssExtractPlugin.loader,'css-loader' ] //使用loader 右到左
},
{
test: /.s[ac]ss/i,
use: [ MiniCssExtractPlugin.loader,'css-loader','sass-loader' ]
}
]
},
optimization:{
minimize: true, //开发环境必须为true ,生产环境无所谓
minimizer:[
`...`, //这个隐藏的话,js不会被压缩
new CssMinimizerPlugin(),
]
},
devtool:'' //准确报错,因为打包时会导致打印时代码不是原来的行(优化注释)
}
打包流程
先找到入口文件,递归查询入口文件的相关文件,执行操作,最后构建和打包
- 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数;
- 初始化编译:用上一步得到的参数初始化 Compiler 对象,注册插件并传入 Compiler 实例(挂载了众多webpack事件api供插件调用)
- AST & 依赖图:从入口文件(entry)出发,调用AST引擎(acorn)生成抽象语法树AST,根据AST构建模块的所有依赖;
- 递归编译模块:调用所有配置的 Loader 对模块进行编译;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统;
loader
- style-loader 使用@import解析
- css-loader,sass-loader, less-loader
- ts-loader vue-loader
file-loader:处理文件路径,并返回其打包后的urlurl-loader:(图片base64),但当文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
plugin
- clean-webpack-plugin 用于在打包前清理上一次项目生成的 bundle 【ban都】文件
- mini-css-extract-plugin 将CSS提取到单独的文件中,从js提取
- extract-text-webpack-plugin 抽离 js中的css 样式,将 css 成生文件
- UglifyJsPlugin 【a g fiy --jsPlugin】是 vue-cli 默认使用的压缩代码方式,用来对 js 文件进行压缩,从而减小 js 文件的大小,加速 load 速度
- css-minimizer-webpack-plugin
Webpack优化
-
(自带)tree-sharking 无用代码不参与打包移除 JavaScript 中的没有使用上的代码
-
(自带)scope-hosting 只打包结果,减少打包体积,创建的函数作用域减少
happypack实现多线程打包,加速代码构建 npm install --save-dev happypack
分解子进程中去并行处理,进程处理完成后把结果发送到主进程中,从而减少总的构建时间
oneOf
打包时每个文件都会经过所有 loader 处理,虽然因为
test正则原因实际没有处理上,但是都要过一遍。比较慢。顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了。
rules: [ { oneOf: [ ], }, ],Thead(多线程打包)
npm i thread-loader -Dconst os = require("os"); const TerserPlugin = require("terser-webpack-plugin"); // cpu核数 const threads = os.cpus().length; { test: /.js$/, // exclude: /node_modules/, // 排除node_modules代码不编译 include: path.resolve(__dirname, "../src"), // 也可以用包含 use: [ { loader: "thread-loader", // 开启多进程 options: { workers: threads, // 数量 }, }, { loader: "babel-loader", options: { cacheDirectory: true, // 开启babel编译缓存 }, }, ], },SourceMap(源代码映射)
构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源
开发模式:cheap-module-source-map
优点:打包编译速度快,只包含行映射
缺点:没有列映射
module.exports = {
// 其他省略
mode: "development",
devtool: "cheap-module-source-map",
};
生产模式:source-map
优点:包含行/列映射
缺点:打包编译速度更慢
module.exports = {
// 其他省略
mode: "production",
devtool: "source-map",
};
优化用到的插件
speed-measure-webpack-plugin 速度分析 用 wrap()方法将配置包起来
优化plugn
-
动态链接库 比如项⽬中引⼊了很多第三⽅库,抽离出来打包到dll⽂件中,当需要导⼊的模块存在于某个dll中时,去dll中获取。
dllPlugin 配置一些dll动态链接库,生成一个json文件(manifest),⽤于打包出⼀个个单独的动态链接库⽂件
之后通过
dllRerencePlugin【瑞 length】 配置这个json文件,在文件中引⼊DllPlugin插件打包好的动态链接库⽂件 -
抽离公共代码块optimize-css-assets-weback-plugin
[ˈ奥破踢妹字] - css - a sets
把css从js中抽离出来,相同的css,引用两次,只打包一次
-
语言包:通过IgnorePlugin ,比如有很多语言,不需要打包所有的语言
loader和plugin区别
plugin是插件,赋予webpack新的功能
loader是一个函数,识别后缀文件进行转译
规范
eslint 规范代码=》缩进空格=》文件命名
- 提升开发体验
- 使用
Source Map让开发或上线时代码报错能有更加准确的错误提示。
- 提升 webpack 提升打包构建速度
- 使用
HotModuleReplacement让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。 - 使用
OneOf让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。 - 使用
Include/Exclude排除或只检测某些文件,处理的文件更少,速度更快。 - 使用
Cache对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。 - 使用
Thead多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
- 减少代码体积
- 使用
Tree Shaking剔除了没有使用的多余代码,让代码体积更小。 - 使用
@babel/plugin-transform-runtime插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。 - 使用
Image Minimizer对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
- 优化代码运行性能
- 使用
Code Split对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。 - 使用
Preload / Prefetch对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。 - 使用
Network Cache能对输出资源文件进行更好的命名,将来好做缓存=,从而用户体验更好。 - 使用
Core-js对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。 - 使用
PWA能让代码离线也能访问,从而提升用户体验。
其他
判断是pc还是移动端
if(/Android|webOS|iPhone|iPod|BlackBerry/i.test(navigator.userAgent)) {
//手机端
console.log(1);
} else {
console.log(0);
}
判断是否为IE
function isIE() { //ie?
if (!!window.ActiveXObject || "ActiveXObject" in window){
alert("我是货真价实的IE浏览器!");
return true;
}else{
alert("我不是IE!");
return false;
}
}
isIE();
上线项目出现bug处理
项目上线出现bug,简单粗暴解决版。首先在master上拉一个hotfix的分支,在上面直接修复,合并到master。推到线上
项目上线出现bug复杂解决版。回退到上一个tag的版本,再上一个tag的版本,进行重新的灰度部署。按量分配,比如说按10%的用户量去部署新功能,部署当前的这个项目。如果10%通过了,再部署到全部 项目更新迭代的周期。一般是以改动。Middle version(中间版本)为主。时间间隔大概一周到两周不等,bigversion(大版本)。大版本一般一个月以上,smallversion(小版本) 小版本的话直接当修复小bug的方式去更新
HTTP状态码
状态码100 客户端继续其请求。
状态码101 切换协议,服务器根据客户端的请求切换协议,只能切换到更高级的协议。
状态码200 请求成功,一般用于GET和POST请求方式。
状态码201 成功求情并创建了新的资源。
状态码202 已接受请求,但是未处理完成。
状态码203 非授权信息,请求成功,但是返回的meta信息不再原始的服务器,而是一个副本。
状态码204 无内容,服务器处理成功,但是未返回内容,再未更新新网页的情况下,
可确保浏览器继续显示当前文档。
状态码205 重置内容,服务器成功处理,用户浏览器应重置文档视图,可通过此返回码清除浏览器的表单域。
状态码206 服务器成功处理了部分GET请求。
状态码300
状态码301 永久移动,请求的资源被永久移动到新的URI,返回的信息会包括新的URI,
浏览器会自动定向到新的URI,今后任何新的请求都应用使用新的URI代替。
状态码302 临时移动,与301类似,但是资源是临时被移动,客户端应继续使用原有的URI。
状态码303 查看其他地址,与301类似,使用GET和POST请求查看。
状态码304 未修改,锁清秋的资源未修改,服务器返回此状态时,不会反悔任何资源。
客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。
状态码305 使用代理。所请求的资源必须通过代理访问。
状态码307 临时重定向,和302类似,使用GET的方式去重定向。
状态码400 客户端的求情语法错误,服务器无法理解。
状态码401 请求要求用户身份验证。
状态码402 保留状态码,未启用。
状态码403 服务器理解请求客户端的请求,但是拒绝执行此请求。
状态码404 服务器无法根据客户端的请求找到对应的资源。
状态码405 客户端请求的方法被禁止。
状态码406 服务器无法根据客户端请求的内容特性完成请求。
状态码407 请求要求代理的身份认证,与401类似,但是请求者应当使用代理进行授权。
状态码408 服务器等待 客户端发送的请求时间过长,请求超时。
状态码409 服务器完成客户端的PUT请求是肯恩返回此代码,服务器处理请求的时候发生了冲突。
状态码410 客户端请求的 资源已经不存在,410与404不同,如果资源以前有,
现在被永久删除了可使用410代码,网站设计人员可通过301代码指定资源的新位置。
状态码411 服务器无法处理客户端发sing的不带Content-Length的请求信息。
状态码412 客户端请求信息的先决条件错误。
状态码413 由于请求的尸体过大,服务器无法处理,因此拒绝请求。为防止客户端的连续请求,
服务器可能会关闭连接。如果只有服务器暂时无法处理,则会包含一个Retry-After的相应信息。
状态码414 请求的URI过长,服务器无法处理。
状态码415 服务器无法处理请求附带的媒体格式。
状态码416 客户端的请求范围无效。
状态码417 服务器无法满足Expect的请求头信息。
状态码500 服务器内部错误无法请求。
状态码501 服务器不支持请求的功能无法完成请求。
状态码502 充当网关或者代理的服务器,从远端服务器接受到了一个无效的请求。
状态码503 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状况是临时的,
并且将在一段时间以后恢复。如果能够预计延迟时间,那么响应中可以包含一个 Retry-After 头
用以标明这个延迟时间。如果没有给出这个 Retry-After 信息那么客户端应当
以处理500响应的方式处理它。
状态码504 充当网关或者代理的服务器,未及时从远端服务器获取请求。
状态码505 服务器不支持请求的HTTP协议的版本,无法完成处理。
qiankun
主应用
import { initGlobalState, MicroAppStateActions } from 'qiankun';
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
微应用:
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
props.setGlobalState(state);
}