前端知识点
实际面试题
1.组件封装过哪些组件
2.v-for,key属性的作用和原理
-
Vue中为v-for提供了一个属性,key,在写v-for的时候,都需要给元素加上一个key属性,这个key属性必须是唯一的标识,并且给key赋值的内容不能是可变的。
-
一句话来讲:key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点
-
如果不用key,Vue会采用就地复地原则:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。
-
如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed
vue不会去改变原有的元素和数据,而是创建新的元素然后把新的数据渲染进去
一、key属性的作用:
1. 提升v-for渲染的效率
2. 提高渲染性能
若对数据进行:逆序添加、逆序删除等破坏顺序操作, 会产生没有必要的真实DOM更新,虽然界面效果没问题, 但效率低。
3.避免数据混乱的情况出现
如果结构中还包含输入类的DOM: 会产生错误DOM更新 ,出现数据混乱。
二、 开发中如何选择key?
1.最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2.如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,只是简单的展示数据,用index也是可以的。
3.如果对数据的逆序添加、逆序删除等破坏顺序操作,用index作为key值,效率低。
3.echart自适应问题
-
在了解echarts绘制机制,echarts图形只绘制一次,且绘制时自动获取父级大小填写宽度,考虑让echarts延迟绘制 使用setTimeout
-
echart图表本身提供了一个resize的函数,我们只需要监听页面(window)的resize(事件)变化以后,去执行echarts的resize方法即可重绘canvas(画布),从而实现对应自适应效果。请看代码中的注释步骤
-
记得移除绑定的监听resize事件
<script> // 第一步,引入echarts插件以供使用 import Echarts from "echarts"; export default { data() { return { myChart: null, // 定义变量用来存放echarts实例 option: { // 配置项写在data里面方便管理 title: { text: "ECharts 示例", }, tooltip: {}, xAxis: { data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"], }, yAxis: {}, series: [ { name: "销量", type: "bar", data: [5, 20, 36, 10, 10, 20], }, ], }, }; }, mounted() { // 第二步,在页面加载渲染的时候执行echarts画图方法 this.drawEcharts(); }, methods: { drawEcharts() { // 第三步,通过echarts的init方法实例化一个echarts对象myChart,并,保存在data变量中 this.myChart = Echarts.init(document.getElementById("echart")); // 第四步,执行myChart的setOption方法去画图,当然至于配置项,我们要提前配置好,这里的配置项 // 写在data中,方便我们在一些事件中去修改对应配置项,比如点击按钮更改配置项数据 this.myChart.setOption(this.option); // 第五步,在页面初始化加载的时候绑定页面resize事件监听。补充resize事件:resize事件是在浏览器窗口大小改变时,会触发。 // 如当用户调整窗口大小,或者最大化、最小化、恢复窗口大小显示时触发 resize 事件。 // 我们一般使用这个事件去做窗口大小与对应元素的大小适配 window.addEventListener("resize", () => { // 第六步,执行echarts自带的resize方法,即可做到让echarts图表自适应 this.myChart.resize(); // 如果有多个echarts,就在这里执行多个echarts实例的resize方法,不过一般要做组件化开发,即一个.vue文件只会放置一个echarts实例 /* this.myChart2.resize(); this.myChart3.resize(); ...... */ }); }, beforeDestroy() { /* 页面组件销毁的时候,别忘了移除绑定的监听resize事件,否则的话,多渲染几次 容易导致内存泄漏和额外CPU或GPU占用哦*/ window.removeEventListener("resize", () => { this.myChart.resize(); }); }, }, }; </script>
4.el-tree懒加载,回显问题
5.iframe优缺点
iframe 标签承载了一个单独的嵌入的窗口,它有自己的 document 和 window,当我们访问嵌入的窗口中的东西时,浏览器会检查 iframe 是否具有相同的源。如果不是,则会拒绝访问(对 location
进行写入是一个例外,它是会被允许的)。
同源策略会限制 窗口(window) 和 frame 之间的通信,因此首先要知道同源策略。同源策略目的是保护用户信息免遭信息盗窃:加入小王有两个打开的页面:一个是 shop.com,一个是 email.com。小王不希望 shop.com 的脚本可以读取 mail 的邮件,这时同源策略就起作用了。
iframe的优点:
1.iframe能够原封不动的把嵌入的网页展现出来。
2.如果有多个网页引用iframe,那么只需要修改iframe的内容,就可以实现调用每一个页面的更改,方便快捷。
3.网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe嵌套,可以增加代码的可重用。
4.如果遇到加载缓慢的第三方内容,如图标或广告,这些问题可以由iframe来解决。
5.iframe会堵塞主页面的Onload事件。
6.iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。
iframe的缺点:
1.会产生很多的页面,不容易管理
2.iframe框架结构个数多的话,可能会出现上下左右滚动条,会分散访问者的注意,用户体验度差。
3.代码复杂,无法被一些搜索引擎索引到,搜索引擎爬虫还不能很好的处理iframe中的内容,所以不利于搜索引擎优化。
4.很多移动设备无法完全显示框架,设备兼容性差
5.iframe框架页面会增加服务器的http请求,对于大型网站是不可取的
现在基本上都是用Ajax来代替iframe,所以iframe已经渐渐退出了前端开发**
面试题1
1、水平垂直居中的方式
- 第一种,用纯flex布局吧。
直接给对应的父盒子加一个display:flex开启布局。然后使用主轴居中;侧轴居中。(两者都是添加给父盒子的。)
- 第二种,用flex+margin的方式。
还是同样的先给父盒子添加一个display:flex开启布局,然后给子盒子添加一个margin:auto就可以水平垂直居中了。
- 第三种,用定位+margin的方式
首先根据子绝父相的方式进行定位,然后子盒子的绝对定位是top:50%,left:50%,但由于这里的50%都是以盒子的左上角为中心点的,所以需要用margin来返回自身宽高的一半,也就是margin-top:负的子盒子高度的一半。margin-left:负的子盒子宽度的一半。
- 第四种,用定位+transform的位移操作
这种方法可以说是上一种方法的改进,他省略了我们自己计算盒子宽高一半的过程。因为transform的位移,可以使用百分比的方式,并且百分比的对象就是盒子本身。也就是在子绝父相的情况下,直接使用transform:translate(-50%,-50%)即可。
- 第五种,用定位+margin的方式
在子绝父相的情况下,给子盒子添加上下左右都设置为0,最后再给子盒子添加一个margin:auto,就可以达到水平垂直居中的效果。
像很多人都会说直接采用margin:auto的方式,但是当盒子没有高度的情况下,这样的方式是不可取的。所以基本不会采用。
2、盒模型
盒模型由四个部分组成,分别是内容、内边距、外边距和边框
盒模型的分类有两种,content-box和border-box,可以通过box-sizing来设置。默认值是content-box。
两者的区别主要是对盒子的大小计算方式不同。
●content-box又被称为w3c标准盒模型。
○width设置的是内容的宽度
○实际宽度=padding+border+width
●border-box又被称为c3模型/ie盒模型等
○width设置的是实际的宽度
○width=内容+padding+border
3、关于flex:1
flex其实是flex-grow、flex-shrink、flex-basis 这三个属性的简化版
flex-grow:用来增大盒子。当父盒子有剩余空间的时候,可以利用flex-grow来设置盒子增大的比例。比如,有两个子盒子,剩余200,盒子a的flex-grow=1,盒子b的flex-grow=3,那么盒子a在原有的基础上加50,盒子b在原有的基础上加150.
flex-shrink:用来缩小盒子。当总数超过父盒子的时候,可以利用flex-shrink来设置盒子缩小的比例。比如,有两个子盒子,超出200,盒子a的flex-shrink为1,盒子b的flex-shrink为3,那么盒子a在原有的基础上减去50,盒子b在原有的基础上减去150.
flex-basis:用来设置盒子的基础宽度。(如果存在,会覆盖原本的width)直接用px设置大小。
flex:1表示1,1,0% 可扩大可缩小,一般为平均分
flex:auto表示1,1,auto 可扩大可缩小,根据内容的大小来分
flex:0表示0,1,0% 不可扩大,可缩小,最小内容宽度(一般为一个字的宽度)
flex:none表示0,0,auto 不可扩大,不可缩小,一般为内容本身的宽度
auto表示的是容器原本的大小,0%表示为零尺寸的。
4、css3的新特性
css3的新特性,那太多了。我讲几个常用的吧
●(1)选择器
○新增了属性选择器,伪类选择器等等,最常用的应该是
■:hover鼠标移动到元素上面
■:nth-child(n)某个元素的第n个子元素
■:last-child 某个元素的最后一个子元素
■:first-child 某个元素的第一个子元素
●(2)新样式
○边框方面
■border-radius:圆角边框,如果设置50%则表示为圆形
■box-shadow:添加阴影
○背景方面
■background-size图片的缩放,cover表示要铺满整个盒子,contain表示宽高有一个铺满就结束
○颜色方面
■rgba(),hsla()以及opacity的透明度
●(3)转换transform
○主要是位移translate、旋转rotate、缩放scale几个技能
●(4)过渡动画transition
○ CSS属性,花费时间,运动曲线(默认ease),延迟时间(默认0)
●(5)自定义动画animation,和transition差不多,最大的区别就是自定义动画不需要触发,定义了就会有动画
●(6)flex布局
5、BFC
.BFC就是块级格式上下文,它是一种属性,可以让渲染区域独立,并且渲染区域中的布局不会影响外界。
BFC的触发情况有很多种,像overflow,float,position,display等等,最常用的应该是overflow:hidden,position:absolute吧。
BFC解决的问题有很多。
情况一:兄弟上下重叠
当上下两个盒子同时拥有上下间距,不是取两个间距之和,而是取最大值。
解决方式就是给盒子添加一个父盒子,并且给父盒子开启BFC。
情况二:外边距塌陷
就是父盒子不存在boder或者是padding的时候,子盒子有一个margin-top,那么父盒子也会对这个指令进行生效。解决方式是给父盒子开启BFC模式,当然,可以给父盒子加一个border或者padding,但是这样容易改变原本的样式,不可取。
情况三:左右布局问题
当A盒子浮动,B盒子不浮动的时候,两者就会产生覆盖。解决的方式就是给非浮动的盒子添加一个BFC模式。一定是给没有浮动的盒子进行添加。
情况四:清除浮动
在父盒子没有设置高度的时候,高度是由内容撑开的,所以子盒子浮动以后,父盒子就会没有高度。只要给父盒子添加BFC布局即可。
6、rem适配原理,响应式布局
rem是相对长度单位,相对于根元素的fomt-size计算值的大小。
1rem=根节点的字体大小
原理:在不同的屏幕下,修改根节点的字体大小。
在项目中:下载一个flexible包,即可动态更改html的字体大小
7、重绘和回流(重排)
重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局步骤来确定页面上所有内容的大小和位置,确定布局后,将像素绘制到屏幕上。
其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。
如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。
8、状态码 3
200;请求成功
201:请求成功,但是逻辑有问题
204:请求正常处理,但是没有数据可以返回
304:协商缓存
400:传的参数出现问题
401:一般都是登录过期
404:资源不存在
500:服务器出错
3XX表示重定向,表明浏览器需要执行某些特殊的处理以正确处理请求。
301永久移动,302临时移动
303和302状态码有着相同的功能,但303明确表示客户端应当采用get方法获取资源,这点与302状态码有区别。
9、浏览器的缓存机制
浏览器会将请求后的资源进行存贮为离线资源,当下次需要该资源时,浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求。
--作用:
●减少了不必要数据的传输、降低服务器的压力
●加快了客户端访问速度
●增强用户体验
缓存机制分为强缓存和协商缓存
-
强缓存:
概念:不向服务端发送请求,强制使用缓存数据
实现方式:后端在响应头中返回 Expires 和 Cache-Control
-
Expires :http 协议 1.0 的字段,缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点
缺点:浏览器使用 expires 到期时间和本地时间进行对比,如果本地时间被修改或者和服务器时间差距较大,造成不准确的问题
-
Cache-Control: HTTP 1.1 的字段,约定过期时间的相对时间。
!当 cache-control 和 expires 同时存在 cache-control 的优先级会比 expires 高。
-
-
协商缓存:
当强缓存失效后,会使用协商缓存
协商缓存由服务器决定是否使用缓存
-
向服务器发送请求资源并携带标识
●Etag 字段:表示请求资源在服务器的唯一标识,浏览器可以根据 ETag 值缓存数据,下次请求的时候以 If-None-Match 字段请求
●Last-Modified 字段:用于标记请求资源的最后一次修改时间
-
服务器会进行判断浏览器缓存的资源是否真的失效(也就是是否更新)
●服务端已经更新,返回 200,重新返回最新资源和缓存标识
●浏览器再次存入缓存
●后续再次从强缓存开始
-
缓存时间到了,但是资源没更新,就还使用本地的,直接返回 304
-
10、slice,substr,substring
三者都是截取的意思。都可以截取字符串,slice可以截取数组。
第一个参数为开始位置,substr的第二个参数是个数,其他两个第二参数表示结束位置。
第一个参数大于第二个参数时,substring会自动调换顺序,slice会在第一参数大于第二参数时返回空,substr不在乎两者的比较。
参数<0,substring无效,slice会给负数加上数据长度,substr第一参数为负数也会加上数据长度,第二参数不可为负数。
11、slice,splice,split
splice
●可以进行增删改
●修改原数组,返回修改的内容
●第一个参数为起始索引,第二个参数是个数,第三个参数开始是添加或修改的值
slice
●进行截取
●原数组不变,返回截取的内容
●第一个参数为起始索引(包括),第二个参数为截止索引(不包括)
○start (可选)
如果start为负数,则加上数组长度
如果start被省略,则从索引 0 开始。
如果start超出原数组的索引范围,则会返回空数组。
○end (可选)
如果 end 被省略,则 slice 会一直提取到原数组末尾。
如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。
如果end小于start,则会返回空数组。
split:字符串 => 数组
○字符串的方法,不是数组的方法。
○返回一个字符串数组。
○str.split(分隔符)
12、数据类型,检测数据类型 3
-
简单数据类型和复杂数据类型。
简单数据类型有number数字,boolen布尔值,字符串,null,undefined,还有es6新增的symbol唯一值。
复杂数据类型有object对象,array数组,function函数以及特殊对象正则RegExp和日期dDate。
-
检测数据类型
第一种:typeof可以正常检测出:number、boolean、string、object、function、undefined、symbol、bigint
- 检测基本数据类型,null会检测object,因为null是一个空的引用对象
- 检测复杂数据类型,除function外,均为object
- 对象,数组以及null无法区分都会被检测为object。
- infinity和NaN会被识别为number
第二种:instance of,检测引用数据类型,用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。写法就是:某个实例对象A instanceof 某个构造函数B。检测null和undefined会返回false
第三种 :toString.call(xxx)
- toString是Object原型对象上的一个方法,必须通过Object.prototype.toString.call(xxx)来获取。
- 因为从原型链的角度讲,所有的对象最终都会指向object。但大部分的对象,本身也可能有toString的方法,这样就会导致没有查到object的toString方法就会被自身的发放给终止掉。
- 需要用call来强制执行,改变方法的this指向所要检测的数据。
- 可以检测出所有数据类型
第四种:是根据对象的constructor(con s jua k t)来判断该对象实例由哪个构造函数创建的,检测的是由字面量方式创建出来的数据类型。(不能用于检测由自定义构造函数new出来的数据)
obj.constructor=== Object //true
第五种:isArray()检测数组
13、递增递减
x++ 先返回值后递增
++x 先递增后返回值
undefined++ = NaN
14、作用域
-
作用域就是所作用的一个范围。作用域和函数的定义有关。而和函数的调用有关的是this的指向
-
作用域的作用是提高了程序逻辑的局部性,增强了程序的可靠性,减少了命名的冲突。
-
有全局作用域和局部作用域两种。
- 全局作用域是作用于所有代码执行的环境,局部作用域是作用于函数内部的代码环境。
-
相对应的还有全局变量和局部变量。
- 全局变量可以在任何一个地方使用,只有在浏览器关闭的时候才会被销毁,比较占内存。
这里有一个特殊就是,如果在函数内部,没有声明直接赋值的变量,也属于全局变量。 - 局部变量是在函数内部使用的,只有所在函数被执行,才会初始化,函数执行结束,就会被销毁。因此比较节省内存空间。
- 全局变量可以在任何一个地方使用,只有在浏览器关闭的时候才会被销毁,比较占内存。
-
作用域链
内部函数访问外部函数的变量,采取的就是链式查找的方式,站在目标角度出发,一层一层往外找,并追求就近原则
15、预解析
JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行.
预解析会把变量和函数的声明在代码执行之前执行完成。
预解析有两种,变量预解析和函数预解析
-
变量预解析,也就是变量的提升,变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。(只提升声明,不提升赋值,提升的是var的,和let,const无关)
-
函数预解析,函数的声明会被提升到当前作用域最上面,但是不会调用函数
-
函数预解析优先级大于变量预解析(先声明函数再声明变量)
16、new的过程 3
-
内存中创建一个空对象
-
将这个空对象的__
proto
__指向了 构造函数的Prototype属性,obj.__proto__=Person.prototype
-
构造函数内部的this被赋值为这个新对象(即this指向新对象)。
-
调用构造函数,执行构造函数内部的代码(给新对象添加属性和方法)
-
构造函数默认返回return this ,也就是this的实例化对象
●如果写了return,那么看return后面的数据类型
●如果是简单数据类型,忽略return简单数据类型,return this
●如果是复杂数据类型,忽略return this,return复杂数据类型
17、静态成员和实例成员
实例成员就是构造函数通过this添加的成员,只能够通过实例化的对象来访问
静态成员就是在构造函数本身上添加的成员,只能够通过构造函数来访问, 如Object.keys()
18、关于原型链、构造函数
-
构造函数的prototype和其实例的__proto__是等价的,都指向原型对象。
-
构造函数就是可以用来new的函数。(箭头函数不能当做构造函数)
-
也是在new的过程中,将实例.__proto__指向了对应的构造函数的prototype
1、每个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数所拥有的。
(通过构造函数创建的实例,实例再去访问属性的时候,自身没有就会去构造函数的prototype上面寻找。 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。也可以解决构造函数方法浪费内存的问题。)
2、每一个对象(除了null)都会有一个__proto__属性,指向构造函数的prototype。
(之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 的存在。 __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype。)
3、每个原型对象都有一个constructor 属性,指向构造函数。
(constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。)
原型链的底层基础,首先是创建一个构造函数,然后new得到一个实例。构造函数的prototype和其实例的__proto__都指向原型对象。这就形成了一个三角形。并且原型对象的constructor指回构造函数本身。又因为原型对象也是一个对象,也有__proto__属性,这样一层一层往上就形成了原型链。原型链的最上面一层是Object的原型对象。再往上就指向了null。
实例对象在查找属性的时候,如果找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查不到,就去找原型的原型,直至查到顶层的Object.prototype,这也就是原型链的概念。或者也可以说顶层是Object.prototype.proto,也就是null。
19、继承有哪几种方式 3
继承和多态、封装共为面向对象的三个基本特征。继承可以使子类具有父类的属性和方法。
-
第一种:原型链继承
- 关键核心:让父类的实例作为子类的原型。
SonFn.prototype=new FatherFn()
- 将子类共有的方法,创建在父类的原型对象上。
- 将子类共有的属性,创建在父类的构造函数内。
- 缺点:
- 子类new实例的时候无法向父类传递参数。
- 如果原型属性值是复杂数据类型,那么该属性会被所有的实例共享。
- 因为重写了子类的原型对象,所有子类的原型对象就没有了constructor属性,需要手动加
- 关键核心:让父类的实例作为子类的原型。
-
第二种:借用构造函数继承
-
关键核心:在子类构造函数中使用call()调用父类构造函数
function Animal(name,age){ this.name=name, this.age=age, } function Cat(name,age){ Animal.call(this,name,age) //new Cat时即将父类的this改成指向子类实例对象 } const cat = new Cat('花花',12) console.log(cat) //{name:'花花',age:'12'}
-
让父类的this指向子类的实例,在子类的构造函数中,用call改变this的指向
-
缺点:
只能实现属性的继承,不能继承方法(因为方法不写在构造函数中,写在原型中,原型链继承可以)
-
第三种:组合式继承(组合上两种方法)
- 关键核心:子类构造函数中使用 call() 调用父类的属性,让父类的实例作为子类的原型(能够调方法)
通俗的讲,就是将原型链继承中的方法继承和借用构造函数继承中的属性继承进行组合
- 缺点:多次调用了父类构造函数。原型链继承方法的时候,会创建一个没有用的父类构造函数,比较浪费。
第四种:寄生式组合继承(组合改进第三种方法)
-
关键核心:使用 Object.create() 来解决多次调用父类构造函数问题
-
用Object.create(参数),作用是创建出来一个新空对象,他的原型对象是括号内的参数。
-
子类的prototype=Object.create(父类的prototype), (用于替换子类的prototype=父类的prototype)
-
也就是新对象的原型对象为父类的prototype,而实例的原型对象指向了新对象,也就是实例的原型对象的原型对象为父类的prototype
第五种:es6类继承
使用class 父类,并且用extends使子类继承父类。
class Person {
say() {
console.log('说话了')
}
}
class Child extends Person {}
const child = new Child()
child.say()
20、常用的es5数组方法 3
第一种forEach
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,没有返回值。
第二种indexOf
第一个参数是需要查找的值,第二个参数是从第几个索引开始查找。
原数组不变,返回值为查到的索引,如果没有该元素,返回-1
第三种some
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回布尔值。
有满足条件的,则返回true,并终止循环。
第四种every
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回布尔值。
全部满足条件返回true,有不满足的,返回false并终止循环。
第五种map
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回新数组。
新数组长度和原数组一致,新数组由原数组中的每个元素进行return之后得到,没有返回值,会返回undefind
第六种filter
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回新数组
新数组长度小于原数组,新数组的数组来自于条件判断为true的原数组数组。
第七种reduce(最复杂的一种)
reduce(callback,[initialValue])
有两个参数,第一参数是回调函数,第二个参数是第一次回调函数的参数1。
第二个参数是可选的,如果没有,那么reduce会从索引1的地方开始执行回调。
回调函数有四个参数,
●参数1:上一次返回的值。
●参数2:当前项
●参数3:当前索引
●参数4,:调用reduce的数组本身
原数组不变,返回最后一次return的值
口诀:
单纯循坏数组,使用forEach
循环数组得到一个新数组,新数组和循环的数组长度一致,此时用map
循环数组得到一个新数组,新数组少于循环的数组,此时用filter
查找后需要返回true和false的,复杂数据类型(里面是对象的)用some
简单数据类型的,用includes
查找后需要返回索引的,简单数据类型,用indexOf
复杂数据类型,用findIndex
21、本地存储
localStorage, sessionStorage, cookie
cookie:
●生命周期:默认自己添加,可设置失效时间,关闭浏览器后失效。
●存储数据大小:4kB左右
●服务器端通信:参与通信,每次自动携带在请求头中,如果使用cookie保存过多数据会带来性能问题
●易用性:很不友好,获取某个cookie,会获取到整个cookie字符串‘key:value;key:value’的形式,一般使用第三方库js-cookie进行处理
localStorage和sessionStorage除了生命周期,其他都相同
●生命周期:localStorage除非手动清除,否则永久存储。
sessionStorage仅在当前会话下有效,关闭页面和浏览器后会被清除。
●存储数据大小:一般为5-20MB
●服务器端通信:仅在浏览器中保存,不参与和服务器的通信
●易用性:还可以,但存储的时候只能存字符串(用到JSON.stringify和JSON.parse进行处理)
22、this指向
this指向除了箭头函数,和函数的定义无关,和函数的调用有关。(箭头函数除外,其定义就指向window)
- this指向四种情况:
●直接调用(实际是win对象调用),this指向window,严格模式下指向undefined
●谁调用指向谁,对象调用this指向对象
●构造函数new时 ,this指向实例(new的第三步)
●箭头函数的this指向上下文
-
改变fn中this指向的方法
●fn.call(this指向的,fn需要的实参) //如:可用于原型链继承
●fn.apply(this指向的,[fn需要的实参数组]) //如:用于合并数组
arr1.push.apply.(arr1,arr2)
●fn.bind(this指向的),bind不会调用函数,而是根据该函数生成一个新函数,并且新函数的this指向了()内的对象
23、异步
- 为什么需要异步:js 是单线程的,也就代表 js 只能一件事情一件事情执行,那如果一件事情执行时间太久,后面要执行的就需要等待,需要等前面的事情执行完成,后面的才会执行。所以为了解决这个问题,js 委托宿主环境(浏览器)帮忙执行耗时的任务,执行完成后,在通知 js 去执行回调函数,而宿主环境帮我们执行的这些耗时任务也就是异步任务
js 本身是无法发起异步的,是委托给宿主环境发起异步的,但是 es5 之后提出了 Promise 可以进行异步操作
- js执行流程如下:
-
主线程先判断任务类型:如果是同步任务,主线程自己执行,如果是异步任务,交给宿主环境(浏览器)执行
-
宿主环境进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列。
-
等主线程任务全部执行完后,会取任务队列中的任务,根据先进先出原则
-
在任务队列中取出来的任务,会回到主线程执行,执行完成后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训
-
而我们所提到的异步任务,也分为宏任务和微任务。
- 由宿主环境发起的异步被称为宏任务。(setTimeOut、setInterval都是webApi,请求XHL也是浏览器发起)
- 由js自身发起的异步被称为微任务。(promise ,await下面的 ,nextTick)
(判断由谁发起的,看是否是ecma的,如果是,则表示是js自身发起的。也就被称为微任务。但是我们用到的微任务基本只有promise————还有两个个特殊情况MutationObserver(监听DOM),queueMicrotask(用于生成微任务),虽然是由web发起的,但是也是微任务)
- promise不是异步的,(.then)才是异步的微任务
- await下面的也可以看作是微任务
-
执行顺序
-
先执行宏任务(将整个script标签的代码段看作是一次宏任务,紧接着执行微任务)----同步任务(主线程)
-
宏任务执行完后看微任务队列是否有微任务
-
没有微任务执行下一个宏任务
-
有微任务将所有微任务执行
-
执行完微任务,执行下一个宏任务
解决异步,最常用的就是promise
24 Promise 的理解
-
Promise对象用于表示一个异步操作的最终完成 (或失败)及其结果值。使得控制异步操作更加容易。可以将异步操作以同步操作的流程表达出来,也是一种为了避免回调地狱的异步解决方案。
-
Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败) 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 回调地狱
回调函数中嵌套回调函数的情况就叫做回调地狱。
回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
一、Promise是什么?
Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。
ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
二、Promise是为解决什么问题而产生的?
promise是为解决异步处理回调金字塔问题而产生的
三、Promise的两个特点
1、Promise对象的状态不受外界影响
1)pending 初始状态
2)fulfilled 成功状态
3)rejected 失败状态
Promise 有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态
2、Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled或者由pending变成rejected
四、Promise的三个缺点
1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
五、Promise在哪存放成功回调序列和失败回调序列?
1)onResolvedCallbacks 成功后要执行的回调序列 是一个数组
2)onRejectedCallbacks 失败后要执行的回调序列 是一个数组
以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
每次实例then的时候 传入 onFulfilled 成功回调 onRejected 失败回调,如果此时的状态是pending 则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中,如果此时的状态是fulfilled 则onFulfilled立即执行,如果此时的状态是rejected则onRejected立即执行
上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行
25为什么nextTick优先是微任务
-
什么是
nextTick
:定义: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM -
优先是
Promise.then
方法,是个微任务,这样可以避免多一次队列(同步代码执行后就会清空一次微任务),进而少一次UI渲染,节省性能 -
其实
nexTick
只是优先选择微任务而已,如果当前运行环境不支持微任务的话,还是会选择宏任务的从源码上可以看出
nextTick
接受一个函数为参数,同时会创建一个Promise
微任务。所以,页面调用nextTick
的时候,会把的参数fn
赋值给p.then(fn)
26 async 和 await
Generator+promise
- async 单独使用和声明普通函数一样
- 函数被async修饰了,其返回值一定是一个promise
- await 必须配合async使用,它右边的代码会立即执行,下面的代码会阻塞(相当于promise.then(下面代码)),右边执行完执行下面
- await修饰promise,可以等待promise的结果
- async 和 await是ES8的语法
27.Promise的用法
-
用处:Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。使得控制异步操作更加容易。可以将异步操作以同步操作的流程表达出来,从而避免了传统的层层嵌套的回调函数的异步操作。
-
本质:Promise 是一个构造函数
-
Promise 相关的方法
-
Promise 构造函数原型上的方法(通过创建一个 Promise 实例进行调用)
-
Promise.prototype.then() :then 方法必须返回一个新的 promise 对象(实现链式调用的关键)
-
Promise.prototype.catch()
catch 异常处理函数,处理前面回调中可能抛出的异常。只接收一个参数onRejected处理程序。它相当于调用Promise.prototype.then(null,onRejected),所以它也会返回一个新的Promise
-
Promise.prototype.finally()
-
-
Promise 构造函数静态方法,(直接通过 Promise 函数调用)
-
Promise.all()
Promise.all()参数可以传递一个promise数组,Promise.all()创建的Promise会在这一组Promise全部解决后在解决。也就是说会等待所有的promise程序都返回结果之后执行后续的程序。返回一个新的Promise,.then()取出来也是一个数组,顺序和所处理的promise数组执行顺序一致。
- 如果所有都成功,则合成Promise的返回值就是所有子Promise的返回值数组。
- 如果有一个失败,那么第一个失败的会把自己的理由作为合成Promise的失败理由(立即执行reject)。
-
Promise.allSettled()
Promise.all 和 allSettled 基本一样,区别是,then 始终可以获取到所有异步的状态和结果(value/reason)的数组,哪怕其中有一个失败
-
Promise.race()
Promise.race 同样传入一样数组,但是只返回第一个结果(数组最快执行完成的那个函数的结果),不管成功或失败,最后返回一个新的Promise
-
Promise.any()和race 的区别是,只会返回第一个成功的结果
-
Promise.reject():用法,如在一个函数中需要将一些数据以promise的形式返回出去,可以用
return Promise.reject(data)
,为什么不用new promise(…) 因为new promise没意义(内部没有异步要执行),其效果相当于定义函数前单独加一个async 的效果 -
Promise.resolve()
-
-
-
Promise对象特点
对象的状态不受外界影响。
- 有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败) - 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。- 只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
- 有三种状态:
28、Generator
-
async和await是Generator一个语法糖 (Generator是ES6出来的,async是ES8)
-
Generator函数的作用是可以将函数执行时进行暂停,而普通函数是一直执行到return,同样也是异步的一种解决方式。
-
定义Generator函数
-
定义函数时通过 function* 进行区分为 Generator 函数,
function* fn() { yield 1 //yield(幼的) 类似插入关键帧 yield 1 return 3 } // 创建了一个generator对象 const gen = demo() //调用函数只会得到一个Generator函数,通过next()得到结果 gen.next() //{value:1,done:true} 执行到第一个yield暂停,1 gen.next() //{value:2,done:true} 执行到第二个yield暂停,2 gen.next() //{value:3,done:true} 最后结果 (没写return时value为undefined)
-
当调用Generator函数后,只是得到一个Generator对象,但并不会执行,需要使用yield和next进行配合来执行。
-
函数内部使用 yield 进行暂停并向外传递数据。函数外部使用next进行逐步执行并接收函数内传递出来的数据。
-
-
向函数内传递参数的话就返回来。由next(xx)向内进行传参,而yield进行接收。但是它的第一次传参是无效的(第一次函数调用传了)。那么第一个yield返回的结果就是函数调用传入的实参,第二个yeild返回的结果就是第一个next(xx)中的xx,以此类推。
function* fn(num) { console.log(num) console.log('第一次next') const num1 = yield console.log(num1) console.log('第二次next') const num2 = yield console.log(num2) } const gen = fn(0) // 第一次传递的参数无效 gen.next() // 0 '第一次next' gen.next(1) // 1 '第二次next' gen.next(2) //2
-
Generator(执行器)配合promise可以处理异步问题(yeild后面跟promise)。可以得到async和await的效果,结果需要.next.value.then()获取,只有上一个yield执行完后才能执行下一个yield,这里推荐使用一个co库(递归调用),它可以让Generator函数进行自动执行。
function co(gen) { const res = gen.next() if (res.done) return res.value.then(() => { co(gen) }) } //自己封装的co函数没有考虑 co函数也可返回promise,以及执行失败的情况,可安装插件co库 npm i co
-
async await 相当于自带了执行器CO库,可以自动向下依次执行
29.Thunk函数
定义:多参数函数,将其替换成单参数的版本(对某个参数进行复用),且只接受回调函数作为参数,那这就是一个thunk
函数
thunk函数是自动执行Generator函数的一种方法
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback)
// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName) //指定文件名,返回一个函数
readFileThunk(callback) //之后可读任何回调函数
var Thunk = function(fileName) { //定义Thunk函数
return function(callback) {
return fs.readFile(fileName, callback)
}
}
//正常定时器
setTimeout(callback,value)
//定义Thunk,可指定延迟时间
const Thunk=function(value){
return function(callback){
setTimeout(callback,value)
}
}
const setValue=Thunk(3000) //指定时间
setValue(callback) //可执行任意回调函数
25、闭包 3
闭包的概念就是让你可以在一个内层函数中访问到其外层函数的作用域。
闭包的原理就是作用域链。就是利用作用域链的特性,首先在当前作用域访问数据,当前作用域访问不到,则向父级访问,父级也没有,一直找到全局。
闭包的作用就是数据私有化,防止污染全局。延长数据的作用域。
闭包的缺点是如果使用不当,会造成内存泄漏,因为闭包的数据没有被回收
解决方案就是将全局指向的函数重新置为 null,利用标记清除的特性。也就是让内层函数置为null,这样没有使用到外层函数的数据,数据就会被回收。
(而数据的回收就需要说道垃圾回收。)
闭包的使用场景:
vue源码中的dep使用
柯里化函数,高阶函数的使用
可以使用闭包来模拟私有方法
26、垃圾回收 3
垃圾回收,简称GC
垃圾回收的概念是,js 的内存是自动进行分配和回收的,内存在不使用的时候会被垃圾回收器自动进行回收,那就需要了解垃圾回收的机制,从而防止内存泄漏(内存无法被回收)
--关于垃圾回收的生命周期
在声明一个变量、对象或者函数等的时候,都会创建一段内存
而在适应变量、函数或者对象的时候,会对内存进行读写,也就是会使用内存
最后再变量、函数、对象等不再需要使用的时候,就会被垃圾回收自动回收掉,进行内存销毁
所以说,全局的是不会被垃圾回收的。
--垃圾回收的核心算法就是判断内存是否需要再使用,如果不需要使用,则进行回收。
那么整个垃圾回收的重点就是如何判断是不是垃圾。这里有多种算法策略。我记得的比较常见的是引用计数和标记清除。
先说引用计数:ie会使用,就是计算当前内存被引用的次数,被引用一次计数+1,不再引用则-1,当计数为0,则表示该内存不再被需要,就进行垃圾回收,释放内存。
优点:简单有效。缺点:循坏引用导致内存泄漏。
标记清除:现在多数浏览器采用的就是这个。它是通过根节点(全局),标记所有从根节点开始的能够访问到的对象。未被标记的对象就是未被全局引用的垃圾对象。这里说的根节点,并不是指window。因为在全局使用的let和const,它是根节点可以访问到的,但是window并不可以。
29、es6及以上 3
(1)let和const的变量声明
-
es6新增了let声明变量,const用来声明常量,const所申明的值,后期不能修改。
-
和var相比,let和const都具有块级作用域{},但是不存在变量提升。
-
暂时性死区的概念。只要写了let或者const,就会形成一个块级作用域。这个块级作用域就像一个封闭区域,里面的变量不会再受到外界的影响。所以,块级作用域内,在let和const声明之前使用的这些变量,会产生报错。这样不能使用外界的值,作用域内的值又还没声明的情况,就被称为暂时性死区(TDZ)。
-
var a = 10 if (true) { a = 20 //a在这个块级作用域里不能访问外部变量 let a = 10 //有let的{}会产生块级作用域 } //结果报错,TDZ
(2)解构赋值
// 数组,使用的时候名字可以任意
const [a,b,c,d,e=5] = [1,2,3,4]
// 对象,解构默认定义对象中的key
const {name:uname, id} = {
name: '张三',
id: 19
}
(3)模板字符串
为了方便字符串的拼接,使用反引号``,反引号内部可以通过${}插入表达式,变量甚至是函数调用。
(4)字符串的几种方法
-
includes:用来查找一个字符串中是否包含另一个字符串,返回值为布尔值。
'llo'.includes('ll') //
-
stratsWith:用来判断某字符串是否以某字符串开头,返回值为布尔值,
'llo'.stratsWith('ll') //
-
endsWith:用来判断某字符串是否以某字符串结尾,返回值为布尔值
(5)箭头函数
箭头函数就是函数的一种简写形式。使用小括号包裹参数,跟随一个 =>,紧接着是花括号包裹的函数体;
1. 箭头函数如果只有一个参数,可以省略小括号
2. 如果只有一行函数体,可以省略花括号,省略后会默认return
3. 尖头函数中没有绑定this,this指向上下文,call,apply,bind方法改变不了箭头函数的指向
4. 箭头函数没有原型,原型是undefined
5. 箭头函数是es6新增的特性
6. 箭头函数没有自己的arguments(实参的集合)
7. 箭头函数不能用作Generator函数,不能使用yeild关键字
8. 箭头函数不能作为构造函数使用
(6)函数的形参部分
-
形参默认值==:可以给形参直接设置默认值(用=)。
-
形参解构:函数的形参也可以解构
const obj={name:'张三',age:'24'} function getInfo({name,age}){console.log(name,age)} //可指定传入对象的key getInfo(obj)
-
形参剩余参数:...语法变量名,返回值是数组,可以将剩余的实参用数组进行获取,此时的…被称为剩余运算符(给剩余的参数加壳,只在形参中使用)
function fn(a, ...args) {
console.log("...args 打印出来的是",args) //args相当于arguments
}
fn(1,2,3) //...args 打印出来的是[1,2,3]
- 单独的...是扩展运算符,是展开内容的(去壳,剩余参数的逆运算)。
(7)对象的使用
-
对象的简写:当对象内的key和值相同时,可以只写一个可以。
-
对象方法的简写:方法名:function(){},可以简写为方法名(){}
-
对象内key写法:对象中的key值写成一个表达式,用['字6符串']或者[变量名]
let name='张三'; k='my' let obj={ [k+'name']:name //key值只能是字符串,key值是变量要用[] }
(8)对象的静态方法
-
Object.assign(目标对象,源对象):类似于合并数据。如果值是复杂数据类型,那么指向的还是同一个值。实现的是浅拷贝。
let copyobj=Object.assign({}, srcObj)
-
Object.create(参数):创建一个对象,并将对象的__proto__指向参数对象。Object.create(null)可以用来创建一个没有原型的对象。
(9)promise
(10)模块化
- common js模块化规范,导出用module.exprots,导入用require(),最早出来的,一般nodejs里面会使用。
但现在web更流行的是es6模块化规范。浏览器是不识别es6的模块化规范的,通过babel处理。
-
web过渡时期的产品(执行时机不同):
- AMD: 前置依赖,AMD加载完模块后,就立马执行该模块 (代表的包:require.js,没装包浏览器不能实现模块化)
- CMD: 就近依赖,CMD加载完某个模块后没有立即执行而是等到遇到require语句的时再执行(代表的包:Sea.js)
-
ES6模块化
-
默认导出,默认导入:export default 加 import xxx from './router.js'
-
按需导出,按需导入:
export const a = 1 export const b = 2 --------------------- import { a as a1, b } from './router.js' //as取别名 console.log(a1) console.log(b)
-
默认、按需导出同时使用(必须默认导入在再按需导入)
export const a = 1 export const b = 2 export default user={age:11,name:'张三'} ---------------------------------------- import user, { b, c } from './router.js' //主要导入顺序,否则报错
-
全部引入:必须用as取别名
import * as moduleRouter from './router.js'
全部引入返回值是一个对象,如果是按需导出的变量添加到对象中的key,值会成为对象中的value, 默认导出的key固定为default,值为导出的对象
-
注:
- export default xxx ,默认导出实际导出的是default对象,可改成按需导出
export { default as xxx } from "..."
- 使用export * from './user' 表示把api的user.js中所有按需导出的数据暴露出去。
-
30.webpack配置流程
1)功能:
简单来说webpack就是一个打包工具。但是webpack能做的事情以及作用是很多的。
- js高级语法转换兼容
- css兼容/预处理语言处理(less、scss)
- 代码压缩混淆
- jsx转换
- 图片压缩
- ...
2)环境配置
-
初始化package.json
npm init (-y为生成默认的package.json)
-
安装webpack
npm i webpack webpack-cli //webpack模块包,webpack-cli为提供终端webpack命令,后另专为npm run build 等
-
在package.json运行命令
"scripts": { "dev": "webpack", //将原有的webpack打包命令改成dev "test": "echo \"Error: no test specified\" && exit 1" },
-
在项目根目录准备默认打包入口src/index.js,默认打包出口dist下的main.js
console.log('hello webpack')
3)打包配置(打包入口出口,loader,plugins)
-
配置打包模式,根目录创建webpack.config.js
module.exports = { mode: 'development', // development 开发阶段 production 发布阶段 entry: './src/index.js' , //自定义打包入口 output: { //自定义打包出口 path: __dirname + '/dist/', // 打包出口必须是绝对路径 filename: 'main.js' } } //开发阶段打包:粗略打包,保留格式,快 生产阶段打包:精细打包
打包多入口多出口(一一对应):
module.exports = { mode: 'development', // development 开发阶段 production 发布阶段 entry: { main: './src/main.js', //key名main为打包后文件的名字 app: './src/app.js' }, output: { path: __dirname + '/dist/', filename: '[name].js' //filename需要修改为[name]占位,打包出来对应的两个文件main.js和app.js } }
-
执行打包命令
npm run dev
-
查看是否生成 dist/main.js
4)Loader
-
webpack默认只能处理js文件,如果想处理其他类型文件( 如CSS,)则借助各种loader实现
-
处理CSS文件(安装css-loader和style-loader),注:一定要在main.js中引入CSS
-
css-loader作用:负责webpack打包到css文件时(import './css/index.css'),加载该css文件,并将css文件转换为commonjs对象(js文件)
-
style-loader作用:负责将样式生成style标签(内嵌样式)插入到DOM中
-
npm i css-loader style-loader -D
-
在config中配置loader,加载顺序为从右到左的,先加载css-loader
-
module.exports = { ..... module: { //module用配合各种loader rules: [ { test: /.css$/, // 匹配打包后缀为css的文件 use: ['style-loader', 'css-loader'] // 顺序必须为 style-loader css-loader } ] } }
-
css开启模块化,开启模块化后,引入的css样式类名(如.test)会被重新编译,并通过对象中key返回原始类名,value返回编译后的类名,防止样式冲突
import styles from './css/index.css' console.log(styles) //{test: 'y792zGC7AFljinknxRtE'} // 控制台输出
config中配置
module.exports = { module: { rules: [ { test: /.css$/, use: [ { loader: 'style-loader', }, { loader: 'css-loader', //改成对象形式 options: { modules: true } }, ] } ] } }
-
-
CSS打包进阶
-
postcss-loader可以进一步打包css,例如添加前缀,px转rem,px转vw等等
(postcss是用js写的工具箱,用来转换CSS代码) -
安装包autoprefixer (给样式类名自动添加前缀(如-webkit),处理某些样式的兼容性)自动给css添加前缀
npm i postcss-loader autoprefixer //autoprefixer为PostCss中的一个工具包
-
配置文件
-
module.exports = { module: { rules: [ { test: /.css$/, use: [ //从最下面模块开始加载 { loader: 'style-loader', }, { loader: 'css-loader', options: { modules: true } }, 'postcss-loader' //识别到postcss会自动查询postcss.config.js配置文件查看用了哪些工具 ] } ] } }
-
项目根目录创建并配置postcss.config.js
module.exports = { plugins: [ require('autoprefixer') ] }
-
-
打包图片字体之file-loader
-
目前webpack5 默认支持打包图片和字体等资源
-
配置打包图片,asset会自动将打包的资源文件打包为单独的文件还是url(base64)进行自动切换
module.exports = { module: { rules: [ { test: /\.(png|jpg|gif|jpeg)$/i, type: 'asset' } ] } }
- 8kb以内图片打包成base64格式,优点:发送时请求变少,缺点:打包后的图片大小大于打包前
- 8kb以上的图片以源文件格式打包,需要请求得到(8kb以上的打包成base64大小增大30%)
-
-
Webpack4.0 需要借助file-loader / url-loader
-
安装file-loader
npm i file-loader
-
配置loader
module.exports = { module: { rules: [ { test: /.(png|jpg|gif)$/, use: ['file-loader'] } ] } }
-
-
babel-loader:用于将js高级语法(或扩展语法)转成可兼容的低级语法
-
1.装包
npm i @babel/core @babel/preset-env babel-loader -D //babel处理js高级语法(或扩展语法) //preset为高低级语法对应字典 //babel-loade为处理js文件的工具模块
-
2.配置解析js的loader
module.exports = { module: { rules: [ { test: /.js$/, use: 'babel-loader' //会自动寻找.babelrc文件 } ] } }
-
3.配置babel配置, 根目录创建.babelrc文件
{ "presets": [ "@babel/preset-env" //使用preset-env语法 ] }
-
5) plugins
-
plugins 为webpack的插件。可以扩展webpack的功能使其更加丰富(如可处理loader不能处理html文件的缺点,loader能处理js,css,图片,less,sass文件)
-
html-webpack-plugin(用插件的形式打包HTML)
简化了 HTML 文件的创建,可以根据模版html生成新的html,并自动引入打包的js文件
-
安装包
npm i html-webpack-plugin
-
配置插件
const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { plugins: [new HtmlWebpackPlugin({ template: './public/index.html', //从该路径打包HTML文件,入口 filename: 'index.html' })] }
-
打包测试
npm run dev
-
-
mini-css-extract-plugin(用插件的形式打包CSS)
可以将css文件单独打包,生成新的css文件(js内嵌样式),注意mini-css-extract-plugin还要配合css-loader使用,同时该插件和style-loader不可同时使用,因为style-loader将css样式动态通过style标签插入到dom中
-
安装包
mini-css-extract-plugin
-
配置插件和loader
const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { module: { rules: [ { test: /.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'] } ] }, plugins: [new HtmlWebpackPlugin({ template: './public/index.html', filename: 'index.html' }), new MiniCssExtractPlugin()] }
-
打包测试
npm run dev
-
查看打包后的目录是否有单独的css文件
-
-
webpack-dev-server(自动热更新)
实现webpack检测文件发生变化进行自动打包(打包的文件在内存中,供开发中使用)
特点:
- 会以服务的方式运行打包的项目
- 打包的文件并不没有在磁盘中,而是在内存中,所以打包速度会更快(开发期间用run dev,不需要拿到真正打包后的代码)
- 文件发生变化自动打包
-
安装包
npm i webpack-dev-server
-
修改package.json
"scripts": { "dev": "webpack server", //开发阶段打包热更新使用的命令 "build":"webpack", //最后打包使用的命令 "test": "echo \"Error: no test specified\" && exit 1" },
-
运行打包
npm run dev
#devServer的参数配置
- open:自动打开浏览器
- port:端口号
-
配置参数
"scripts": { "dev": "webpack server --open --port=8083", "test": "echo \"Error: no test specified\" && exit 1" }, Copied!
-
运行测试模块工具(
30、暂停分支开发git stash
- 应用场景:
- 当正在dev分支上开发 某个项目,这时项目中出现一个bug,需要紧急修复,但是正在开发的内容只是完成一半,还不想提交,这时可以用git stash命令将修改的内容保存至堆栈区,然后顺利切换到hotfix分支进行bug修复,修复完成后,再次切回到dev分支,从堆栈中恢复刚刚保存的内容。
- 由于疏忽,本应该在dev分支开发的内容,却在master上进行了开发,需要重新切回到dev分支上进行开发,可以用git stash将内容保存至堆栈中,切回到dev分支后,再次恢复内容即可。
- 总的来说,git stash命令的作用就是将目前还不想提交的但是已经修改的内容进行保存至堆栈中,后续可以在某个分支上恢复出堆栈中的内容。这也就是说,stash中的内容不仅仅可以恢复到原先开发的分支,也可以恢复到其他任意指定的分支上。git stash作用的范围包括工作区和暂存区中的内容,也就是说没有提交的内容都会保存至堆栈中。
- git stash:能够将所有未提交的修改(工作区和暂存区)保存至堆栈中,用于后续恢复当前工作目录。
- git stash save:作用等同于git stash,区别是可以加一些注释
git stash save “test1”
- git stash list :查看当前stash中的内容
- git stash pop:将当前stash中的内容弹出,并应用到当前分支对应的工作目录上。
注:该命令将堆栈中最近保存的内容删除(栈是先进后出),顺序执行git stash save “test1”和git stash save “test2”命令,test2的stash是首先pop出来的。如果从stash中恢复的内容和当前目录中的内容发生了冲突,也就是说,恢复的内容和当前目录修改了同一行的数据,那么会提示报错,需要解决冲突,可以通过创建新的分支来解决冲突。 - git stash apply: 将堆栈中的内容应用到当前目录,不同于git stash pop,该命令不会将内容从堆栈中删除,也就说该命令能够将堆栈的内容多次应用到工作目录中,适应于多个分支的情况。
- git stash drop + 名称: 从堆栈中移除某个指定的stash
- git stash clear: 清除堆栈中的所有 内容
- git stash show: 查看堆栈中最新保存的stash和当前目录的差异。
- git stash branch: 从最新的stash创建分支。
应用场景:当储藏了部分工作,暂时不去理会,继续在当前分支进行开发,后续想将stash中的内容恢复到当前工作目录时,如果是针对同一个文件的修改(即便不是同行数据),那么可能会发生冲突,恢复失败,这里通过创建新的分支来解决。可以用于解决stash中的内容和当前目录的内容发生冲突的情景。
发生冲突时,需手动解决冲突。
30、深拷贝和浅拷贝的方法
- 浅拷贝
- 数组:slice(start,end)
- 对象:扩展运算符,Object.assign(),
- 深拷贝
- JSON.parse( JSON.stringify(目标对象))
- 手写递归函数,循环
- 实际开发中使用lodash函数库的_.cloneDeep方法
31、深拷贝和浅拷贝
●浅拷贝:
概念:对数据拷贝的时候只拷贝一层,深层次的只拷贝了地址。
...和object.assign都属于浅拷贝
我们会发现通过浅拷贝更深层次的引用类型,如果修改 b.googs,最终 obj.goods 也会跟着修改,是因为在拷贝的时候,我们只是将引用地址拷贝给了 b.goods,也就是说 b.goods 和 ob.goodsj 引用的是同一个对象
缺点:拷贝复杂数据类型的时候,新数据和旧数据都会指向同一个地址。
●深拷贝:
方法一:JSON 方法实现深拷贝
我们先将需要拷贝的代码利用 JSON.stringify 转成字符串,
然后再利用JSON.parse 将字符转转回对象,即完成拷贝
问题:造成数据丢失和数据异常
●function、undefined 直接丢失
●NaN、Infinity 和-Infinity 变成 null
●RegExp、Error对象只得到空对象;
方法二:递归深拷贝
1定义一个方法,返回一个深拷贝的数据
2既然要返回一个数据,我们首先就要定义一个数据,但是数据是对象还是数组?所以需要判断,如果要拷贝的数据是数组,即定义一个数组,如果是一个对象,即定义一个对象
3方法里面怎么拷贝啊?还是一样的利用 for in 循环,在循环内部,需要判断,如果是类型是简单类型,直接拷贝,如果是引用类型,就需要在一次的将引用类型里面的值取出来
4但是递归也会遇到上面同样的问题
a数据丢失和异常处理:处理函数 Symbol 正则 Error 等数据类型正常拷贝
b循环引用问题:数据自己引用自己,此时拷贝就会进入死循环
解决思路(循环引用问题)
将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝
存储数据的方式:
1使用数组
2使用 map 数据:强引用,无法被垃圾回收,key可以是任何形式
3使用 hash 表:弱引用,可被垃圾回收
真正在开发中,我们一般都是使用一个lodash的包。
29、数组去重的方法 3
1双重for循环
2for循环就数组,indexOf 遍历新数组,判断新数组中有没有for循环的每一项,没有就放入新数组。最后新数组就是去重后的数组。
3用filter加indexOf,用filter代替for来循环旧数组的每一项
4用sort进行排序,然后for循环判断相邻两个是否相等,不相对就放入新数组中
5for循环每一项,然后用对象的方式放入,计算个数。
6用set与解构赋值。set数组类型的最大特点就是数据不重复
29.1 数组去重
const arr = [1, 1, '1', 17, true, true, false, false, 'true', 'a', {}, {}];
// => [1, '1', 17, true, false, 'true', 'a', {}, {}]
参考答案:
- 方法一:利用Set
1const res1 = Array.from(new Set(arr));
- 方法二:两层for循环+splice
1const unique1 = arr => {
2 let len = arr.length;
3 for (let i = 0; i < len; i++) {
4 for (let j = i + 1; j < len; j++) {
5 if (arr[i] === arr[j]) {
6 arr.splice(j, 1);
7 // 每删除一个树,j--保证j的值经过自加后不变。同时,len--,减少循环次数提升性能
8 len--;
9 j--;
10 }
11 }
12 }
13 return arr;
14}
- 方法三:利用indexOf
1const unique2 = arr => {
2 const res = [];
3 for (let i = 0; i < arr.length; i++) {
4 if (res.indexOf(arr[i]) === -1) res.push(arr[i]);
5 }
6 return res;
7}
当然也可以用include、filter,思路大同小异。
- 方法四:利用include
1const unique3 = arr => {
2 const res = [];
3 for (let i = 0; i < arr.length; i++) {
4 if (!res.includes(arr[i])) res.push(arr[i]);
5 }
6 return res;
7}
- 方法五:利用filter
1const unique4 = arr => {
2 return arr.filter((item, index) => {
3 return arr.indexOf(item) === index;
4 });
5}
- 方法六:利用Map
复制
1const unique5 = arr => {
2 const map = new Map();
3 const res = [];
4 for (let i = 0; i < arr.length; i++) {
5 if (!map.has(arr[i])) {
6 map.set(arr[i], true)
7 res.push(arr[i]);
8 }
9 }
10 return res;
11}
29.1跨页面通信
-
onstorage
WindowEventHandlers.onstorage 属性包含一个在 storage 事件触发时运行的事件处理程序。当更改存储时会触发事件处理程序。
<div id="app"></div> <button id="tab">新开 Tab</button> <button id="l-btn">触发 LocalStorage 更新</button> <button id="s-btn">触发 SessionStorage 更新</button> <script> window.onstorage = function(e) { console.log(`The ${e.key} key has been changed from ${e.oldValue} to ${e.newValue} .`); }; document.getElementById('l-btn').onclick = function () { localStorage.setItem('storage1', Date.now()) } document.getElementById('s-btn').onclick = function () { sessionStorage.setItem('storage1', Date.now()) } </script>
注意点
- 该事件不在导致数据变化的当前页面触发(如果浏览器同时打开一个域名下面的多个页面,当其中的一个页面改变 数据时,其他所有页面的 storage 事件会被触发,而原始页面并不触发 storage 事件)。
- sessionStorage(❎)不能触发 storage 事件 , localStorage(✅)可以。
- 如果修改的值未发生改变,将不会触发 onstorage 事件。
- 优点:浏览器支持效果好、API直观、操作简单。
- 缺点:部分浏览器隐身模式下,无法设置 localStorage。如safari,这样也就导致 onstrage 事件无法使用。
-
BroadCast Channel
BroadcastChannel 接口代理了一个命名频道,可以让指定 origin 下的任意 browsing context 来订阅它。它允许同源的不同浏览器窗口,Tab页,frame或者 iframe 下的不同文档之间相互通信。通过触发一个 message 事件,消息可以广播到所有监听了该频道的 BroadcastChannel 对象。说到 BroadCast Channel 不得不说一下 postMessage,他们二者的最大区别就在于 postMessage 更像是点对点的通信,而 BroadCast Channel 是广播的方式,点到面。
<div id="app"></div>
<button id="tab">新开 Tab</button>
<button id="l-btn">发送消息</button>
<button id="s-btn">关闭</button>
<script>
// 创建
const broadcastChannel = new BroadcastChannel('channelName');
// 监听消息
broadcastChannel.onmessage = function(e) {
console.log('监听消息:', e.data);
};
document.getElementById('tab').onclick = function () {
window.open('xxx');
}
document.getElementById('l-btn').onclick = function () {
// 发送消息
broadcastChannel.postMessage('测试,传送消息,我发送消息啦。。。');
}
document.getElementById('s-btn').onclick = function () {
// 关闭
broadcastChannel.close();
}
</script>
注意点
-
监听消息除了 .onmessage 这种方式,还可以 使用addEventListener来添加'message'监听,
-
关闭除了使用 Broadcast Channel 实例为我们提供的 close 方法来关闭 Broadcast Channel。
我们还可取消或者修改相应的'message'事件监听。两者是有区别的:取消'message'监听只是让页面不对广播消息进行响应,Broadcast Channel 仍然存在;
而调用 close 方法会切断与 Broadcast Channel 的连接,浏览器才能够尝试回收该对象,因为此时浏览器才会知道用户已经不需要使用广播频道了。
-
兼容性:如果不使用 IE 和 sf on iOS 浏览器,兼容性还是可以的。
30、你能不能自己配置一个webpack搭建一个项目 3
简单来说webpack就是一个打包工具。但是作用很多:
●js高级语法转换兼容
●css兼容/预处理语言处理(less、scss)
●代码压缩混淆
●jsx转换
●图片压缩
项目搭建过程:
1初始化npm init,搭建项目环境,搭建一个基本的项目目录架构。
2安装包webpack和webpack-cil
3package.json中添加运行命令‘build’:‘webpack’
4添加默认打包入口src/index.js
5配置打包模式,根目录创建webpack.config.js。添加commonjs模块化规范的导出module.exports={mode:'development'}
6执行打包命令npm run build
7正确操作应该会生成dist/main.js的默认出口文件
出入口是可以进行修改的。
31、webpack的打包流程 3
1查找配置文件
2根据配置文件的入口和出口进行打包
3找到对应的入口,根据import文件依赖查找进行打包
4但是webpack默认只能打包低级的js文件,遇到其他类型的文件,需要借助相应的loader进行打包。遇到高级的语法文件,需要借助babel来进行降级处理。
5打包完成后需要生成html文件,用html-webpack-plugin进行处理打包。
6最后输出到出口,将文件进行类型,单独打包,使用plugin插件。
7通过output多出口
32、bable和plugin、loader
●webpack默认只能处理js文件,如果想处理其他类型文件则借助各种loader实现,用来处理非js文件。
acss-loader:负责webpack打包到css文件时(import './css/index.css'),加载该css文件,并将css文件转换为commonjs对象
■css开启模块化,开启模块化后,引入的css样式类名会被重新编译,并通过对象中key返回原始类名,value返回编译后的类名,防止样式冲突。开启方式就是给css-loader添加一个modules为true。
bstyle-loader:负责将样式生成style标签插入到DOM中
cpostcss-loader:可以进一步打包css,处理兼容性,或者将单位统一转为px等
dfile-loader
●babel是一个JavaScript编译器,会对js高级语法进行降级处理。配置babel可以在根目录创建 .babelrc 文件
●plugin 插件可以扩展丰富webpack功能
ahtml-webpack-plugin:简化了 HTML 文件的创建,可以根据模版html生成新的html,并自动引入打包的js文件
babel: 将高级语法转换成浏览器可以识别的语法
loader: 加载器, 结合 webpack 来处理非 js 资源文件 .css .less .sass .png
plugin: webpack 的各种各样的插件,能够增强 webpack 的功能
babel工作原理:
●parse:通过 parser 把源码转成抽象语法树(AST)
●transform:遍历 AST,调用各种 transform 插件对对抽象语法树(AST)进行变换操作
●generate:把转换后的 AST 打印成目标代码,并生成 sourcemap
33、computed和watch的区别 3
计算属性computed:
●支持缓存,只有依赖数据发生改变,才会重新进行计算
●不支持异步,因为有return的存在
●定义的时候是方法,使用的是属性
侦听属性watch:
●不支持缓存,数据变,直接会触发相应的操作
●watch支持异步
●监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
●watch可以监视:props\data\computed$route
●监视的是一个对象: 开启深度监听 deep:true
34、生命周期 1
-
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
-
生命周函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
-
生命周期有四个阶段,常用的八个钩子函数
-
阶段1:创建阶段
beforeCreate:开始创建实例,此时实例的数据和方法还没有。
created:实例已经创建完成,数据和方法都已存在
应用场景:发送请求获取数据,页面一进入需要立即执行
!如果非要在created中操作dom也可以利用$nextTick
-
阶段2:挂载阶段
beforeMount:开始挂载dom,真正的dom元素还没有挂载完成,不可以操作dom
mouted:dom已经挂载完成,可以操作真实dom
应用场景:页面一进去就需要操作dom元素
-
阶段3:更新阶段
beforeUpdate:数据变了,但是视图没有变
updated:数据和视图都变了
-
阶段4:销毁阶段
beforeDestory:即将销毁
destoryed:组件销毁
应用场景:清除挂载在window相关的行为,例如定义器/事件
- 父子生命周期
-
创建挂载阶段(父父父子子子子父)
父beforeCreated > 父created > 父beforeMounted >子TbeforeCreate > 子Tcreated > 子TbeforeMount > 子>mounted > 父mounted
-
更新阶段(父子子父)
如果更新的数据不涉及到子组件,只会父组件更新父beforeUpdate>父updated
如果更新的数据涉及到子组件, 父beforeUpdate >子TbeforeUpdate > 子Tupdated > 父updated
-
销毁阶段(父子子父)
父beforeDestory >子 beforeDestory > 子destoryed> 父destoryed
- 不常用的生命周期3个:
activated
:keep-alive所缓存组件激活时调用deactivated
:keep-alive所缓存组件停用时调用errorCaptured
:子孙组件的错误捕获,此函数可返回false阻止继续向上传播
34.1 mixin的生命周期执行顺序
父子组件及mixin的生命周期执行顺序:
-
mixin的生命周期钩子在当前组件的生命周期钩子之前执行
-
在父组件中引入了mixin,生命周期顺序如下:
mixin的beforeCreate > 父beforeCreate > mixin的created > 父created > mixin的beforeMount > 父beforeMount > 子beforeCreate > 子created > 子beforeMount > 子mounted > mixin的mounted >父mounted
35、vue组件通信(传值)
(1)父传子
父组件属性方式传值,子组件用props进行接收
(2)子传父
子组件通过$emit传值,父组件通过自定义事件接收
(3)eventbus(兄弟组件传值):
-
本质:就是一个vue实例对象
-
实现:A向B传值,B通过
eventBus.$on(事件名,回调)
注册自定义事件,用对应的方法接受A传过来的数据,A通过eventBus.$emit(事件名,数据)
触发自定义事件,并传出数据 -
实现原理:是利用发布订阅模式,至于发布订阅模式的话,发布订阅模式是一对多的依赖关系,发布方是一,订阅方是多。发布方通过eventBus的emit发布自定义事件,并传递数据。订阅方通过eventBus的$on订阅自定义事件,并通过回调函数接收数据。
(4)vuex
(5)v-model
(6).sync
(7)ref获取子组件
ref加在普通的元素上,可以用this.$refs
来获取相应的dom元素,ref加在子组件上,用this.$refs
获取到的是组件实例,可以使用组件上的所有方法,用this.$refs.方法名()进行使用。ref必须在dom渲染完成之后才会有
(8)$children:
可以获取当前组件的所有子组件,并以数组的格式返回数据, $children 并不保证顺序,也不是响应式的。
(9)$parent: (要少用,因为其父组件有可能不唯一(且父组件数据逻辑不同),如该组件在很多地方复用)
可以获取到当前组件的父组件, 返回当前组件的父组件
(10)provide/inject
provide可以给当前组件所有的后代组件提供数据
使用方式和data类似,只不过data是给当前组件用的数据,而provide是给后代用的数据
inject在后代组件上使用,表示接受值,用数组加字符串的形式接收,和props的最初方式相同。
(11)$attrs
获取到当前组件节点上的所有属性集合
父组件在引用的时候传值,子组件不使用props接收,而是在需要的时候用$attrs进行使用
35.1. 组件之间的传值方式有哪些?
-
父传子: 组件使用
props
进行接收 -
子传父: 子组件使用
$emit
+事件对父组件进行传值 -
父子之间通过
$parent
和$chidren
获取实例进而通信 -
使用
$refs
获取组件实例,进而获取数据。 -
使用vuex进行状态管理,实现数据共享 // ocalStorage/sessionStorage持久化存储
-
使用
eventBus
进行跨组件触发事件,进而传递数据 -
使用浏览器本地缓存,例如
localstorage``sessionStorage
-
$attrs
和$listener
适用于多级组件嵌套,但是不做中间处理的情况。比如祖先组件向孙子组件传递数据。
$attrs
可以获取父组件传进来,但是没有用props接收的属性。可以通过v-bind="$attrs"传入内部组件。
搭配inheritAttrs=true使用,这个只是用来控制attrs是否在DOM中渲染。$listeners
包含父作用域中的(不包含.native的) v-on时间监听器。可以通过v-on="$listeners"
来传入内部组件。 -
依赖注入provide/inject多个组件嵌套时,顶层组件provide提供变量,后代组件都可以通过inject来注入变量。
export default{ //顶层转提供变量 provide(){ //与data同级 return{ msg:'hello world' //可通过 msg:this.xxx 实现响应式 } } } ====== export default{ //顶层转提供变量 inject:['msg'] //与data同级,数组形式接收变量/方法名 }
36、动态路由和静态路由
静态路由是管理员手动配置的,不便于拓展网络拓扑结构,一但网络拓扑发生改变,静态路由配置量会很大。动态路由是路由器通过网络协议,动态的学习路由,当网络拓扑发生变化的时候,路由器会根据路由协议自动学习新的路由。
动态路由,因为OSPF,RIP等路由协议都会有周期更新,所以更新量大,占用宽带大。
使用静态路由的好处是网络安全保密性高。动态路由因为路由器之间频繁交替,需要经常使用路由表,而路由表可以分析出拓扑结构和网络地址等,所以安全性低。
动态路由:灵活性高
静态路由:安全,占用宽带小,简单,高效。
37、路由传参
-
动态路由传参
- 路由配置,需要在path上进行参数设置
- params方法 path: '/xxx/:uid',
- query方法 path: '/xxx?uid=值',
- 路由跳转,this.$router.push('地址');
- 在对应页面中拿到路由参数,
this.$route.params.uid / this.$route.query.uid;
- 路由配置,需要在path上进行参数设置
-
query方式传参
○路由配置不变
○路由跳转
■用path跳转,this.$router.push(),参数为对象,path:对应路由配置的值,query参数
■用name跳转,this.$router.push(),参数为对象,name:对应路由配置的值,query参数
○在对应页面中拿到路由参数,this.$route.query.uid;
-
params方式传参,只能通过name跳转
○路由配置不变
○路由跳转,this.$router.push(),参数为对象,name:对应路由配置的值,params参数
○在对应页面中拿到路由参数,this.$route.params.uid;
-
页面跳转的方法:
1、声明式导航跳转《router-link to="需要跳转到页面的路径”》
2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面
3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
4、this.$router.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数
37.1.router.push、router.replace、router.go的区别?
router.push
:跳转,并向history栈中加一个记录,可以后退到上一个页面router.replace
:跳转,不会向history栈中加一个记录,不可以后退到上一个页面router.go
:传正数向前跳转,传负数向后跳转
38、路由守卫
路由守卫分为三种,全局守卫,路由独享守卫以及组件内守卫。
- 全局守卫
●全局前置守卫
○写法router.beforeEach(to,from,next),写在路由文件/权限文件中,new Vuerouter后配置
○每一个路由进入前触发
○可以用来进行路由权限的控制。
○在回调中有三个参数,to,from,next,其中next()必写。
●全局解析守卫:不用
●全局后置守卫
○写法router.afterEach()
○每一个路由进入后触发
○用于提示语之类的
- 路由独享守卫
●写法beforeEnter(),写在路由规则配置里面,和path,name同级
●单独进入某个路由前触发
●在回调中有三个参数,to,from,next,其中next()必写
- 组件内守卫
●写在组件内,和created等生命周期同级
●在回调中有三个参数,to,from,next
●写法有三个
○beforeRouteEnter
■渲染组件前触发
■ 不能获取组件实例 this
○beforeRouteUpdate(例如组件复用了(如商品里推荐商品),但组件没有重新创建不会触发create(),里的函数,可用这个路由解决,记得加next()再调用函数)
■ 在当前路由改变,但是该组件被复用时调用
■一般就是是同一个页面,不同的参数这样,此时组件复用,没有被创建,也就无法重新创建。
■ 可以访问组件实例 this
○beforeRouteLeave(常用):(例如误触路由跳转可提示是否跳转,如拼多多砍刀退出弹窗是否跳转)
■路由离开时调用,其中next()必写
■ 可以访问组件实例 this
■比如在编辑过程中,要离开给页面,就会跳出没有保存,是否需要离开的提示。
39、聊聊vuex
对于vuex的话。
vuex是一个状态管理的库,是用来实现组件之间的数据共享的。
-
vuex的优点是数据的响应式,可以便于数据的传递,方便维护和管理。
-
vuex包含了5个属性:
-
state,用于定义和存储共享的数据。用$store.state或者是辅助函数mapState来进行触发。
-
mutations,用来修改数据,也是修改数据的唯一来源。(当然,其实state数据是可以直接进行赋值修改的,但是并不建议使用,就像是所有人都可以更改公司数据一样,存在着各种不安全隐患。)用commit或者辅助函数mapmutations来进行触发修改数据。
-
actions:说道actions,就要提到刚刚说的mutations,mutations的同步的,而actions是异步的。用dispatch或者是辅助函数mapactions来触发。但是actions只能够进行异步处理,不能修改state的数据。所以如果需要修改数据,还是需要调用mutations来处理。
所以我们的同步流程就是直接触发mutations,而异步流程则是先触发actions,再由actions进行异步处理以后再去触发mutations。
-
getters:基于state进行派生数据
-
moudles:模块化,将数据模块化后,会有上述的四种属性。模块化后的数据方便维护和管理。每一个模块可以设置命名空间,如果不设置,那么mutations和actions的使用和全局并无差别。如果开启命名空间,则需要通过模块名进行访问。
-
-
vuex的缺点就是不能持久化。我们在使用的时候,如果数据是从后台请求回来的,那么可以直接忽略持久化的问题。如果不是请求回来的,那么就需要解决持久化的问题。
解决的方式就是本地存储。除了localStorage, sessionStorage, cookie以外,我们还常用一个自动存储的插件
vuex-persistedstate(默认储存在localStorage)。
39.1为什么 Vuex的mutation中不能做异步操作
mutations是处理同步的数据的,如果处理异步的话,会导致devtool的记录出现问题,也就无法及时的知道状态是何时更新的,无法追踪状态,给调试带来困难。那我们公司就是不管同步还是异步,都会先进行一个actions的异步处理,再去调用mutations来处理修改数据。
40、vue.use()的原理
通过全局方法vue.use()使用插件
Vue.use会自动阻止多次注册相同插件
需要在调用new Vue()启动应用之前完成
Vue. use()至少传入一个参数
如果参数是一个Object对象,那么这个对象必须提供一个install函数
如果参数是一个function函数,那么就相当于install函数
Vue. use()的其他参数,都会依次传给install函数
install函数默认的第一个参数是vue,后面的参数来自于Vue.use后续参数
vue.use本质是无法注册全局组件或者给vue原型添加方法的,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过vue.component注册了全局组件或者给Vvue.prototype手动添加方法。
41、v-if和v-show
v-show和v-if都是可以用来控制元素的隐藏和显示的。true的时候表示显示,false的时候表示隐藏。
v-show的隐藏使用的是css样式display:none的方式,不管是true还是false,它都会渲染对应的dom元素。
优点:不会频繁创建dom
缺点:首次渲染为false也会创建
场景:适用于频繁切换的场景。
v-if的隐藏是直接从dom上移出,不会产生对应的元素。在true的时候,再创建相对应的整个标签。
优点:他是懒渲染,默认首次如果是false,元素不会创建。如果是组件,利用v-if可以重新触发生命周期
缺点:频繁的删除和重建
场景:适用于一进入页面,就确定是显示和隐藏,后期不会改变的场景。
或者组件需要重新触发生命周期的场景
v-if还有一个v-show没有的高级语法,就是和v-else搭配使用的判断条件的用法。
42、$route和$router
$route
:用来获取当前路由信息的,每个路由都会有一个$route
对象,是一个局部的对象
可以写作$route.path(当前路径)params,name等
$router:全局路由实例,用来操作路由的
等价于 new VueRouter
包含所有的路由,路由的跳转方式,钩子函数等等。
最常用的就是this.$router.push()进行跳转
43、v-model
作用:一是数据双向绑定,二是实现组件通信
原理:v-model就是一个语法糖(v-on和v-bind,数据变视图变的原理还是数据响应式原理),动态绑定了value和注册了input事件(或change事件)
使用场景:
●一是在表单中使用,比如input输入框之类的,需要双向绑定这个数据,就可以绑定一个v-model,绑定的就是input内输入的值
●二是在组件上使用,在组件上使用的情况就是:父组件数据要传给子组件,并且子组件需要修改数据(两件事情都需要的情况下才会使用),子组件用value接受
缺点:v-model在一个组件上只能使用一次
43.1v-model和.sync
v-model:
v-model
是语法糖,默认情况下相当于:value
和@input/@change
。通常在表单项上使用v-model
- 通过
<input v-model="xxx">
的方式将xxx的值绑定到表单元素value上;对于checkbox,可以使用true-value
和false-value指定特殊的值,对于radio可以使用value指定特殊的值;对于select可以通过options元素的value设置特殊的值;还可以结合.lazy,.number,.trim对v-mode的行为做进一步限定;v-model
用在自定义组件上时又会有很大不同,
.sync:
sync修饰符是@update:属性名 事件和 :属性名=“传值数据” 的语法糖,值组件可以通过$emit('update:属性名',数据)触发并向父组件传值
44、MVVM 的设计思想的优势
●mvc的改进版
●双向绑定技术,当 数据变化时,视图也会自动变化,视图发生更新,数据也跟着同步
●我们减少了 dom 的操作,因为我们只需要关注数据就可以
●mvvm 的设计思想大大提高了代码的耦合性(以前页面结构变,js代码也得变)
●数据响应式的原理
45、keep-alive
keep-alive是一个内置组件,它会缓存不活动的组件实例,而不是将其销毁。不会渲染dom元素
它提供了include和exclude属性。
它包含了activated和deactivated钩子函数
include:指定缓存的组件
exclude:指定不缓存的组件
activate:激活状态
deactivated:失去激活状态
46、图片懒加载
图片懒加载的原理:优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。
一张图片就是一个标签,浏览器是否发起请求图片是根据的src属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。
47、虚拟dom和diff算法
虚拟dom的本质就是一个js对象,用来描述真实dom是什么样子的,这个对象就是我们常说的虚拟dom。
虚拟dom的出现可以进行高效更新,同时可以使用虚拟dom进行跨平台。
我们在初始化渲染的时候,会根据数据和模板生成一个虚拟dom树,当数据发生变化的时候,又会根据新的数据和模板,生成一个新的虚拟dom树。然后将新旧两颗虚拟dom树进行对比。对比的过程使用的就是diff算法。说道diff算法。它的特点是同级比较,深度优先,且采用双指针算法。这里的双指针算法会产生四个指针,新旧虚拟dom树各有两根指针,都是一个指向开始位置,一个指向结束位置。在进行循环的时候,开始位置的指针会在对比完以后向后推,结束位置的指针在对比玩以后会向前推,从而达到高效更新。
diff对比情况分为三种。
一是元素不同,会直接删除重建。
二是元素相同,属性不同,那么元素会进行复用,只会更新属性。
三是v-for循环的情况。这也会分成两种,
一是没有key的时候,如果数据的变化没有影响到顺序,那么性能没有影响。如果数据的变化影响到了顺序,那么性能也会受到影响。因为没有key的时候,我们是根据顺序进行对比的。
二是有key的时候,key是不建议使用索引的,因为索引是会变化的。我们推荐使用唯一值,对比的使用会根据key值进行对比。
48、路由模式
路由模式分为三种
-
abstract,支持所有 JavaScript 运行环境,如果发现没有浏览器的 API,路由会自动强制进入这个模式。
-
hash模式:
a有#/,
b通过window.location.href进行跳转
c通过window.onhashchange进行监听
-
history模式:(推荐使用)
a. 没有#/
b. 通过history.pushState和history.repleaceState进行跳转,
c. 通过onpopState进行监听,只能监听前进和后退,无法监听history.pushState和history.repleaceState, 源码中将history.pushState和history.repleaceState,进行了统一的包装,通过pushState函数进行包装,不管是history.pushState还是history.repleaceState实际底层最终都会通过pushState这个函数进行跳转,通过pushState进行监听
d. 刷新会404,需要后端的支持。
49、为什么会出现跨域(同源策略)
跨域产生的原因就是同源策略的存在。
同源策略是浏览器提供的一种安全机制,可以防止跨站脚本攻击,
也就是A网站请求B网站的资源,若是不同源,则不能够请求。
满足同源的条件是:协议、域名/IP地址、端口号,三者完全一致则表示同源,可以进行资源共享
有一项不同既不同源,代表是两个网站,此时资源不共享
跨域的本质是浏览器,在浏览器中才会出现。
那么为什么会出现跨域呢?
是因为当下,最流行的就是前后分离项目,也就是前端项目和后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域的现象。
跨域存在着两种,一种是开发环境的跨域,一种是生产环境的跨域
生产环境的跨域的话,等我们项目打包上线,会有运维人员处理的。
我讲一下开发环境的跨域的解决方式吧。
解决的方法有:JSONP,CORS(后端开启),代理服务器
如果需要我们自己解决的话,就要用到代理服务器。
我们在使用浏览器进行请求的时候,不再直接请求服务器的接口,而是向本地服务器进行请求,这就不会出现跨域。再让本地服务器向服务器的接口进行请求,两个服务器之间也不存在跨域问题。再反向进行响应。
这整个过程就是本地服务器启到一个代理服务器的作用。
当然也可以使用jsonp,但他只支持get,不支持post。
50、环境变量
一个项目在开发的过程中,会经历各种过程才会发布,每个过程的环境不同,就会需要不同的配置参数,所以就可以用环境变量来方便我们管理。
每个不同的环境有一个不同的文件,这些文件都和src同级
一个项目,基准地址(环境)会有3套,分别是开发期间的、测试的、线上的
在package.json中会有相对应的配置,运行不同的命令代表不同的环境,使用的就是不同文件内,环境变量的值。
环境变量是存放基准地址的,不同的环境(比如测试,开发期间,上线等)的基准地址可能会不同。用环境变量来存储。环境变量的值来自于不同的环境文件.env.的文件中
51、nextTick
作用:数据发生变化后,可以利用nextTick获取最新的视图
原理:数据发生变化会驱动视图,这是一个异步操作。为了性能,等所有数据变化后,进行合并更新视图。因为这个原因,导致数据发生变化后,无法立即获取最新视图。
解决方案就是使用nextTick
nextTick不传回调的时候,则内部是promise对象
nextTick传回调的时候,则内部是setTimeout定时器
52、token过期问题
token一般的过期时间是2个小时。这里要引入一个其他内容,refresh_token过期时间较长(一周、两周)
在token过期的时候,我们会在用户不知情的情况下,偷偷的发送一个请求,获取新的token。通过refresh__token偷偷进行换取,登录状态就可以继续维持一周到两周。如果refresh__token也过期了,那么就会跳转到登录页,需要重新进行登录。
如何判断token过期
1、前端主动处理
当判断token过期时候,不再发送请求,就可以优化性能。减少网络请求的次数
借助时间戳:
登录成功的时候存下token的时间戳
发送请求前,获取当前的时间戳-存下token的时间戳,等到的值如果超过了token的有效期(两个小时),那么就不发送请求,而是跳转到登录页面,要求重新登录。
2、前端被动处理,由后端主动处理。也就是需要判断请求的时候,返回的状态码,一般401是登录过期,也就是token过期的情况(当然具体是要看后端的返回,这里的401只是一般情况下)
53、.sync修饰符
修饰符有哪些:
v-on的
●.stop - 阻止事件冒泡
●.prevent - 阻止默认行为
●.once - 程序运行期间, 只触发一次事件处理函数(不可以和其他修饰符连用)
●.native - 在某个组件的根元素上监听一个原生事件
v-model的
●.number 以parseFloat转成数字类型
●.trim 去除首尾空白字符
●.lazy 在change时触发而非inupt时
作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)
原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发
.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件
:属性名.sync=‘变量’
等价于
:属性名=‘变量’(父向子传值)
@update:属性名=‘变量=$event’(@update表示自定义事件)
54、你们的项目是如何打包部署的
1运行npm run build 进行打包,可以进行打包优化,打包之后将dist文件交给后端
2首先自测,没问题我们将代码合并到development分支,我们的development分支是受保护的,所以需要进行合并申请,审核通过合并成功,代码合到release分支,测试人员进行自动化测试,从release合到master,从master发布一个tag发布到线上.实际上我们的是cicd.会进行自动打包部署gitlab+docker+Jenkins
55、webpack打包优化 3
1移除console
2soucemap,可以映射,精确的定位到开发代码的哪一行
3splitChunks:将公共代码进行提取,设置那些重复使用的代码,用合并方式进行打包
4vue-cli3默认开启prefetch,在加载首页的时候,就会提前获取用户可能访问的内容,提前加载其他路由模,所以我们要关闭该功能
这里要注意:不会影响首屏的加载速度,实际是为了优化子页面,可以快速打开子页面,但是像移动端,用户可能只会访问首页,也预加载其他模块的资源,浪费用户流量。
5打包成gzip,可以进行资源请求的时候速度更快
6runtimeChunk:运行时代码,也就是异步加载的代码,比如路由懒加载的都属于运行时代码。
没有开启runtimeChunk,运行时代码或者代码没有发生变化,重新打包时,主模块也会重新打包hash会发生变化。会导致项目部署后,强缓存失效。
开启runtimeChunk,会将运行时代码的信息单独存放在runTime中,主模块就不会被影响,也就不会重新打包,可以继续使用本地缓存。(但要配合7使用)
7script-ext-html-webpack-plugin:用这个插件可以将runTime代码生成到行内
8通过image-webpack-loader进行图片的打包压缩
9开启路由懒加载 将每个路由进行单独打包
10排除打包,用externals将比较大的包排除掉,然后引入响应的CDN资源
56、首屏优化 3
1soucemap: 关闭
2路由懒加载
3cdn资源
4splitChunks: 提取公共资源
5图片压缩
6gzip
7runtimeChunks
8ssr: 服务端渲染
●解决首屏加载速度慢的问题,因为首屏服务端直接返回,不需要加载js文件
●还可以解决seo,html不再是只有一个id为app的标签,更加有利于seo搜索
●实现ssr的方法是使用vue结合nuxt
spa单页面应用
我们vue是使用spa的,spa页面响应速度快,可以减轻服务器压力,但是不利于seo,首屏加载也会比较慢。
这时候就会用到ssr。也就是在vue中使用nuxt框架。虽然可以让爬虫更容易爬到数据,响应速度也更快,但是会增加服务器的压力,并且开发的难度也比较大。
57、技术栈(了解一下)
1vue(全家桶 vue、vuex、vueRouter、axios、elementUi、)echarts-cos-js-sdk-v5、dayjs、js-cookie、vuex-persistedstate
2xlsx、file-saver excel导入导出
58、封装组件思想
(1)组件封装思想
-
组件的结构:结构考虑复用灵活,一般使用插槽、允许自定义
-
组件的样式:考虑支持自定义,一般使用属性传值(通过样式或者类名)
-
组件的数据:通过数据传递
-
暴露事件:例如弹框组件,点击遮罩弹框关闭,用户使用组件的时候,也需要监听到点击遮罩的行为,用户可以进行自定义的逻辑
(2)如何创建一个全局组件
通过 Vue.component 来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过 template 配置组件的结构,data 定义数据等等
(3)如何创建一个局部组件
在组件内部通过 components 来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
(4)如何定义局部自定义指令
在组件内部通过 directives 来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
(5)如何定义局部过滤器
在组件内部通过 filters 来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
59、出现bug怎么解决
线上出现bug,首先要去评估bug的影响范围。
首先看是在新版本上的还是旧版本上
新版本上,回退到上一个版本。然后去fixBug进行修复新版本上产生的bug。
旧版本上,说明这个bug很长时间才出现,说明不是很严重,可以定位bug的位置进行修复,完成后将bug修复的代码发布到线上。也可以等下个版本上线的时候,进行覆盖。
总的来说,就是要将影响范围缩到最小为主要思想,来使用不同的方式进行bug的解决。
60、工作流程
首先我们组长去拿到项目需求,然后他会开会召集我们去进行开发时间评估,然后ui会根据需求出设计稿,前后端会沟通API接口,沟通完以后我们开始静态页面的开发,后端也开始API接口的开发,后面我们静态页面开发完了,如果后端接口写好了我们直接调用渲染页面,后端还没完成的话我们可以先用mock模拟数据进行测试。后边如果发现啥问题的话再跟后端协商处理(联调),我们自己写的时候是会边写边测试的,基本完成感觉没啥问题了就会交给测试人员测试,然后就开始测bug,测试人员会把bug发布到协作平台上(禅道、ones),然后我们去解决bug。 等到项目可以上线的时候就由运维人员发布上线。
61、如何和后端进行联调
概念:在我们开发的过程中,发送请求的ajax数据都不是后端返回的真数据,而是我们自己通过接口mock模拟的假数据,当前端的代码编写完成后,后端的接口也写好后,我们就需要把mock数据换点,尝试使用后端提供的数据,进行一个前后端的调试,我们会把这个过程叫做前后端接口联调。
我们需要测试后端的数据和我们所使用的mock数据是否适配,格式是否正确。看所返回的数据是否够用。比如你想实现分页或者列表功能,可是后端就只写了两条数据, 这些问题就会在联调的过程中进行协商解决。
我们公司开发是前后端分离,部署时是一个域名和一台服务器。
62、link与@import的区别是什么
1、从属关系区别
@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。
2、加载顺序区别
加载页面时,link标签引入的 CSS 被同时加载;@import引入的 CSS 将在页面加载完毕后被加载。
3、兼容性区别
@import是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link标签作为 HTML 元素,不存在兼容性问题。
4、DOM可控性区别
可以通过 JS 操作 DOM ,插入link标签来改变样式;由于 DOM 方法是基于文档的,无法使用@import的方式插入样式。
63、http和https
http超文本传输协议,用于在浏览器和服务器之间传递信息。不适合传输敏感信息。
https安全套接字超文本传输协议,在http的基础上加上了SSL协议。
http的默认端口是80,https是443
http是传输过程是明文的,https是加密传输的
http是无状态连接,https由ssl+http构成的
http是基于7层协议中的应用层,https是基于传输层的
https使用非对称加密进行密钥传输,使用对称加密进行数据传输
对称加密(数据传输)
发送方和接收方使用同一个密钥(一串字符串)进行加密和解密
1服务端使用密钥进行加密
2客户端使用密钥进行解密
但是第一次要传输一次密钥,如果密钥被拦截,就被破解了
性能好,速度快,缺点,密钥被拦截被破解
非对称加密(密钥传输)
一个公钥,一个私钥
1服务端使用私钥解密
2客户端使用公钥加密
优点:安全
缺点:性能差,耗时间
64、双向数据绑定? 2
数据发生变化,同步视图,视图发生变化,同步数据。
v-model可以完成数据双向绑定。
但原理是v-on绑定事件和v-bind绑定数据,是一个语法糖。
v-bind可以实现数据变同步视图,这是因为数据响应式的原理。
v-on绑定事件可以实现视图变同步数据,这是数据响应式的原理。
65、是单向数据流? 1
在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
66、事件传参 2
●事件函数没有括号,表示没有传递参数,则默认的第一个参数是事件对象。
●事件函数有括号,就算为空,也表示传递参数。则形参和实参一一对应。如果需要事件对象,实参用$event进行传递。
67、自定义指令:directive
当vue 提供的系统指令满足不了我们的需求时,我们就需要自定义指令
全局通过 Vue.directive 进行自定义指令的定义。
局部通过directives进行定义。key为自定义指令的名字,value为对象,对象内有下面三个钩子函数。
●bind(相当于created):只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
●inserted(相当mounted):被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
●update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
67.1.vue自定义指令介绍:
通过 Vue.directive 进行自定义指令的定义
指令的注册方式和「过滤器」「混入」「组件」注册的方式一样都分为两种:一是全局注册,二是局部注册。,先介绍自定义指令的钩子函数.
(1)自定义指令的钩子函数
Vue 提供了自定义指令的5个钩子函数:
bind:指令第一次绑定到元素时调用,只执行一次。在这里可以进行一次性的初始化设置。
inserted:被绑定的元素,插入到父节点的 DOM 中时调用(仅保证父节点存在)。
update:组件更新时调用。
componentUpdated:组件与子组件更新时调用。
unbind:指令与元素解绑时调用,只执行一次。
注意:
1.除 update 与 componentUpdated 钩子函数之外,每个钩子函数都含有 el、binding、vnode 这三个参数
-
在每个函数中,第一个参数永远是 el, 表示被绑定了指令的那个 dom 元素,这个el 参数,是一个原生的 JS 对象,所以 Vue 自定义指令可以用来直接和 DOM 打交道
-
binding 是一个对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers
- oldVnode 只有在 update 与 componentUpdated 钩子中生效
- value就是自定义指令后要赋值的数据,如v-image=‘….’
- 除了 el 之外,binding、vnode 属性都是只读的
68、vue 的两个核心
组件系统、数据驱动(数据响应式)
69、组件插槽
●默认插槽:
○在组件标签中间可以传递一些子节点
○组件内部利用 slot 标签进行接收
●具名插槽(可以传多个插槽)
○在组件标签中间通过定义 slot 的名字传递子节点
○组件内部利用 slot 的 name 进行对应接收
●作用域插槽
○在组件内部定义数据,将数据传递给插槽的结构
○通过给 slot 动态绑定属性
○插槽内部:通过 slot-scope=“scope”来接收
70、vue 单页面应用的优缺点
缺点:
●不利于 seo
●兼容到 ie9
●初次加载耗时相对增多
优点
●用户体验好,不用重新刷新整个页面
●前后端分离
●mvvm 设计模式
●减轻服务期压力,只需要服务器提供数据
71、v-if和v-for为什么避免同时使用
v2中:
v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,再利用v-if进行移除
解决方式:
●v-if写到外层
●先通过计算属性将数据计算好
v3中: v-if优先级高
72、mock假数据
现在的项目都是前后端分离的,在前后端同时开发的过程中,后端接口数据没有出来,前端可以使用mock假数据。(可使用Apifox网站提供的mock功能制作mock请求接口)
优点:
团队可以并行工作。更好的进行前后端分离。
增加测试的真实性,通过随机数据,模拟各种场景。
开发无侵入,不需要修改既有代码,就可以拦截ajax请求,返回模拟的响应数据。
数据类型丰富,支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
方便扩展,支持扩展更多数据类型,支持自定义函数和正则。
不涉及跨域问题
73、mixins
mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可
可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中
优先级:
●生命周期:组件和混入的都会调用(混入的先调用)
●data数据::进行合并,发生冲突以组件为主,mixins中的会被覆盖
●methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
74、axios取消重复请求
1场景:如果在输入框中输入12,然后先发1的请求,再发12的请求,但是如果关键词1的请求响应慢,12请求的数据先回来,那么1请求回来的数据会覆盖12的数据。和我们所需要的结果产生了出入。
2解决方案:使用取消请求的方式。
3原生的ajax可以使用abort()
4取消axios请求,则使用axios内部的cancelToken方法
75、浏览器的进程和线程
当我们启动某个程序时,操作系统会给该程序创建一块内存(当程序关闭时,该内存空间就会被回收),用来存放代码、运行中的数据和一个执行任务的主线程,这样的一个运行环境就叫进程
而线程是依附于进程的,在进程中使用多线程并行处理能提升运算效率,进程将任务分成很多细小的任务,再创建多个线程,在里面并行分别执行。
●进程与进程之间完全分离,互不影响。
●进程与进程之间传递数据使用进程通信管道IPC
●一个进程中可以并发多个线程,每个线程中可以并发执行不同的任务
●一个线程出错会导致所在的整个进程奔溃
●同一个进程内的线程之间可以相互通信和共享数据
●进程关闭,会被操作系统回收至内存空间。
一个浏览器会包含多个进程,一般为:
1浏览器主进程: 负责控制浏览器除标签页外的界面,同时提供存储功能
2GPU进程:负责整个浏览器界面的渲染。
3网络进程:负责发起和接受网络请求。
4插件进程:主要是负责插件的运行。
5渲染进程:负责控制显示tab标签页内的所有内容。
我们在浏览器中的工作,多数都是通过渲染进程完成的。
渲染进程中的线程:
1GUI渲染线程:负责渲染页面
2JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。和GUI不能同时运行。
3计时器线程:负责计时器工作
4异步请求线程: 处理异步的
5事件触发线程:主要用来控制事件循环eventLoop
76、三次握手和四次挥手 1
三次握手
●客户端向服务端发送信息,“我发信息,你收得到吗”
●服务端向客户端发送信息,“收到了,我发信息你收得到吗”
●客户端向服务端发送信息,“收到了,我们连接成功了”
四次挥手
●客户端向服务端发送信息,“我说完了”
●服务端向客户端发送信息,“好的,我知道了,我看看我还有没有要说的”
●服务端向客户端发送信息,“我也说完了”
●客户端向服务端发送信息,“好的,我知道了” 2MSL之后挂断
77、浏览器输入url,敲下回车后发生的事情 1
●URL解析
○首先判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行解析。如果是一个关键字,会使用浏览器默认的搜索引擎去添加关键字。
●DNS查询
○通过域名解析得到IP地址(先进行本地解析,没有再通过DNS解析)
●三次握手
○得到服务器的IP地址后,进行tcp连接
●发送http请求
○tcp连接成功后,浏览器发送http请求到目标服务器(数据请求)
●响应请求
○返回http响应消息(返回数据资源)
●四次挥手(http1.0的时候)
○数据请求完成以后,为避免资源占用和损耗,关闭连接。
●页面渲染
○对返回的资源进行解析渲染
●四次挥手(http1.1的时候,会判断有没有keep-alive,有,就会在关闭网页的时候进行断开。没有keep-alive,请求结束就直接断开连接。)
78、防抖和节流
防抖和节流都是为了避免函数被多次调用导致页面调用,但它们的本质不一样,防抖是将多次执行变为最后一次执行,节流是将多次执行变为每个一段时间执行一次。
防抖是指触发事件函数后,函数在n秒后只能执行依稀,如果n秒内再次触发,会重新计算时间。也就说说连续触发,只执行最后一次。
场景:
●搜索框搜索输入,只需要用户最后一次输入完成,再发送请求。
节流会限制一个函数在n秒内只能执行一次,过了n秒又可以执行一次。
场景:
●发送验证码的时候,60秒内只能发送一次。
79、假值有哪些
假值就是值boolean转出来是false的
10
2Null
3NaN
4False
5undefined
6空字符串
80、get和post的区别
●从标准上来说:
GET 用于获取信息,是无副作用的,是幂等的,且可缓存 ,安全性差
POST 用于修改服务器上的数据,有副作用,非幂等,不可缓存
●从请求报文上来说:
GET 和 POST 只是 HTTP 协议中两种请求方式(异曲同工),而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。
在报文格式上,不带参数时,基本一致,带参数时,在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中。
81、响应拦截器里面都做什么事情
●请求拦截器 在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装;
●响应拦截器 同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
82、实现实时更新
websocket
比如直播间弹幕啊,股票的实时数据更新,进入页面客服的自动发送信息等等
websocket是一种数据通信协议,常见的是http协议。
http协议的缺陷:通信只能由客户端发起,http基于请求响应实现。
83、vue 组件中的 data 为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
83.1 vue组件的data 为什么必须是一个函数
组件实例对象data必须为函数,目的是为了防止多个组件实例对象之间共用一个data,产生数据污染。因为当data是函数时,当每次组件实例化的时候这个函数将会被调用,返回一个新对象,计算机会给这个对象分配一个新的内存地址,所以各个组件的数据地址都不一样,即每个组件中的数据不会相互干扰,也就是为了保证组件的独立性和可复用性,如果data是个函数的话,每复用一次组件就会返回新的data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响
83、babel的使用过程
babel是一个降级语法的工具
首先需要下载安装这个包
在根目录比如babel。config。js中babelrc进行配置
在plugins中配置babel的插件
84、浅谈事件冒泡和事件捕获
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
阻止事件冒泡e.stopPropagation()
简单来说,当你鼠标在浏览器上点击了一下。
1浏览器捕获到了click事件。
2然后浏览器根据你点击的事件,从window开始向下,就会触发每个父祖element捕获模式的事件回调。
3直到找到点击所在的最终(最小的element)
4然后浏览器开始继续又向上冒泡其父祖element的click事件,直至window。
5默认的事件都是冒泡模式下触发的。
85、二维转一维数组,扁平化
1、flat
flat是ES10新增的一个数组处理的方法,非常的好用,它专门用来扁平化数组。
合并返回新数组
参数是层级数,默认为1,层级未知可以用Infinity
2、concat + 扩展符
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
扩展运算符(...)可以将数组转为用逗号分隔的参数序列。
只能二维转一维,多维就不行
3、reduce + concat
reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组 。
callback:函数中包含四个参数
- previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
- currentValue (数组中当前被处理的元素)
- index (当前元素在数组中的索引)
- array (调用的数组)
initialValue (作为第一次调用 callback 的第一个参数。)
将初始值设置为了[]空数组,然后将需要扁平化的数组的每一项都用concat重新连接,最终得到一个一维数组。
4、toString + split
先使用 toString 把数组转成字符串,再使用 split 把字符串转回数组:
该方法存在局限性,不适用于一些包含相对特殊子元素的数组,比如包含 null、undefined、对象类型等。
使用map是为了让数组元素变为Number类型。
86、前端性能优化方式
●缩小html、css和js
○使用到插件Gulp和JSMin
87、单点登录
概念: 一个大型公司有很多系统,用的是同一个账号,登录一个系统时,其它系统也可以正常访问
cookie:
某个系统登陆成功,再次去登录其它系统系带token,token如何在多个网站中共享
domain/path
domain: 设置网站域名 设置为主域名(父级域名)/二级域名是可以获取到cookie数据
path: 路径 /
脱离父级域名不可以共享了
认证中心
iframe
88、webpack+browserify+gulp+grunt四个工具的区别
●gulp和grunt是前端自动化构建的工具,帮助用户完成js\css压缩、less编译等(只不过现在webpack也可以完成压缩等任务,可以替代gulp的这部分功能)。
●webpack和browserify是前端模块化方案,与seajs和requirejs是一个东西,只不过seajs和requirejs是在线编译方案,引入一个CMD\AMD编译器,让浏览器能认识export、module、define等,而webpack和browserify是预编译方案,提前将es6模块、AMD、CMD模块编译成浏览器认识的js。
●他们之间的区别见以上两点,只不过相互之间也会有一些相似的功能。
●grunt配置复杂繁重,是基于文件流的操作,比较慢;gulp是基于内存流的操作,配置轻量级,代码组织简单易懂,异步任务。
●webpack的话,就是配置复杂,文档杂乱,插件繁多,难于上手。
89 em/px/rem/vh/vw 这些单位有什么区别?
一、介绍
传统的项目开发中,我们只会用到px
、%
、em
这几个单位,它可以适用于大部分的项目开发,且拥有比较良好的兼容性
从CSS3
开始,浏览器对计量单位的支持又提升到了另外一个境界,新增了rem
、vh
、vw
、vm
等一些新的计量单位
利用这些新的单位开发出比较良好的响应式页面,适应多种不同分辨率的终端,包括移动设备等
二、单位
在css
单位中,可以分为长度单位、绝对单位,如下表所指示
CSS单位 | |
---|---|
相对长度单位 | em、ex、ch、rem、vw、vh、vmin、vmax、% |
绝对长度单位 | cm、mm、in、px、pt、pc |
这里我们主要讲述px、em、rem、vh、vw
px
px,表示像素,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的,所以像素为计量单位被分在了绝对长度单位中
有些人会把px
认为是相对长度,原因在于在移动端中存在设备像素比,px
实际显示的大小是不确定
这里之所以认为px
为绝对单位,在于px
的大小和元素的其他属性无关
em
em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸(1em = 16px
)
为了简化 font-size
的换算,我们需要在css
中的 body
选择器中声明font-size
= 62.5%
,这就使 em 值变为 16px*62.5% = 10px
这样 12px = 1.2em
, 10px = 1em
, 也就是说只需要将你的原来的 px
数值除以 10,然后换上 em
作为单位就行了
特点:
- em 的值并不是固定的
- em 会继承父级元素的字体大小
- em 是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸
- 任意浏览器的默认字体高都是 16px
举个例子
1<div class="big">
2 我是14px=1.4rem
3 <div class="small">我是12px=1.2rem</div>
4</div>
样式为
1<style>
2html {font-size: 10px; } /* 公式16px*62.5%=10px */
3.big{font-size: 1.4rem}
4.small{font-size: 1.2rem}
5</style>
这时候.big
元素的font-size
为14px,而.small
元素的font-size
为12px
rem
rem,相对单位,相对的只是HTML根元素font-size
的值
同理,如果想要简化font-size
的转化,我们可以在根元素html
中加入font-size: 62.5%
1html {font-size: 62.5%; } /* 公式16px*62.5%=10px */
这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助
特点:
- rem单位可谓集相对大小和绝对大小的优点于一身
- 和em不同的是rem总是相对于根元素,而不像em一样使用级联的方式来计算尺寸
vh、vw
vw ,就是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,vh
则为窗口的高度
这里的窗口分成几种情况:
- 在桌面端,指的是浏览器的可视区域
- 移动端指的就是布局视口
像vw
、vh
,比较容易混淆的一个单位是%
,不过百分比宽泛的讲是相对于父元素:
对于普通定位元素就是我们理解的父元素
- 对于position: absolute;的元素是相对于已定位的父元素
- 对于position: fixed;的元素是相对于 ViewPort(可视窗口)
三、总结
px:绝对单位,页面按精确像素展示
em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size
按自身来计算,整个页面内1em
不是一个固定的值
rem:相对单位,可理解为root em
, 相对根节点html
的字体大小来计算
vh、vw:主要用于页面视口大小布局,在页面布局上更加方便简单
90.vue2中的响应式原理简述
响应式原理主要就是通过数据劫持,依赖收集,派发更新的方式来实现的
1.数据劫持,vue2是通过Object.defineProperty方法的get、set来将对对象进行递归劫持。
其中修改对象的属性时 就会触发set, 使用对象的属性时就会触发get
2.依赖收集(观察者模式)。使用watcher
进行观察数据使用的地方
3.派发更新(发布订阅模式):使用dep
收集watcher
,数据更改时,通过notify
方法通知dep
里的watcher
去进行相应的更新
使用Object.defineProperty做响应式的缺点有三点
1.深度监听,不管数据用不用都会需要一次性递归到底,计算量比较大,会造成性能浪费。
2.描述符只有get和set,无法监听新增属性和删除属性的操作
3.数组没有使用劫持的模式,而是通过重写数组原型上的方法,来实现数组的响应式
这三个缺点中,第二点是defineProperty本身API的缺陷,而第一点和第三点都是出于性能考虑而做的取舍
解决缺点的方法:
当我们通过数组的方法去更改数组时或是直接删除data数据,数据并不能实现响应式,因为Object.defineProperty是没有办法处理属性删除和新增的
因此vue2的响应式,通过数组方法(pop,push),或是删除,vue是不能监听的
vue2中通过vue2中可以通过vue.datele和vue.set这些vue内置api来改变属性,实现响应式。
91.vue响应式原理
- 在new vue()的过程中,vue会对传入的options进行初始化,里边包括对data定义的数据的初始化,同时对数据进行观测(observe)。
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。- 对不同数据的响应式
- 对象:
- 1.通过Object.defineProperty()进行数据劫持,配置getter和setter
- 2.getter取值触发,setter设置值的时候触发
- 3.初次宣染页面触发getter,同时dep(目标)会把调用此属性的watcher(依赖/观察者)收集到一个集合中;而在我们给属性赋值(修改属性)时,触发setter函数同时触发dep(目标)去通知集合中的watcher(依赖/观察者)更新,做到数据变更驱动视图变更
- 数组:对于数组,get拦截过程和对象拦截过程相似,Vue重写了数组的七种方法,所以,通过这七种方法更新数组值时,会通知所有依赖进行更新操作。
- 对象:
- 缺点:
- 由于利用object.defineProperty递归对对象进行规测,所以对象的层级过深会导致性能受影响
- 此时的观测是基于data定义的对象属性(observe(data)初他化之后执行,之后不再执行),如果后期给对象新增/删除属性时,Vue是拦截不到的,也就是说不是响应式的,因为数据劫持是在数据初始化阶段进行劫持的。
- 可以使用$set让对象自己去notify,用户对对象属性赋值新对象vue会对这个对象进行观测
- 通过数组索引修改数组或者改变数组长度,是不会触发更新的,通过7种重写的方法更新视图
- 数组重写的7种方法push-shift-unshift-pop-reverse-sort-splice
- Vue支持在data屋性中自定义setter和getter在通过object.defineProperty进行动持时会进行合并
- vue中的数据不响应又响应的原因:单纯通过数组索引改变数组的数据或者对象新增属性,不是响应式的,但当它下面又操作其他数据的时候,此时时可以响应的,因为下面数据发生变化,组件进行了更新,所以拿到了响应后的值展示在页面上
- 数据响应劫持顺序
92.$set使用场景
使用场景:
- 在vue中,当你需要对象新增/删除属性,并且需要确保这个属性是双向绑定==的,那么就需要使用
this.$set()方法(或vm.$set
),进行强制视图更新。 - 解决通过索引号更改数组不响应的情况
-
this.$set()是绑定在vue的.prototype原型上,vue是使用Object.defineProperty给对象做了一层拦截,当触发get时进行依赖收集,当触发set时触发依赖导致渲染函数执行重新渲染。第一次触发get是在首次加载页面渲染的时候触发,就会递归的将对象的属性都依赖收集。而我们修改对象新加的属性不是对象已有属性,并未进行依赖收集,所以导致不会重新渲染。
-
调用方法: this.$set( target , key , value)
target: 要更改的数据源(可以是一个对象或者数组)
key 要更改的具体数据 (索引)
value 重新赋的值
注意:如果不是使用this.$set动态添加属性值,这个属性值是可以添加上,但是不会作为响应式数据,也就是不会重新解析模板。
93.为什么只对对象劫持,而要对数组进行方法重写?
数组的元素大概率是成百上千的,所以对数组下标进行劫持的话会非常消耗性能。Vue通过对数组原型上方法的重写(push,pop,unshift,shift,reverse,sort,splice),实现数组的响应式
90命名空间namespace
面试题2
3.介绍 v-if/v-show
v-if和v-show的效果都是实现dom元素的显隐,实现原理不同,v-if是通过移除或添加dom元素实现的,如果是组件会触发生命周期,不适用于频繁切换显隐的场景;v-show是通过样式的display:none/block来实现元素显隐的,适用于频繁切换显隐的功能场景
5.call/apply/bind作用和区别
1.call和apply 可以立即调用函数,this指向他们的第一个形参
2.bind 不能调用函数,会返回一个新函数,以后调用了才会执行,this指向他的第一个形参。
3.call和apply 作用类似,都可以改变指针和执行函数,区别在于传参不同,call 需要单个传参,apply 通过数组传参
fucntion.call(thisArg, arg1, arg2, ...)
function.apply(thisArg, [arg1, arg2, ...])
fucntion.bind(thisArg, arg1, arg2, ...)
6.闭包是什么
闭包:在JS中,局部变量即函数内的定义的变量,在函数执行完毕之后,局部变量作用域会被销毁、内存也会被回收,但由于闭包在函数内部创建一个子函数,且子函数可访问父函数中的作用域/变量(即持有引用,作用域内的变量不会被回收),即使父函数执行完,作用域也不会被销毁,这就是闭包,而且闭包引用的变量是存于堆内存中
闭包作用:可以阻止变量被回收,数据私有化,防止污染全局
缺点:比普通函数更占用内存,因为闭包的数据没有被回收,可能会造成内存泄漏
闭包作用:可以防止变量被回收,令数据私有化,延伸变量的作用范围
闭包案例:循环注册点击事件,防抖节流
7.原型、原型链介绍
-
原型
每一个构造函数都有一个prototype属性,指向另一个对象,构造函数通过原型分配的函数是所有对象所共享的。由于在构造函数中定义的方法在创建实例时,都会开辟一个新的内存从而造成内存浪费的问题,这时我们就可以把那些不变的方法直接定义在prototype对象上,这样所有的对象的实例就可以共享这些方法。
-
对象原型_____proto_____
对象都会有一个_____proto_____属性,指向构造函数的prototype原型对象,之所以我们对象可以使用原型对象的属性和方法,就是因为对象有_____proto_____原型的存在。
①实例.__proto__和构造函数.prototype是等价的
②方法的查找规则:首先看实例对象上是否存在这个方法,如果存在就调用实例本身的方法,如果没有就去构造函数原型对象上去查找,如果还是没有就继续向Object.prototype中查找,直到返回null (就近原则)
③__proto__对象原型的意义就在于为对象的查找机制提供一个方向,它是一个非标准属性,因此实际开发中不可以使用这个属性,它只是内部指向原型对象。
-
constructor构造函数
对象原型和构造函数原型对象里面都有一个属性:constructor。我们称为构造函数,因为它指回构造函数本身。
而__proto__原型的constructor也是同样指向的是构造函数,是通过 实例.proto 先指向原型对象然后再 通过 constructor指向构造函数
很多情况下,我们需要手动的利用constructor这个属性指回原来的构造函数,例如,我们修改了原来的原型对象,给原型对象赋值的是一个对象,那么就必须手动的利用constructor指回原来的构造函数。在对象中写:constructor:目标构造函数。
-
原型链
①只要是对象就有__proto__原型,指向原型对象
②实例的原型对象里面的proto原型指向的是Object.prototype
③Object.prototype里面的proto原型指向的是null
④由__proto__原型连接而成的'链子'就是原型链
8.继承方法有哪些?
- 原型链继承
核心:将父类的实例作为子类的原型。
优点:父类方法可以复用。
缺点:- 父类的引用属性会被所有子类实例共享
- 子类构建实例时不能向父类传递参数
SubType.prototype = new SuperType()
// 所有涉及到原型链继承的继承方式都要修改子类构造函数的指向,否则子类实例的构造函数会指向SuperType。
SubType.prototype.constructor = SubType;
- 构造函数继承
核心:将父类构造函数的内容复制给了子类的构造函数。这是所有继承中唯一一个不涉及到prototype的继承。
优点:和原型链继承完全反过来- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数 -
缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。
SuperType.call(SubType);
-
组合继承
核心:原型式继承和构造函数继承的组合,兼具了二者的优点。
优点:- 父类的方法可以被复用
- 父类的引用属性不会被共享
- 子类构建实例时可以向父类传递参数
缺点:
- 调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性,从而覆盖了子类原型中的同名参数。这种被覆盖的情况造成了性能上的浪费。
-
ES6 Class extends
核心: ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。但是,寄生组合继承是先创建子类实例this对象,然后再对其增强;而ES6先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
ES6继承与ES5继承的异同:
相同点:本质上ES6继承是ES5继承的语法糖。
不同点:
- ES6继承中子类的构造函数的原型链指向父类的构造函数,ES5中使用的是构造函数复制,没有原型链指向。
- ES6子类实例的构建,基于父类实例,ES5中不是。
9.token过期
- 前端主动处理(为什么前端去处理?优先前端,如果已经判断过期,不用发请求,可以较少网络请求)
- 借助时间戳登录成功存token的时间戳, 发请求时当前时间戳,当前时间戳 - 存token的时间戳 > token过期的时间
- 前端被动处理, 后端主动处理 , 根据后端的同学返回的code 处理
思考一下:如果该token过期,直接退出,跳转到登录页,交互好吗??
解决方案:refresh_token
token一般过期时间为2小时,refresh_token,过期时间较长(一周-两周)
token过期后, 我们在背后(用户不知情的情况下),偷偷的发送一个请求获取新的token, 通过refresh_token进行换取,登录状态就可以维持一周-两周,如果refresh_token也过期,跳到登录页
无感刷新: 更新token过程中如果有多个请求并发,可以第一个节流阀,并可先将请求接口配置项用数组存起来,待获取新的token后面在重新发送,从而实现无感刷新,如果只有一个请求的话,获取token后直接请求即可(return request(error.config))
let isRefreshing = false // 标记是否正在刷新 token
let requests = [] // 存储待重发请求的数组
// 响应拦截器(更新token)
request.interceptors.response.use(
// 响应成功进入第1个函数
function (response) {
return response
},
// 响应失败进入第2个函数,该函数的参数是错误对象
async function (error) {
// 如果响应码是 401 ,则请求获取新的 token
// 响应拦截器中的 error 就是那个响应的错误对象
console.dir(error.response)
if (error.response.status === 401) {
// 校验是否有 refresh_token
const tokenObj = store.state.tokenObj
if (!tokenObj || !tokenObj.token || !tokenObj.refresh_token) {
router.push('/login')
// 代码不要往后执行了
}
// 如果有refresh_token,则请求获取新的 token
if (!isRefreshing) {
isRefreshing = true
try {
const res = await request({
method: 'POST',
url: '/v1_0/authorizations',
headers: {
Authorization: `Bearer ${tokenObj.refresh_token}`
},
data: {
code: '246810',
mobile: '18270164361'
}
})
// 如果获取成功,则把新的 token 更新到容器中
console.log('刷新 token 成功', res)
store.commit('setUser', {
token: res.data.data.token, // 最新获取的可用 token
refresh_token: tokenObj.refresh_token // 还是原来的 refresh_token(如果用新refresh_token的就永远不会失效了)
})
// 把之前失败的用户请求继续发出去
// config 是一个对象,其中包含本次失败请求相关的那些配置信息,例如 url、method 都有
// return 把 request 的请求结果继续返回给发请求的具体位置
const { token } = res.data.data
//依次请求上次因token过期请求失败的接口
requests.forEach((cb) => cb(token))
requests = [] // 重新请求完清空
//请求第一个失败的接口
return request(error.config)
} catch (err) {
// 如果获取失败,直接跳转 登录页
console.log('请求刷线 token 失败', err)
router.push('/login')
} finally {
isRefreshing = false
}
} else {
return new Promise(resolve => {
// 用函数形式将 resolve 存入,等待刷新后再执行
//储存请求失败的接口
requests.push(token => {
error.config.headers.Authorization = `Bearer ${token}`
resolve(request(error.config))
})
})
}
}
return Promise.reject(error)
}
)
export default request
10.vue watch侦听属性
作用:可以侦听data/computed/prop属性值改变, 并进行相关操作
注意点:数组的变化不需要深度监听,在watch中不要使用箭头函数,因为箭头函数中的this是指向当前作用域
computed和watch之间的区别:
1.computed能完成的功能,watch都可以完成。
2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
用法:
-
简写函数形式:"被侦听的属性名" (newVal, oldVal)
-
深度监听对象写法:"要侦听的属性名": { immediate: true, // 组件创建时立即执行一次监听 deep: true, // 深度侦听复杂类型内变化 handler (newVal, oldVal) {... }//触发函数
注:如果对象的属性较多,也可用简写形式监听函数内某个属性,写法:'msg.count'(val, oldval)
11.computed计算属性
作用:当要用的属性不存在,且可以通过已有属性计算得来时,可以用到计算属性
原理:底层借助了Objcet.defineproperty方法提供的getter和setter实现的。即初次执行通过get属性获取
优势:1. 与methods实现相比,计算属性是依赖其他数据的,生成的数据放在缓存中,不像方法要每次要调用,直接从缓存取值,效率更高,调试方便。2. 依赖项值-变化, 函数会"自动"重新执行-并缓存新的值。
12.垃圾回收GC
定义:js 的内存是自动进行分配和回收的,内存在不使用的时候会被垃圾回收器自动进行回收,但是我们需要了解垃圾回收的机制,从而防止内存泄漏(内存无法被回收)。
通常情况下有两种实现方式:引用计数,标记清除
- ie浏览器通过引用计数判断内存是否需要被回收,即判断当前内存在上下文引用计数为0时则需要销毁释放内存。其特点简单有效,缺点是当内存变量存在互相循环引用时,引用计数不为0从而导致内存泄露,
- 现在的浏览器使用标记清除方式进行垃圾回收,标记就是通过根节点(全局),标记所有从根节点开始的能够访问到的对象。未被标记的对象就是未被全局引用的垃圾对象。最终清除所有未被标记的对象
注:根节点不是指window,还包括不在window中被const和let声明的内存。
13.http 缓存 (协商缓存和强缓存)
http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。常见的http缓存只能缓存get请求响应的资源。http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源。
http缓存的分类
根据是否需要重新向服务器发起请求来分类,可分为(强制缓存,协商缓存) 根据是否可以被单个或者多个用户使用来分类,可分为(私有缓存,共享缓存) 。强制缓存如果生效,不需要再和服务器发生交互,而协商缓存不管是否生效,都需要与服务端发生交互。
-
强缓存
强缓存简单理解就是:给浏览器缓存设置过期时间,超过这个时间之后缓存就是过期,浏览器需要重新请求。存放的位置是由浏览器控制的(存在硬盘或内存中)。是否强缓存由 Expires、Cache-Control 和 Pragma 3 个 Header请求头 属性共同来控制。但是强制缓存存在一个问题,该缓存方式优先级高,如果在过期时间内缓存的资源在服务器上更新了,客服端不能及时获取最新的资源。这时怎么办?于是就有了协商缓存.
这种方式页面的加载速度是最快的,性能也是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了。
这种情况就是我们在开发种经常遇到的,比如你修改了页面上的某个样式,在页面上刷新了但没有生效,因为走的是强缓存,所以Ctrl + F5一顿操作之后就好了。 跟强制缓存相关的header头属性有(Cache-Control/Expires), 当 cache-control 和 expires 同时存在 cache-control 的优先级会比 expires 高
总结: 不向服务端发送请求,强制使用缓存数据
-
协商缓存
当第一次请求时服务器返回的响应头中没有Cache-Control和Expires或者Cache-Control和Expires过期还或者Cache-Control的属性设置为no-cache时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新。如果服务器端的资源没有修改,那么就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码,服务器就会返回更新后的资源并且将缓存信息一起返回。跟协商缓存相关的header头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since)请求头和响应头需要成对出现。
总结:当强缓存失效后,会使用协商缓存,协商缓存由服务器决定是否使用缓存
14.http常用状态码
一、1开头的状态码(信息类)
100,接受的请求正在处理,信息类状态码
二、2开头的状态码(成功类)
2xx(成功)表示成功处理了请求的状态码
200(成功,命中强缓存)服务器已成功处理了请求。
三、3开头的状态码(重定向)
3xx(重定向)表示要完成请求,需要进一步操作。通常这些状态代码用来重定向。
301,永久性重定向,表示资源已被分配了新的 URL
302,临时性重定向,表示资源临时被分配了新的 URL
303,表示资源存在另一个URL,用GET方法获取资源
304,(未修改命中协商缓存)自从上次请求后,请求网页未修改过。服务器返回此响应时,不会返回网页内容
四、4开头的状态码(客户端错误)
4xx(请求错误)这些状态码表示请求可能出错,妨碍了服务器的处理
400(错误请求)服务器不理解请求的语法
401表示发送的请求需要有通过HTTP认证的认证信息
403(禁止)服务器拒绝请求(没有权限)
404(未找到,页面失效)服务器找不到请求网页
405 请求方式错误 是get却用来post
五、5开头的状态码(服务器错误)
5xx(服务器错误)这些状态码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求的错误
500,(服务器内部错误)服务器遇到错误,无法完成请求
503,表示服务器处于停机维护或超负载,无法处理请求
15.什么是防抖和节流
-
节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
-
防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
相同点:
- 都可以通过使用
setTimeout
实现 - 目的都是,降低回调执行频率。节省计算资源
不同点:
- 函数防抖,在一段连续操作结束后,处理回调,利用
clearTimeout
和setTimeout
实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能 - 函数防抖关注一定时间连续触发的事件,只在最后执行一次,而函数节流一段时间内只执行一次
- 都可以通过使用
-
应用场景
- 防抖在连续的事件,只需触发一次回调的场景有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求
- 手机号、邮箱验证输入检测
- 窗口大小
resize
。只需窗口调整完成后,计算窗口大小。防止重复渲染。
- 节流在间隔一段时间执行一次回调的场景有:
- 滚动加载,加载更多或滚到底部监听
- 搜索框,搜索联想功能
- 防抖在连续的事件,只需触发一次回调的场景有:
16.Vue-router的三种模式
1、hash:#后面是路由路径,特点是前端访问,#后面的变化不会经过服务器,使用URL的hash来模拟一个完整的URL,当URL发生改变时,页面不会重新加载,其显示的网络路径中会有#号,这是最安全的模式,因为他兼容所有的浏览器和服务器
2、history:正常的/访问模式,特点是后端访问,任意地址的变化都会访问服务器。美化后的hash模式,路径中会去掉#。依赖于html5的history,pushState API,所以要担心IE9及以下的版本。并且还包括back、forward、go三个方法,对应浏览器的后退、前进、跳转操作,就是浏览器左上角的前进后退等按钮进行的操作
3、abstract:适用于所有JavaScript环境,例如服务器端使用Node.js。如果没有浏览器API,路由器将自动被强制进入此模式,然后在const router = new VueRouter({routes, mode:'hash|history|abstract',})
这里进行切换
总结:
1:hash模式(vue-router默认模式URL后面带#)使用URL的hash值来作为路由,支持所有浏览器 缺点:只能改变#后面的来实现路由跳转。
2:history模式(通过mode: 'history’来改变为history模式)HTML5 (BOM)History API 和服务器配置 缺点:怕刷新如果后端没有处理这个情况的时候前端刷新就是实实在在的请求服务器这样消耗的时间很多还很慢。
17.js事件循环机制
是什么:简单的说,在js代码执行时,同步任务进入主线程,即主执行栈,异步任务进入任务队列,主线程内的任务执行完毕为空时,会去任务队列读取对应的任务,推入主线程执行。上述过程的不断重复的也称为事件循环eventloop
作用:将js分成同步任务和异步任务执行,防止耗时任务造成阻塞
注:同步任务和异步任务(es5之前无异步,es5之后有promise才能发起异步任务)
为了防止某个耗时任务导致程序假死的问题,Javascript 把待执行的任务分为了两类:
同步任务 (synchronous)
又叫做非耗时任务,指的是在主线程上排队执行的那些任务,它的执行顺序是,只有前一个任务执行完毕,才能执行后一个任务
异步任务 (asynchronous)
又叫做耗时任务,它执行机制的是这样的,首先异步任务由 JavaScript 委托给宿主环境(浏览器)进行执行,完成的异步任务对应的回调函数后,会被加入到任务队列中等待执行,当JavaScript 主线程的执行栈被清空,也就是同步任务都执行完后,会读取任务队列中的回调函数,按次序执行,另外这个JavaScript 主线程从“任务队列”中读取异步任务的回调函数,在放到执行栈中依次执行。这个过程是循环的,这个机制也被叫做事件循环。
异步任务也被分为宏任务和微任务,每一个宏任务执行完之后,都会检查是否存在待执行的微任务,如果有,则执行完所有微任务之后,再继续执行下一个宏任务。如果没有则直接执行下一个宏任务。
- 常见的宏任务有
- script (可理解为外层同步代码)
- 定时器函数setTimeout/setInterval
- UI rendering/UI事件
- postMessage、MessageChannel
- setImmediate、I/O(Node.js)
- Ajax请求,文件操作方法
- 常见的微任务有
- (promise的.then().cath().finally()...)
.
- (promise的.then().cath().finally()...)
18.如何实现js函数缓存
函数缓存,就是将函数运算过的结果进行缓存,本质上就是用缓存空间替代函数的计算时间。它只是一个临时的数据存储,以便将来更快得到该数据。
如何实现:主要依靠闭包、高阶函数、柯里化、的特性实现
- 闭包:父函数体内被子函数引用变量的不被回收的特性
- 高阶函数:通过接收其他函数作为参数或返回其他函数的函数,所谓的参数函数和被返回的函数对高阶函数内都持有引用关系,即变量不会被回收,实现缓存
- 柯里化:把一个有多个参数的函数转换成多个单参数函数嵌套的形式,实际类似闭包函数作用域逐层可被访问,即作用域变量不会被回收,从而实现缓存。
使用场景:
对于昂贵的函数调用,执行复杂计算的函数
对于具有有限且高度重复输入范围的函数
对于具有重复输入值的递归函数
对于纯函数,即每次使用特定输入调用时返回相同输出的函数
19.flex相关整理
flex布局父项常见属性:
flex-direction: 设置主轴的方向
justify-content: 设置主轴上的子元素排列方式
flex-wrap: 设置子元素是否换行
align-content: 设置侧轴的子元素的排列方式(多行)
align-items:设置侧轴上的子元素排列方式(单行)
flex-flow:复合属性,相当于同时设置了flex-direction 和 flex-wrap
flex:1实际代表的是三个属性的简写
{flex-grow:1;flex-shrink:1;flex-basis:0%}
- flex-grow是用来增大盒子的,比如,当父盒子的宽度大于子盒子的宽度,父盒子的剩余空间可以利用flex-grow来设置子盒子增大的占比
- flex-shrink用来设置子盒子超过父盒子的宽度后,进行缩小的比例取值
- flex-basis:0% 设置盒子的基准宽度,并且basis和width同时存在会替代width
20.git相关整理
-
git分支和标签的区别:
tag代表了当前的提交点,是个点,tag是当前提交点的一个记录,tag名字是不能重复的,就代表了唯一的这个点,branch代表里新的支线,是个线,可以继续延展 ,当在某个分支上打了个tag,那么这个tag就代表了当前这个分支的这个点,当回滚或者检出到这个tag的时候,代码就会回到这个点
tag是静态的,branch要向前走;
稳定版本备份用tag,新功能多人开发用branch(开发完成后merge到master)。
-
git命令
1、克隆远程仓库到本地:git clone ssh/https 2、克隆远程仓库到本地并重新命名:git clone ssh/https 仓库名 3、查看本地分支:git branch 4、查看远程分支:git branch -r 5、git branch 命令: git branch //查看本地所有分支 git branch -r //查看远程所有分支 git branch -a //查看本地和远程的所有分支 git branch <branchname> //新建分支 git branch -d <branchname> //删除本地分支 git branch -d -r <branchname> //删除远程分支,删除后还需推送到服务器 git push origin:<branchname> //删除后推送至服务器 git branch -m <oldbranch> <newbranch> //重命名本地分支 6、检查当前文件的状态:git status -s 7、切换分支: git checkout 切换分支 git checkout -b mywork origin //基于远程分支”origin“,创建一个叫”mywork“的分支 8、本地拉取远程分支 git pull :将远程主机最新内容拉到本地后直接合并 git fetch :将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作分支中 //如果只想取回特定分支的更新,可以指定分支名 git fetch <远程主机名> <分支名> //注意之间有空格 9、跟踪文件并添加到临时存储区:git add 文件名 /git add . 添加所有文件 10、提交并添加注释:git commit -m "注释" 11、上传至远端仓库:git push git merge//主要用于将两个或两个以上的开发历史加入(合并)一起 git merge -b // 指将 b 分支合并到当前分支 git rebase //在另一个分支基础之上重新应用,用于把一个分支的修改合并到当前分支
-
git合并分支
git checkout 分支1 ;git merge 分支2 ;git push //将分支2合并到分支1中
-
git打标记:
开发过程中会经过多次commit 提交才会确定一个版本,那么除了用commit comments来标识一次提交记录,还使用标签可以对某一次提交记录做上一个小标记,这可以让我们以后可以更方便的索引。 git tag <lightweght_name>:为当前分支所在的提交记录打上轻量标签。 git tag <lightweght_name><commit SHA-1 value>:为某次具体的提交记录打上轻量标签。 git tag-a <anotated_name>-m <tag_message>:为当前分支所在的提交记录打上附注标签。 git tag:列出所有的标签名。 git tag-d<tagname>:删除某个标签,本质上就是移除.git/refs/tags/中对应的文件。 git show<tag name>:显示标签对应提交记录的具体信息。 git push <remote><tag name>:推送某个标签到远程仓库。 git push <remote>--tags:推送所有标签到远程仓库。 git push <remote>-delete <tag name>:删除远程仓库中的某个标签。
-
git pull 和git fetch的区别
区别:1、fetch能够直接更改远端跟踪分支,而pull无法直接对远程跟踪分支操作;
2、fetch将数据拉取到本地仓库不会自动合并或修改当前的工作,pull是从远程获取最新版本并merge到本地,会自动合并或修改当前的工作。
Git fetch和git pull区别为:远端跟踪分支不同、拉取不同、commitID不同。
一、远端跟踪分支不同
1、Git fetch:Git fetch能够直接更改远端跟踪分支。
2、git pull:git pull无法直接对远程跟踪分支操作,我们必须先切回本地分支然后创建一个新的commit提交。
二、拉取不同
1、Git fetch:Git fetch会将数据拉取到本地仓库 - 它并不会自动合并或修改当前的工作。
2、git pull:git pull是从远程获取最新版本并merge到本地,会自动合并或修改当前的工作。
三、commitID不同
1、Git fetch:使用Git fetch更新代码,本地的库中master的commitID不变,还是等于1。
2、git pull:使用git pull更新代码,本地的库中master的commitID发生改变,变成了2。
21.移动端兼容性问题
-
移动端音视频问题:自动播放
-
ios safari
iPhone Safari中不支持,但在webview中可能被开启;iOS开发文档明确说明蜂窝网络下不允许autoplay; -
chrome中,设置
mouted
后可以自动播放 -
微信中不允许自动播放。但是可以借助
WeixinJSBridge
实现视频:
-
preload,ios下是不支持的。通用的方法是对视频进行
play()
后立即停止 -
ios视频自动全屏播放:设置内联属性
playsinline webkit-playsinline
-
-
经典的1px边框
一般是采用伪元素模拟的方式,原理:
把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,并 transform 的 scale 缩小一半,原先的元素相对定位,新做的 border 绝对定位。
-
CSS3动画卡顿
尽量使用transform,避免使用height,width,margin,padding等。可以开启GPU硬件加速,但用硬件加速的时候也要注意,因为这个也有坑,不合理使用反而会让应用越来越卡!
-
设置圆角(border-radius:50%;)部分手机显示为椭圆
具体是因为,使用了rem布局,在部分机型上出现的问题,设置具体的px数值,不用50%即可
-
android里line-height不居中
把字号内外边距等设置为需求大小的2倍,使用zoom进行缩放,可以完美解决。
22.vue路由切换后页面滚动条位置不变(需回到顶部)
由于vue是单页面应用,只是更换了路由内容,还在当前页面滚动条是不会回到顶部的。解决办法是在切换路由的时候,将滚动区域的滚动条复位为0。
-
方法一:配置vue-Router时,设置scrollBehavior属性来管理路由切换时页面的滚动行为。
const createRouter = () => new Router({ scrollBehavior: (to, from, savedPosition) => { // console.log(88, savedPosition) //savedPosition值浏览器箭头切换时返回{x:0,y:0} if (to.path === '/attendances') { return { y: 100 } //自定义单个页面滚动条位置 } return { y: 0 } }, // 管理滚动行为 如果出现滚动 切换就让 让页面回到顶部 routes: [...] })
-
方法二:监听路由改变
直接在app.vue监测路由变化,让body的滚动距离 scrollTop=0或scrollTo(0,0)
// 使用 watch 监听$router的变化, watch: { '$route': function(to, from) { window.scrollTo(0,0) //等于document.documentElement.scrollTop = 0 //或document.body.scrollTop = 0 } }
-
方法三:全局后置守卫,离开当前路由时执行
router.afterEach((to,from,next)=>{ window.scrol1To(0,0); });
23.实现切换页面滚动条位置不变
第一步(监听当前的滚动高度)
scroll:""
mounted(){ window.addEventListener('scroll',this.handleScroll)},
destroyed(){ window.removeEventListener('scrol1',this.handleScroll) },
methods:{
handleScroll(){
this.scroll=document.documentElement.scrollTop|| document.body.scrollTop
//console.1og(this.scrol1)
}
}
第二步(监听当前的路由)
watch:{
'$route'(to, from){
this. $nextTick(()=>{
document. body. scrol1Top=this. scroll;
document. documentElement. scrollTop=this.scroll ;
})
}}
24.图片懒加载原理
定义:滚动动态加载,也就是懒加载,显示在屏幕之外的图片默认不加载,随着页面的滚动,图片进入了显示的范围,则触发图片的加载显示
原理:
-
页面的img标签的url不能直接用图片的真实路径,否则会直接加载,可将真实路径暂存在自定义属性data-url中
<img data-url="真实url" />
-
img的src属性也不能是空或者坏路径(会出现图片无显示图标),可将所有的img标签的src属性填入同一个1px的t透明图片,用于占位,且设置loading效果的背景图,
<img data-url="xxx"src="lpx. gif"width="180"height="180"style="background: url(loading. gif) no-repeat center;"/>
-
需要一个滚动事件,判断元素是否在浏览器窗口,一旦进入视口才进行加载,当滚动加载的时候,就把这张透明的1px.gif图片替换为真正的url地址(也就是data-url里保存的值)
-
最后图片进入视口后,利用js提取data-url的真实图片地址赋值给src属性,就会去发送请求加载图片,真正实现了按需加载
25.for, for in, for of, forEach的区别
for:
优点:程序简洁,结构清晰,循环初始化,循环变量化和循环条件位置突出。
缺点:结构比while循环复杂,容易出编码错误。
for..in:
优点:避免了for in的所有缺点,可以使用break,continue和return,不仅支持数组的遍历,还可以遍历类似数组的对象,支持字符串的遍历最简洁。
缺点:不适用于处理原有的原生对象。
for...of:仅用于遍历可迭代(可按顺序遍历)对象的元素语法:for(var 变量of 数组名){console.log(变量)}
优点:避免了for..in的所有缺点,支持break,continue,return。支持遍历map,object,array,set string等。
缺点:不适用于处理原有的原生对象。
forEach:
优点:便利的时候更加简洁,效率和for循环相同,不用关心集合下标的问题,减少了出错的效率缺点:不能同时遍历多个集合,在遍历的时候无法修改和删除集合数据,方法不能使用break,continue语句跳出循环,或者使用return从函数体返回,对于空数组不会执行回调函数
区别:四个算法语句区别主要体现在响应break,continue,return上和使用的对象上。
for 语句性能最好;能响应break,continue,return控制循环。
for in 无法响应break,continue,return控制循环;for in 主要针对对象,它不仅会循环对象本身的属性,还会查找循环原型上的属性;循环的顺序不确定。s
for of 能响应break,continue,return控制循环,还能遍历map、set等类数组,但是不能循环普通的对象forEach 无法响应break,continue,return控制循环。
26.es5和es6继承实现的区别
es5和es6继承的区别:ES5的继承是通过原型或构造函数机制实现的;它先创建子类,再实例化父类并添加到子类this中。ES6先创建父类,再实例化子集中通过调用super方法访问父级后,再通过修改this实现继承。
es6继承和es5继承的区别
- ES5的继承实质上是先创建子类的实例对象,然后再将父类的方法添加到this上(Parent.apply(this)).
- ES6的继承机制完全不同,实质上是先创建父类的实例对象this(所以必须先调用父类的super)方法),然后再用子类的构造函数修改this。
- ES5的继承是通过原型或构造函数机制来实现。
- ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。
注意super关键字指代父类的实例,即父类的this对象。
注意:在子类构造函数中,调用super后,才可使用this关键字,否则报错。|
27.vue2中怎么用echarts的
-
npm install echarts --save
-
在main.js中引入echarts ,下面是全部引入和按需引入
import * as echarts from "echarts"; //引入所有的图例 Vue.prototype.$echarts = echarts; //在原型上挂载,用于全局使用
在某个组件中按需引入
var echarts = require('echarts/lib/echarts') // 引入echarts主模块 require('echarts/lib/chart/radar') // 引入雷达图 // 引入提示框和标题组件 require('echarts/lib/component/tooltip') require('echarts/lib/component/title')
-
在组件中使用
export default { date(){ options:{ ... } }, method:{ //定义绘制函数 drawEc(){ let myChart = this.$echarts.init(this.$refs.myechart); myChart.setOption(this.options) } }, mounted(){ this.drawEc() //调用函数 } }
28.数据类型
值类型(基本类型)6个:字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol。
Symbol是ES6中新增的一种数据类型,用来表示一个独一无二的值,制作框架或第三方插件时可避免使用者将框架内同名属性覆盖。
引用数据类型(对象类型)5个:对象(Object)、数组(Array)、函数(Function),还有两个特殊的对象:正则(RegExp)和日期(Date)。
29.什么是NaN,NaN==NaN输出啥
NaN意思是指数据not a number不是一个数字,但是NAN却属于数字类型。需要注意的是,NAN 不会和任何一个值相等,包括NAN本身。运行结果都为false。
30. Vue的生命周期,有哪些?
beforeCreate
:实例话Vue,未初始化和响应式数据created
:已初始化和响应式数据,可访问数据beforeMount
:render调用,虚拟DOM生成,未转真实DOMmounted
:真实DOM挂载完成beforeUpdate
:数据更新,新虚拟DOM生成updated
:新旧虚拟DOM进行对比,打补丁,然后进行真实DOM更新beforeDestroy
:实例销毁前,仍可访问数据destroy
:实例销毁,子实例销毁,指令解绑,解绑本实例的事件activated
:keep-alive所缓存组件激活时调用deactivated
:keep-alive所缓存组件停用时调用errorCaptured
:子孙组件的错误捕获,此函数可返回false阻止继续向上传播
链接:https://juejin.cn/post/7073300624707682317
31.显示转换和隐式转换
显示类型:转换是通过 JS 提供的一些函数或运算符,可以直接将类型进行转换
如Number() String() Boolean()parseInt()
隐式转换:是指在代码执行过程中,通过运算符运算或语句执行等操作,js 引擎会自动隐式的改变类型
32.为什么v-if和v-for不建议用在同一标签?
v-for
优先级高于v-if
,每项都通过v-for
渲染出来后再去通过v-if
判断显隐,过程中会增加无用的dom操作,渲染了无用的节点
解决方案:
- 如果v-if后的数据与v-for遍历的数据没有依赖关系,可以直接找v-for外加template并用v-if判断
- 如果v-if后的数据依赖于v-for遍历的数据,可去掉v-if,并将v-for要遍历的数据放在计算属性中处理后遍历
34.国际化步骤
// npm install vue-i18n
// import VueI18n from 'vue-i18n'
// Vue.use(VueI18n) // 全局注册国际化包
// import elementEN from 'element-ui/lib/locale/lang/en' // 引入饿了么的英文包
// import elementZH from 'element-ui/lib/locale/lang/zh-CN' // 引入饿了么的中文包
// import customEN from './en'
// import customZH from './zh'
// export default new VueI18n({
// locale: Cookie.get('language') || 'zh', // 从cookie中获取语言类型 获取不到就是中文
// messages: {
// en: {
// ...elementEN, // 将饿了么的英文语言包引入
// ...customEN
// },
// zh: {
// ...elementZH, // 将饿了么的中文语言包引入
// ...customZH
// }
// }
// })
// 使用$t('route.xxx')使用对应的语言文字
// 使用$tc('route.xxx',1)使用对应的第几个语言文字(语言包中定义:xxx='he|hell|hello')
35.axios和ajax区别
1.区别
axios是通过promise实现对ajax技术的一种封装,就像jQuery实现ajax封装一样。
简单来说: ajax技术实现了网页的局部数据刷新,axios实现了对ajax的封装。
axios是ajax ajax不止axios。
2.优缺点:
ajax:
本身是针对MVC的编程,不符合现在前端MVVM的浪潮
基于原生的XHR开发,XHR本身的架构不清晰,已经有了fetch的替代方案
JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理(采取个性化打包的方案又不能享受CDN服务
axios:
从 node.js 创建 http 请求
支持 Promise API
客户端支持防止CSRF
提供了一些并发请求的接口(重要,方便了很多的操作)
36.MVVM是什么?和MVC有何区别呢?
Mvc
- Model(模型):负责从数据库中取数据.
- View(视图):负责展示数据的地方
- Controller(控制器):用户交互的地方,例如点击事件等等·思想:Controller将Model的数据展示在View 上
MVVM
VM: 也就是View-Model,数据的双向绑定将【模型】转化成【视图】,即后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM事件监听。
区别
整体看来,MVVM 比MVC精简很多,不仅简化了业务与界面的依赖,还解决了数据频繁更新的问题,不用再用选择器操作DOM元素。因为在MVVM 中,View不知道 Model的存在,Model和ViewModel也观察不到View,这种低耦合模式提高代码的可重用性 Vue是不是MVVM框架? Vue是MVVM框架,但是不是严格符合MVVM,因为MVVM规定Model和View不能直接通信,而Vue的ref可以做到这点
37.说说nextTick的用处?
修改数据时不能马上得到最新的DOM信息,所以需要使用nextTick,在nectTick回调中可以获取最新DOM信息
39.Vue中封装的数组方法有哪些,如何实现页面更新
- 在Vue中,对响应式处理利用的是Object.defineProperty对数据进行拦截,而这个方法并不能监听到数组内部变化,数组长度变化,数组的截取变化等,所以需要对这些操作进行hack,让Vue能监听到其中的变化
- push()
- pop()
- shift()
- unshift()
- solice()
- sort()
- reverse()
40.请问 http协议为什么一定要区分 post,get,put,delete
所有 http 请求,一律用 POST,在业务功能的实现是没有问题的. post,get,put,delete 是标准, 大家都遵循这样的规则. 这样的api对于它人来说一目了然, get就是获取数据, post就是提交数据, put就是更新数据, delete就做删除操作. 如果一律使用post对一个项目组的内部人员来说是没有问题的, 但是对于对外公开的接口就让调用者摸不着头脑了. 另外这四种方法还有特殊的用意. GET 请求可被缓存, 请求可保留在浏览器历史记录中, 请求可被收藏为书签, get方法具有Safe特性会影响是否可以快取(post不支持快取) POST 请求不会被缓存, 请求不会保留在浏览器历史记录中, 不能被收藏为书签 这就是为什么取数据要使用get而不是post. 因为get可以快取, 缓存和保留历史记录及书签等特殊功能. 除了上面的4种常见方法还有一个很重要的方法PATCH.
41.promise A+规范
规定了promise是如何设计的
42.Promise
-
用处:Promise 对象用于表示一个异步操作的最终完成 (或失败)及其结果值。使得控制异步操作更加容易。可以将异步操作以同步操作的流程表达出来,从而避免了传统的层层嵌套的回调函数的异步操作。
-
本质:Promise 是一个构造函数
-
Promise 相关的方法
-
Promise 构造函数原型上的方法(通过创建一个 Promise 实例进行调用)
-
Promise.prototype.then() :then 方法必须返回一个新的 promise 对象(实现链式调用的关键)
-
Promise.prototype.catch()
catch 异常处理函数,处理前面回调中可能抛出的异常。只接收一个参数onRejected处理程序。它相当于调用Promise.prototype.then(null,onRejected),所以它也会返回一个新的Promise
-
Promise.prototype.finally()
-
-
Promise 构造函数静态方法,(直接通过 Promise 函数调用)
-
Promise.all()
Promise.all()参数可以传递一个promise数组,Promise.all()创建的Promise会在这一组Promise全部解决后在解决。也就是说会等待所有的promise程序都返回结果之后执行后续的程序。返回一个新的Promise,.then()取出来也是一个数组,顺序和所处理的promise数组执行顺序一致。
- 如果所有都成功,则合成Promise的返回值就是所有子Promise的返回值数组。
- 如果有一个失败,那么第一个失败的会把自己的理由作为合成Promise的失败理由(立即执行reject)。
-
Promise.allSettled()
Promise.all 和 allSettled 基本一样,区别是,then 始终可以获取到所有异步的状态和结果(value/reason)的数组,哪怕其中有一个失败
-
Promise.race()
Promise.race 同样传入一样数组,但是只返回第一个结果(数组最快执行完成的那个函数的结果),不管成功或失败,最后返回一个新的Promise
-
Promise.any()和race 的区别是,只会返回第一个成功的结果
-
Promise.reject():用法,如在一个函数中需要将一些数据以promise的形式返回出去,可以用
return Promise.reject(data)
,为什么不用new promise(…) 因为new promise没意义(内部没有异步要执行),其效果相当于定义函数前单独加一个async 的效果 -
Promise.resolve()
-
-
-
Promise对象特点
对象的状态不受外界影响。
- 有三种状态:
pending
(进行中)、fulfilled
(已成功)和rejected
(已失败) - 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
一旦状态改变,就不会再变,任何时候都可以得到这个结果
Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。- 只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
- 有三种状态:
43.keep-alive组件
1.作用:vue自带的一个用于缓存组件的组件标签,避免重复创建相同的组件,提升项目运行性能
2.用法:
-
搭配动态组件使用,在不同动态组件状态的切换下,各组件内的状态依旧维持在内存之中,在下次渲染时不需要重新创建。
-
配合router-view使用:有时候需要将整个路由页面缓存下来,可用keep-alive包裹router-view标签实现缓存
-
两个与其相关的钩子函数: 被缓存的组件不再创建和销毁, 而是激活和非激活
- activated:
keep-alive
所缓存组件激活时调用 - deactivated:
keep-alive
所缓存组件停用时调用 - 初始进入和离开 created ---> mounted ---> activated --> deactivated
后续进入和离开 activated --> deactivated
- activated:
-
keep-alive属性
-
include = 字符串或正则表达式或数组。只有名称匹配的组件会被缓存。
-
exclude =字符串或正则表达式或数组。任何名称匹配的组件都不会被缓存。
-
// 指定home组件和about组件被缓存 <keep-alive include="home,about" > <router-view></router-view> </keep-alive>
-
-
keep-alive的应用场景举例
- 查看表格某条数据详情页,返回还是之前的状态,比如还是之前的筛选结果,还是之前的页数等
- 填写的表单的内容路由跳转返回还在,比如input框、下选择拉框、开关切换等用户输入了一大把东西,跳转再回来不能清空啊,不用让用户再写一遍
45. 路由传参方式(动态路由)
46.路由跳转
47.箭头函数的特点
48.vue2混入mixin
一、mixin是什么
Mixin
是面向对象程序设计语言中的类,提供了方法的实现。其他类可以访问mixin
类的方法而不必成为其子类
Mixin
类通常作为功能模块使用,在需要该功能时“混入”,有利于代码复用又避免了多继承的复杂
- Vue中的mixin
先来看一下官方定义
mixin
(混入),提供了一种非常灵活的方式,来分发Vue
组件中的可复用功能。
本质其实就是一个js
对象,它可以包含我们组件中任意功能选项,如data
、components
、methods
、created
、computed
等等
我们只要将共用的功能以对象的方式传入 mixins
选项中,当组件使用 mixins
对象时所有mixins
对象的选项都将被混入该组件本身的选项中来
在Vue
中我们可以局部混入跟全局混入
- 局部混入
定义一个mixin
对象,有组件options
的data
、methods
属性
1var myMixin = {
2 created: function () {
3 this.hello()
4 },
5 methods: {
6 hello: function () {
7 console.log('hello from mixin!')
8 }
9 }
10}
组件通过mixins
属性调用mixin
对象
1Vue.component('componentA',{
2 mixins: [myMixin]
3})
该组件在使用的时候,混合了mixin
里面的方法,在自动执行create
生命钩子,执行hello
方法
- 全局混入
通过Vue.mixin()
进行全局的混入
1Vue.mixin({
2 created: function () {
3 console.log("全局混入")
4 }
5})
使用全局混入需要特别注意,因为它会影响到每一个组件实例(包括第三方组件)
PS:全局混入常用于插件的编写
- 注意事项:
当组件存在与mixin
对象相同的选项的时候,进行递归合并的时候组件的选项会覆盖mixin
的选项
但是如果相同选项为生命周期钩子的时候,会合并成一个数组,先执行mixin
的钩子,再执行组件的钩子
二、使用场景
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立
这时,可以通过Vue
的mixin
功能将相同或者相似的代码提出来
举个例子
定义一个modal
弹窗组件,内部通过isShowing
来控制显示
1const Modal = {
2 template: '#modal',
3 data() {
4 return {
5 isShowing: false
6 }
7 },
8 methods: {
9 toggleShow() {
10 this.isShowing = !this.isShowing;
11 }
12 }
13}
定义一个tooltip
提示框,内部通过isShowing
来控制显示
1const Tooltip = {
2 template: '#tooltip',
3 data() {
4 return {
5 isShowing: false
6 }
7 },
8 methods: {
9 toggleShow() {
10 this.isShowing = !this.isShowing;
11 }
12 }
13}
通过观察上面两个组件,发现两者的逻辑是相同,代码控制显示也是相同的,这时候mixin
就派上用场了
首先抽出共同代码,编写一个mixin
1const toggle = {
2 data() {
3 return {
4 isShowing: false
5 }
6 },
7 methods: {
8 toggleShow() {
9 this.isShowing = !this.isShowing;
10 }
11 }
12}
两个组件在使用上,只需要引入mixin
1const Modal = {
2 template: '#modal',
3 mixins: [toggle]
4};
5
6const Tooltip = {
7 template: '#tooltip',
8 mixins: [toggle]
9}
通过上面小小的例子,让我们知道了Mixin
对于封装一些可复用的功能如此有趣、方便、实用
50.Vue.use 方法简单介绍
- 参数
- 对象或者是函数
- 用法
- 安装 Vue.js 插件。
- 如果插件是一个对象,必须提供
install
方法。 - 如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。
- 该方法需要在调用
new Vue()
之前被调用。 - 当 install 方法被同一个插件多次调用,插件将只会被安装一次。
- 应有场景
- 注册全局组件,全局过滤器,全局自定义指令,
51.首屏优化
常见的几种SPA首屏优化方式
- 减小入口文件积
- 静态资源本地缓存
- UI框架按需加载
- 图片资源的压缩
- 组件重复打包
- 开启GZip压缩
- 使用SSR
什么是首屏:Web中只网站加载后,用户不用滚动屏幕所看到的所有信息。
首屏优化分为两大方向:资源加载优化 和 页面渲染优化
- 前端通用的优化策略
压缩资源,使用 CDN ,http 缓存等,本节先讨论首屏优化 - 路由懒加载
把不同路由对应的组件分割为不同的代码块,当路由被访问的时候,再加载对应的组件。
如果是SPA(单页面应用),优先保证首页加载。
SPA 是一种特殊的 Web 应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的。它将所有的活动局限于一个 Web 页面中,仅在该 Web 页面初始化时加载相应的 HTML 、 JavaScript 、 CSS 。一旦页面加载完成, SPA 不会因为用户的操作而进行页面的重新加载或跳转,而是利用 JavaScript 动态的变换 HTML(采用的是 div 切换显示和隐藏),从而实现UI与用户的交互。在 SPA 应用中,应用加载之后就不会再有整页刷新。相反,展示逻辑预先加载,并有赖于内容Region(区域)中的视图切换来展示内容。
- 服务端渲染SSR
服务端渲染(SSR)后,简单理解是将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。渲染时请求页面,返回的body里已经存在服务器生成的html结构,之后只需要结合css显示出来。这就节省了访问时间和优化了资源的请求。 - APP预取
如果 H5 在 App webview 中展示,可以使用 App 预取资源,在列表页,App 预取数据(一般是标题、首页文本,不包括图片、视频)
进入详情页,H5 直接即可渲染 App 预取的数据
可能会造成“浪费”:预期了,但用户未进入该详情页 - 第三方库使用按需引入方式加载。
- 图片 lazyLoad:先加载内容,再加载图片。
注意,提前设置图片容器的尺寸,尽量重绘,不要重排。
- 离线包 hybrid
提前将 html css js 等下载到 App 内。
当在 App 内打开页面时,webview 使用 file:// 协议加载本地的 html css js ,然后再 ajax 请求数据,再渲染。
可以结合 App 预取。
52.$nextTick
官方对其的定义
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
什么意思呢?
我们可以理解成,Vue
在更新 DOM
时是异步执行的。当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
举例一下
Html
结构
1<div id="app"> {{ message }} </div>
构建一个vue
实例
1const vm = new Vue({
2 el: '#app',
3 data: {
4 message: '原始值'
5 }
6})
修改message
1this.message = '修改后的值1'
2this.message = '修改后的值2'
3this.message = '修改后的值3'
这时候想获取页面最新的DOM
节点,却发现获取到的是旧值
1console.log(vm.$el.textContent) // 原始值
这是因为message
数据在发现变化的时候,vue
并不会立刻去更新Dom
,而是将修改数据的操作放在了一个异步操作队列中
如果我们一直修改相同数据,异步操作队列还会进行去重
等待同一事件循环中的所有数据变化完成之后,会将队列中的事件拿来进行处理,进行DOM
的更新
- 为什么要有nexttick
举个例子
1{{num}}
2for(let i=0; i<100000; i++){
3 num = i
4}
如果没有 nextTick
更新机制,那么 num
每次更新值都会触发视图更新(上面这段代码也就是会更新10万次视图),有了nextTick
机制,只需要更新一次,所以nextTick
本质是一种优化策略
- 二、使用场景
如果想要在修改数据后立刻得到更新后的DOM
结构,可以使用Vue.nextTick()
第一个参数为:回调函数(可以获取最近的DOM
结构)
第二个参数为:执行函数上下文
1// 修改数据
2vm.message = '修改后的值'
3// DOM 还没有更新
4console.log(vm.$el.textContent) // 原始的值
5Vue.nextTick(function () {
6 // DOM 更新了
7 console.log(vm.$el.textContent) // 修改后的值
8})
组件内使用 vm.$nextTick()
实例方法只需要通过this.$nextTick()
,并且回调函数中的 this
将自动绑定到当前的 Vue
实例上
1this.message = '修改后的值'
2console.log(this.$el.textContent) // => '原始的值'
3this.$nextTick(function () {
4 console.log(this.$el.textContent) // => '修改后的值'
5})
$nextTick()
会返回一个 Promise
对象,可以是用async/await
完成相同作用的事情
复制
1this.message = '修改后的值'
2console.log(this.$el.textContent) // => '原始的值'
3await this.$nextTick()
4console.log(this.$el.textContent) // => '修改后的值'
54.前端性能优化
1.使用分子域名加载资源
2.使用较近的CDN或dns预解析
3.使用高性能传输方式或方法,http2,quic,gzip...
4.减少http请求的数量,合并公共资源、使用雪碧图、合并代码块、按需加载资源
5.减少传输总量或加快传输速度
6优化图片的加载展示策略,根据网络状况加载图片、图片格式优化、图片展示位置优化
7.减少cookie体积,试试缓存数据的方法localStorage/sessionStorage/indexedDB
8.使用更有效的缓存策略,keep-alive,expiration,max-age...
9.合理安排路由策略
10.减少反复操作dom\重绘和重排
11.使用防抖和节流对UI进行优化
12.无阻塞加载js,减少并发下载或请求
13.异步加载资源。
14.公用css类
15.使用GPU宣染初始动画和图层的合成
16.减少循环和循环嵌套以减少js执行时间
17.组件提取、样式提取、函数提取
18.使用web worker加载资源
19.减少301 302
20.减少iframe
21.注意页面大小,特别是canvas的大小和占用内存
22.减少插件中的多语言版本内容
23.减少同时的动画
55.webpack打包优化
- 移除console
npm install babel-plugin-transform-remove-console --save-dev
- 关闭soucemap,可以映射,将问题精确的定位到开发代码的哪一行,productionSourceMap:有了map之后代码就像没压缩一样,生产阶段不需要
- 配置splitChunks:将公共代码进行提取,设置那些重复使用的代码,用合并方式进行打包
- 关闭prefetch: vue-cli3默认开启prefetch,在加载首页的时候,就会提前获取用户可能访问的内容,提前加载其他路由模块,所以我们要关闭该功能
- 这里要注意:不会影响首屏的加载速度,实际是为了优化子页面,可以快速打开子页面,但是像移动端,用户可能只会访问首页,也预加载其他模块的资源,浪费用户流量。
- 打包成gzip,可以让资源请求的时候速度更快
- 开启runtimeChunk:运行时代码,也就是异步加载的代码,比如路由懒加载的都属于运行时代码。
- 没有开启runtimeChunk,运行时代码或者代码没有发生变化,重新打包时,主模块也会重新打包hash会发生变化。会导致项目部署后,强缓存失效。
- 开启runtimeChunk,会将运行时代码的信息单独存放在runTime中,主模块就不会被影响,也就不会重新打包,可以继续使用本地缓存。(但要配合7使用)
- script-ext-html-webpack-plugin:用这个插件可以将runTime代码生成到行内
- 通过image-webpack-loader进行图片的打包压缩
- 开启路由懒加载 将每个路由进行单独打包
- 排除打包,用externals将比较大的包排除掉,然后引入响应的CDN资源
56.插槽
57.keep-alive组件
58.BFC
59.盒模型
1.W3C标准盒模型(content-box,border、padding 的设置会破坏元素宽高,必须得重新计算,非常麻烦(除了在IE浏览器,默认就是标准盒模型,所以可以用 box-sizing 来转换)
- 标准盒模型width设置了内容的宽,所以盒子实际宽度加上padding和border;
2.IE(怪异)盒模型(border-box),border、padding 的设置不会影响元素的宽高,这非常实用(且因为IE盒模型不是标准,所以才有 box-sizing:border-box 这个标准属性来设置,将它标准化)【IE6/5 是怪异模型,IE7开始是标准盒模型】
- 设置width后,实际盒子的宽度就固定为该宽度,包含了内容+padding+border
60.css3新属性(项目常用)
- c3盒模型box-sizing
- flex布局
- transition过渡
- transform2D转换
- background-size背景缩放
- border-radius圆角
- 等等,不用都说说一些常用的就ok
61、本地存储
localStorage, sessionStorage, cookie
cookie:
- 生命周期:默认自己添加,可设置失效时间,关闭浏览器后失效。
- 存储数据大小:4kB左右
- 服务器端通信:参与通信,每次自动携带在请求头中,如果使用cookie保存过多数据会带来性能问题
- 易用性:很不友好,获取某个cookie,会获取到整个cookie字符串‘key:value;key:value’的形式,一般使用第三方库js-cookie进行处理
localStorage和sessionStorage除了生命周期,其他都相同
- 生命周期:localStorage除非手动清除,否则永久存储。
sessionStorage仅在当前会话下有效,关闭页面和浏览器后会被清除。
- 存储数据大小:一般为5-20MB
- 服务器端通信:仅在浏览器中保存,不参与和服务器的通信
- 易用性:还可以,但存储的时候只能存字符串(用到JSON.stringify和JSON.parse进行处理)
62、require 和 import 的区别
node编程中最重要的思想就是模块化,import 和 require 都是被模块化所使用。在 ES6 当中,用 export 导出接口,用 import 引入模块。但是在 node 模块中,使用module.exports导出接口,使用 require 引入模块。
两者的区别如下:
遵循规范:
- require 是 AMD 规范引入方式
- import是 ES6 的一个语法标准,如果要兼容浏览器的话必须转化成 ES5 的语法
调用时间:
- require是运行时调用,所以require理论上可以运用在代码的任何地方
- import是编译时调用,所以必须放在文件开头
本质:
- require 是赋值过程。module.exports后面的内容是什么,require的结果就是什么,比如对象、数字、字符串、函数等,然后再把require的结果赋值给某个变量,它相当于module.exports的传送门
- import 是解构过程,但是目前所有的引擎都还没有实现import,我们在node中使用babel支持ES6,也仅仅是将ES6转码为ES5再执行,import语法会被转码为require
import 虽然是 es6 中的语法,但就目前来说,所有的引擎都还没有实现import。
我们在 node 中使用 babel 支持ES6(在 node 当中,比如 node.js 代码,也不能直接使用 import 来导入,必须使用 babel 支持才能使用 import 语法),实际上也是将 ES6 转码为 ES5 再执行,import 语法实际上会被转码为 require。这也是为什么在模块导出时使 module.exports,在引入模块时使用 import 仍然起效,因为本质上,import 会被转码为 require 去执行。
- 2、require() | exports 的用法
通过require引入基础数据类型时,属于复制该变量。通过require引入复杂数据类型时,属于浅拷贝该对象。
- 2.1、导入模块 require() 的用法
require() 函数用于在当前模块中加载别的模块。在函数内写入模块的路径即可(相对路径和绝对路径都行)
var foo1 = require('./foo'); // .js 扩展名可忽略var foo2 = require('./foo.js');var data = require('./data.json'); //不仅仅是 JS 文件,也可以是其他文件
require 相当于module.exports的传送门,module.exports 后面的内容是什么,require 的结果就是什么,比如对象、数字、字符串、函数等,然后再把 require 的结果赋值给某个变量。
require理论上可以运用在代码的任何地方,甚至不需要赋值给某个变量之后再使用,比如:
require('./a')(); //假设a模块是一个函数,此时将立即执行a模块函数var data = require('./a').data; //假设a模块导出的是一个对象var a = require('./a')[0]; //假设a模块导出的是一个数组
- 2.2、输出接口 exports 的用法
exports 对象是当前模块的导出对象,用于导出该模块的方法或属性。别的模块通过 require() 函数引用别的模块时实际上得到的就是别的模块的 exports 对象:
exports.hello = function () { console.log('Hello World!');};
上面代码实际上就是导出了一个对象,通过该对象的 hello 属性可以拿到该方法:
var obj = require('./index2.js')obj.hello(); //输出 Hello Word!
导出模块的方法或者属性可以直接用 exports,也可以使用 module.exports 来进行导出。通过module
对象可以访问到当前模块的一些相关信息,但最多的用途是替换当前模块的导出对象。
module.exports = function () { console.log('Hello World!');};
实际上,exports是module.exports的一个引用,module.exports 指向的就是 exports。
console.log(exports === module.exports); //输出true
- 3、import | export 的用法
import 是编译时运行的(require是运行时的),它必须放在文件开头,而且使用格式也是确定的,不容置疑。它不会将整个模块运行后赋值给某个变量,而是只选择import的接口进行编译,这样在性能上比require好很多。
import | export 遵循 ES6 规范,支持编译时静态分析,便于JS引入宏和类型检验。动态绑定。写法比较多:
import fs from 'fs'import {default as fs} from 'fs'import * as fs from 'fs'import {readFile} from 'fs'import {readFile as read} from 'fs'import fs, {readFile} from 'fs'export default fsexport const fsexport function readFileexport {readFile, read}export * from 'fs'
63、数据类型,检测数据类型 3
- 简单数据类型和复杂数据类型。
简单数据类型有number数字,boolen布尔值,字符串,null,undefined,还有es6新增的symbol唯一值。
复杂数据类型有object对象,array数组,function函数以及特殊对象正则RegExp和日期dDate。
- 检测数据类型
第一种:typeof可以正常检测出:number、boolean、string、object、function、undefined、symbol、bigint
- 检测基本数据类型,null会检测object,因为null是一个空的引用对象
- 检测复杂数据类型,除function外,均为object
- 对象,数组以及null无法区分都会被检测为object。
- infinity和NaN会被识别为number
第二种:instance of,检测引用数据类型,用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。写法就是:某个实例对象A instanceof 某个构造函数B。检测null和undefined会返回false
第三种 :toString.call(xxx)
- toString是Object原型对象上的一个方法,必须通过Object.prototype.toString.call(xxx)来获取。
- 因为从原型链的角度讲,所有的对象最终都会指向object。但大部分的对象,本身也可能有toString的方法,这样就会导致没有查到object的toString方法就会被自身的发放给终止掉。
- 需要用call来强制执行,改变方法的this指向所要检测的数据。
- 可以检测出所有数据类型
第四种:是根据对象的constructor(con s jua k t)来判断该对象实例由哪个构造函数创建的,检测的是由字面量方式创建出来的数据类型。(不能用于检测由自定义构造函数new出来的数据)
obj.constructor=== Object //true
第五种:isArray()检测数组
64.Set 和 Map
- Set 和 Map主要的应用场景在于 数据重组 和 数据储存。;
Set 是一种叫做 集合 的数据结构,Map 是一种叫做 字典 的数据结构。
-
Set:
-
ES6 新增的一种新的数据结构,类似于数组,成员唯一(内部元素没有重复的值)。且使用键对数据排序即顺序存储。
-
Set 本身是一种构造函数,只能用构造函数创建,接受一个数组作为参数,用来初始化。
const set = new Set([1, 2, 3, 4, 4]);
-
方法:
set.size 获取长度,相当于length add(value):添加某个值,返回 Set 结构本身 delete(value):删除某个值,返回一个布尔值,表示删除是否成功 has(value):返回一个布尔值,表示该值是否为 Set 的成员 clear():清除所有成员,没有返回值 也拥有forEach方法,可遍历
-
应用场景:数组去重(new的过程自动去重,再用扩展符去壳赋值给新数组)
-
-
Map
-
Map是一组键值对的结构,用于解决以往不能用对象做为键的问题,具有极快的查找速度。(注:函数、对象、基本类型都可以作为键或值。)
-
它类似于对象,但是成员的值都是唯一的,没有重复的值。能够记住键的原始插入顺序。
-
初始化Map需要一个二维数组,或者直接初始化一个空Map
let defaultMap = new Map([[ 'name' , '张三' ], [ 'age' , 20]]); //name是键,张三是值 , defaultMap.get(name)可得到'张三'
-
map的属性可以用布尔值,undefined,null ,键可以用任何数据类型()
-
方法
set(key, val): 向Map中添加新元素 get(key): 通过键值查找特定的数值并返回 has(key): 判断Map对象中是否有Key所对应的值,有返回true,否则返回false delete(key):通过键值从Map中移除对应的数据 clear(): 将这个Map中的所有元素删除
-
-
Set和Map的区别
65.回调函数和异步操作的关系是什么?回调函数是异步么?
定义:回调函数被认为是一种高级函数,一种被作为参数传递给另一个函数的高级函数。回调函数的本质是一种模式(一种解决常见问题的模式),因此回调函数也被称为回调模式。
简而言之:一个函数在另一个函数中被调用。而且可以当参数传给其他函数。
所以: 回调函数和异步操作的关系是没有关系!!!
那为什么很多的异步操作都有回填函数啊??
问:你所知道的异步操作,是回调的作用么??? 并不是。
回调:更多的可以理解为一种业务逻辑把 异步编程:JS代码的执行顺序
简单理解:callback 顾名思义 打电话回来的意思
eg1:你点外卖,刚好你要吃的食物没有了,于是你在店老板那里留下了你的电话,过了几天店里有了,店员就打了你的电话,然后你接到电话后就跑到店里买了。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
eg2:再比如,你发送一个axios 请求,请求成功之后,触发成功的回调函数,请求失败触发失败的回调函数。这里面的回调函数更像是一个工具,后台通过这个工具告诉你,你成功了抑或是失败了。这里面的所有异步操作都和回调没关系,真正的异步是then方法。
66. 为什么nextTick优先是微任务
-
什么是
nextTick
:定义: 在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM -
优先是
Promise.then
方法,是个微任务,这样可以避免多一次队列(同步代码执行后就会清空一次微任务),进而少一次UI渲染,节省性能从源码上可以看出
nextTick
接受一个函数为参数,同时会创建一个Promise
微任务。所以,页面调用nextTick
的时候,会把的参数fn
赋值给p.then(fn)
1.token刷新
2.登录权限判断
项目总结
健康管理系统
简介
健康管理系统是一款应用于健康管理机构业务管理系统,主要功能包括 工作流程内容可视化,会员管理,预约管理,套餐管理,检查组管理, 检查项管理,各种运营数据分析,从而帮助机构提高工作效率,加强与会员互动,加强管理者对健康机构运营情况了解。
技术栈
vue 全家桶+element-ui组件库+axios+echarts
主要负责业务
登录模块
实现登录功能,比较核心的技术是 JWT(也就是 token)和 刷新令牌(refresh_token)。通过 token 来记录用户的登录状态,来控制后续的访问权限控制。 refresh_token 实现无感刷新。
-
登录通过 用户名+密码+图片验证 进行登录
-
登录成功之后 获取 token 和 refresh_token, 存储到 vuex 中, 也做了数据持久化
-
token 在请求拦截器 注入
-
针对于 token 失效这个问题 是在 响应拦截器里做的,当错误码 返回的是 401,意味着token失效,会用refresh_token 去获取新token,新值更新。
-
针对于refresh_token 做了两层优化
- 防止多次刷新 token 加了变量 通过改变变量的值 来控制是否调用刷新接口
- 同时发起多个请求的处理 缓存失败的借口 等待拿到token之后 重新发一下
权限模块
权限这一块解决方案采用的是 RBAC(基于角色的权限分配), 主要是处理了访问权限(登录和菜单),按钮级别的控制权限。
登录就是根据token来确定用户的登录状态,有就是登录,无就是没有登录 外加一个白名单控制。
菜单权限 是基于角色分配的,根据后端返回的menus字段 来匹配初当前用户有的权限,通过addRoutes来加追加动态路由。
按钮级别的控制权 根据后端返回的字段,前端这便采用自定义指令实现的,当前按钮权限 在 后端放回的列表就显示 否则隐藏。
这个权限是这么维护的??
另外有一个权限管理系统,可以在给系统配置 角色 配置权限 并且分配的到人。配置权限点的时候 会有一个code 标识码的,作为权限的控制点。
预约管理
这个模块主要是实现 机构客户的预约管理,主要包含 列表展示 (项目信息 用户信息),新建编辑删除预约,批量导入导出 等功能。
-
公司的导入因为数据量并不大,所以采用的主要方式的前端主导的导入方案,主要是借助插件,通过插件把 excel 表格转化成 js 的数据类型,然后按照后端接口的要求调用接口完成导入,这里主要的问题还是数据处理,插件处理之后的数据和后端接口要求的数据并不一致
-
导出的话主要是调用的后端接口,告诉后端需要导出的数据,然后利用插件把后端接口返回的数据转化成插件要求的格式,然后触发浏览器的下载功能进行下载
-
导入组件的封装 input type="file" + xlsx 插件读取数据
工作流程内容可视化
这个模块的主要就是将不同项目,科室的排队信息 展示出来 引导用户。一般的话 是会有一个整个机构里项目检测的统计信息,不同科室那边也会有 这个我们更多接的是 定制化的需求。
- Charts + 列表信息统计 //
组织架构管理
整个公司 人员部门组件,就是 直接用elementui的el-tree来做的。主要就是涉及到一个要把数据转换成 树形数据结构的问题
- 前端在做树形组件的时候,组件需要的 prop 数据为树形数据 ,但是后端接口返回的是平铺的数组结构,所以需要我们前端自己做一步数据转化以适配组件的需要,转树形数据主要是 自己写了一个属性数组处理方法,大概思路是寻找父节点,找到自己的父节点然后把自己放到父节点的 children 属性中
公司里的项目
一. 就是这个产品是给我自己的公司开发的,比如说支付宝 他是不是买支付宝的,他是有自己的用户群体的
二.就是公司开发这个产品是买的,比如说我们之前做的人资系统。
- 你的受众是谁??
- 你这个产品分几种,管理端 用户端
- 有没有基础版本 ???
面试相关
自我介绍
我叫某某某,本科,计算机 专业,老家 哪里哪里,从事前端开发2年,主要的技术栈是vue全家桶+elementui +微信小程序,我上家公司是某某某,我们公司主要是给 一些小型的健康管理机构做业务管理系统以及他们的用户端。就比如说很常见的 体检套餐 他可以在我们的业务管理系统里配置好,然后他们的用户端 就可以看到他们配置好的套餐。
介绍一下项目 以及 你负责的模块
我最近在做的一个项目是 针对于美年大健康管理机构的业务管理系统,我这个是属于管理端,主要包括
预约管理(预约设置,套餐管理), 客户管理(普通用户,vip用户,用户健康档案,体检报告上传),运营数据的统计(套餐预约占比,会员活动占比,运营数据的导出等)。
我最近在做的一个项目是一个物流公司的后台管理项目,其中我负责的前端模块有四个,分别是货主管理,承运商管理,货品管理以及货品类别管理,四个模块对应的四个页面,他们的业务逻辑类似,就对货主,承运商管理,货品以及货品类别的增删改查的请求以及相关页面操作,考虑到到页面结构都差不多,我首先能想到的是对各页面相同结构的内容进行组件封装,来提高效率以及减少代码的冗余。当然各页面都会不同的内容以及结构,对于文本数据的不同一般通过props等组件通信方式来实现具体渲染时的差异,如果遇到小区域的dom结构不同可以在组件内设置插槽来实现结构的自定义,对于增加功能的实现一般注意点是对表单验证的处理,对于删除功能一般注意点是对删除的误操作的弹窗判断,对于修改一般注意的是组件需要有回显功能,查询功能注意的是请求的内容是否符合规范
面试官好, 我叫,来自江西,目前从事前端开发已经两年,之前是在**有限公司工作从事开发岗位。这种公司的主营业务是生产木门的,在公司参与的项目初期主要参与的公司主页的更新和维护,和一些新产品节点或页面的制作,后面主要参与了公司一个内部使用的WMS仓储智能管理系统,这个系统主要服务于各生产车间的原材料到成品的物料管理和控制,所用的技术栈是vue2+elementUI,这个在这个系统中我先后负责了像是仓库管理模块比如里面可以添加各事业部的存放原材料和成品的库区库位啥的,还有负责了货品类型的管理模块还有生产进度管理模块的制作,还有像是各种生产设备的台账管理页面。还有质检模块也是我做的,也就是产品部件在智能流水线上生产过程中产生的ok品和ng品数据记录和汇总。
最近这个项目做了多久
列举一个
2~3个月,页面差不多四五十个,前后端连调,提测, bug修复,一般都是有并行需求再接的。
如何与后端进行接口连调
前后端开发完成之后,会进行连调,一般都是后端把代码部署到开发环境,前端这边连接开发环境,进行接口检测,看看接口通不通,业务逻辑可不可以走下去。
接口不通的情况下,一般会先确定一下 是前端的问题还是后端问题,看状态码,确认不是自身的问题之后 会找后端排查
业务有问题 一般是后端沟通,确定不来 也会再找产品
线上出现问题怎么解决
生产环境的 Bug 分两种情况:
- 紧急 Bug:严重影响用户使用的为紧急 Bug,需立即进行修复,如关键业务流程存在问题,影响用户正常的业务行为。
- 非紧急 Bug 或优化:非关键业务流程问题,仅影响用户使用体验,或出现频率较小等,为非紧急 Bug,可规划到后续版本进行修复。
紧急 Bug 修复
- 需要从
master
分支切出一个紧急 bug 修复分支(hotfix), 修改完成之后 提测 测试通过之后 发布 合并master。 - 因为 已经影响用户的正常使用了 需要版本回退,保证用户的正常使用
非紧急 Bug 修复参考“正常开发流程”,可以把它当作优化需求,用来正常开发。
项目排期是怎么排的
一般情况下 是会根据产品的需求和以及他希望发布日期进行排期。也会根据当前的开发任务进行合理安排,在现有任务安排的很满的话 也会合理砍掉一个需求 或者 一些不重要的需求 进行延后
项目成员组成
- 我们开发跟着项目走,我们项目组的话,是两个前端,一个负责客户端,一个负责管理端。 4个后端,UI的话 我们是从其他团队接的,产品1个 专门对接我们这个需求的
- 我们是有自己的前端团队的,前端总共是11个人,后端 二三十个,UI几个 产品几个 不同的人负责不同的产品线。
在项目中后端接口没有写好,怎么模拟数据渲染页面
可以说 根据后段的字段 直接写死数据
线上跨域问题怎么解决
我们用的是nginx服务器做了请求代理转发,一般都是运维的同学进行 配置的
server {
listen 80;
server_name 域名;
root ####;
location / {
try_files $uri $uri/ /index.html; //解决 HTML5 History 模式直接访问子路由404问题
}
location ^~/api/ {
proxy_pass 域名或者ip地址:端口/; //匹配api开头的请求 端口后面加 / 请求的时候会把api去掉 不加 / 会带上完整的访问接口地址,这个可以和后端约定
}
}
如何上线
我们公司里是有一套专门负责发布的运维系统,测试预发环境测试通过之后,测试会通知开发,可以发布,开发会打包需要发布代码,点击部署即可。
你用的 仓库是什么
公司里提项目管理平台
项目有没有遇到什么难点
我们在项目开发中,特别难的点 其实是没有的,可能会因为一些东西没有接触过被卡住 比如说前端时间一个项目 用到了百度地图。
可以把难点给转换一下 你没有接触过新技术 或者 你写项目的过程 确确实实 碰到的问题
作者:ywjbalabala
出处:https://www.cnblogs.com/ywjbalabala/
本文版权归作者和博客园所有,欢迎转载。转载请在留言板处留言给我,且在文章标明原文链接,谢谢!
如果您觉得本篇博文对您有所收获,觉得我还算用心,请点击右下角的 [推荐],谢谢!