性能优化
- 函数式组件 没有生命周期 没有参数,直接通过propo来获取父组件传的参数来渲染,节约了性能
- 局部变量,当有操作大量重复获取那些监听的属性时 也会有性能消耗,这时候把变量存储到局部变量上 可以节省性能
- v-if 和v-show 的使用,在组件会频繁显示影藏切换时 应该多用v-show
- KeepAlive,当有些页面不需要获取最新数据且频繁查看时 可以用KeepAlive来包一下 避免了重复加载,而且KeepAlive也有声明周期,需要做一些小改变 可以在声明周期中完成
- 有些写死的不需要改变的数据,可以定位为非监听的数据 data不是一个函数吗 在return前 在this上挂在这些属性就可以了
- 按需加载,import()方法 在router里 通过improt方法引入的组件 就会按需加载
- webpack 代码分割,可以把超过多少k的代码单独分割出去,避免首屏加载过慢
- 合理利用computed 的缓存
- 使用vue-lazyload来实现图片懒加载
- ui组件的按需引入 可以借助 babel-plugin-component 就可以实现了
- vue-virtual-scroll-list 来实现长列表加载,原理就是只显示可视区域内的dom
- mini-css-extract-plugin 来把css单独抽离出来,optimize-css-assets-webpack-plugin terser-webpack-plugin 通过该插件来压缩css,但是他会覆盖webpack的自带的uglifyjs压缩,所以还需要使用terser来压缩js,一般这个配置在生产环境的optimization里
- 善用浏览器的缓存
- cdn加速
打包速度优化
noParse: 用来阻止webpack去解析某些js的依赖关系,提高打包速度,这个使用有风险 需要多测试
DllPlugin:一般我会新建一个文件来写,里面指定一些需要抽离的第三方库,比如vue系列,通过DllPlugin来生成索引,通过library来设置暴露出来的变量然后在主打包文件里通过DllReferencePlugin来指定索引文件
webpack小知识
tree shaking 打包时会移除那些未引用的代码 scope hoisting 打包时会结果预解析,这样会减少代码体积运行速度更快 都是基于import和 export的静态结构特性。
面试题解析
vue
1.vue的父子组件渲染过程
父组件执行到beforeMount 后 子组件开始渲染 执行完mounted 后父组件执行mounted
2.对象新属性无法更新视图,删除属性无法更新视图,为什么?怎么办?
vue只会对组件初始化return的data里有的数据进行监听,新添加的无法监听,可以通过$set方法来重新监听
3.说说nextTick的用处?
一般用于获取到最近dom,也可以用于在created里拿到dom元素,我之前做表格时需要实现上一条下一条数据这个功能 就需要用nextTick来包一下 不然选中的那一行数据无法设置高亮状态当然也可以直接使用setTimeout来完成这个功能
4.组件通信方式有哪些?
1.props 接收
2.emit 发送给父组件
3.busevent 中间传输
4.vuex
5.浏览器缓存 例如localstroge cookie
5.Vue的路由实现:hash模式 和 history模式
hash模式:有#号,hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。
history模式:前端的 URL 必须和实际向后端发起请求的 URL 一致,如 www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面
6.router的区别
route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而router是“路由实例”对象包括了路由的跳转方法,钩子函数等
7.data为啥是函数
因为直接是对象的化 所有组件的data都会指向同一个内存地址,而通过函数返回的对象,他会返回一个全新的对象,其实可以通过一个构造函数的原型链上挂载一个data属性 那么这个函数的实例使用的data都会是这个地址的data,如果用函数返回的话,实例调用后 都可以得到一个全新的data
8.computed 和 watch 的区别和运用的场景?
computed 是有缓存的,意思就是传入的参数如果不变 那么他就会直接返回值,不做运算,watch是监听一个属性变化的,意思是当这个值变化后 我执行这个回调, 当我们只是简单的值运算的时候,可以使用computed ,当我们需要根据值变化做出一些异步或者特殊处理时使用watch
react
1.类组件和函数组件之间的区别是啥?
类组件可以使用其他特性,如状态 state 和生命周期钩子。
当组件只是接收 props 渲染到页面时,就是无状态组件,就属于函数组件,也被称为哑组件或展示组件。
2.组件通信
父到子
{/* 渲染子组件 */}
<XiaoWang name={this.state.lastName} getChildMsg={this.getMsg}></XiaoWang>
{/* 子组件内部 */}
<p onClick= {this.handleClick}>子组件(小王):{this.props.name}</p>
handleClick = ()=>{
// 调用父组件传过来的方法
this.props.getChildMsg()
}
子到父: 子组件接收一个父组件的方法,然后在子组件中通过props调用该方法 传过去的参数就是子到父
3.生命周期
先执行构造函数,然后执行componentWillMount这是组件挂载前执行的,然后执行render挂载dom,然后执行componentDidMount 这个函数用的最多 用来发请求渲染数据,到这里 渲染阶段的就执行完了,然后就是数据变更的生命周期,shouldComponentUpdate 这个一般用于判断 数据变更了是否更新视图,如果更新则执行componentWillUpdate 这是更新前执行的,更新后执行componentDidUpdate,然后还有就是子组件的componentWillReceiveProps 这是接收到新属性时调用的,最后就是componentWillUnmount,卸载前执行
4.render-props
就是在组件中 render函数返回一组数据 然后 使用该组件时 可以调用render来渲染视图 也可以通过children来返回
// 组件的render
render() {
const { x, y } = this.state
return this.props.children(x, y)
}
//app中使用
<Mouse>
{(x, y) => (
<p>
鼠标当前位置:(x: {x}, y: {y})
</p>
)}
</Mouse>
5.高阶组件
就是通过一个函数接收一个组件,然后在函数里完成一些可复用的逻辑处理,再把包装过的组件返回出去
6.setState
在一次逻辑处理中不间断多次设置值,只会取最后一次的值来执行可以通过改变调用方式来避免
this.setState(
state => {
return {
count: state.count + 1
}
},
// 当组件完成重新渲染后,这个回调函数就会执行
() => {
console.log('组件已经更新完成', this.state.count)
console.log(document.querySelector('h1').innerText)
}
)
js
1.继承的2种方式
function Son(name) {
Father.call(this);
this.name = name || "son";
}
//直接借用Object.create()方法
Son.prototype = Object.create(Father.prototype);
// 修复构造函数指向
Son.prototype.constructor = Son;
class Son extends Father {//es6
constructor(name) {
super(name);
this.name = name || "son";
}
}
2.防抖和节流
防抖的概念:设定一个时间,当触发后,进入倒计时,在倒计时结束前 如果再次触发,那么倒计时将重新计时
节流的概念:设定一个时间,当触发后,进入倒计时,在倒计时结束前是无法再次触发的,倒计时结束可再次触发
3.哪些那些操作会造成内存泄漏
1.闭包
2.意外的全局变量
3.遗忘的定时器
5.没有清理的dom元素引用
4.微任务与宏任务
微任务:典型的微任务就是then方法,
宏任务:settimeOut
优先级: 首先浏览器会执行同步任务,然后检测到微任务 宏任务它会一一挂起,然后同步任务执行完毕后,检索微任务,把微任务执行完毕后再执行宏任务。
5.JS中typeof与instanceof的区别
typeof:只能检测原始数据类型,当检测到数组 对象时 只会返回object
instanceof: 这是根据原型链进行判断的,a instanceof b 就是判断 a是否继承过b 返回的值 是true 和false
6.const let 变量
Const是常量,设置后普通属性是无法改变值的 对象和数组是无法改变其指向的地址的,意思是可以改变内容,不能改变指向。
相同点: let 和const是块级作用域属性,他们不会和var一样有变量提升,其次不能重复声明。
7.Object有哪些方法
seal() 封闭一个对象,阻止添加新属性,原有属性的值是可以改变的,原有属性指向对象,该对象是可以添加新属性的。
freeze() 完全冻结对象,不能添加新的属性,不能删除已有属性,冻结一个对象后该对象的原型也不能被修改。
keys() 获取一个对象的所有可枚举的键 返回成为一个数组
Object.prototype.toString.call() 一般用来检查数据类型 返回值为 [object xxxx] String Array之类的
hasOwnProperty() 判断对象自身属性中是否具有指定的属性,不包括对象原型链上的方法的
defineProperty(obj, key, descriptor) 是用来定义对象属性的 传入一个对象 对象的key,如果key是不存在的 就为该对象新建一个key。
descriptor里可以配置value:'key对应的值', writable: true可以被修改,enumerable: true可以被枚举,configurable: true可以被删除。
也可以传入 get set方法,vue就是通过该方法完成双向绑定的。
isPrototypeOf() 用来判断当前对象是否在传入的参数对象的原型链上比如 构造函数.isPrototypeOf(实例对象a)
valueOf() 返回对象的原始值,可能是字符串、数值或bool值等,看具体的对象。
8.伪数组转真数组
Array.from() [].slice.call(伪数组)
9.ES6箭头函数
书写方便,没有自己的this 一般默认使用自己的上一个作用域的this,不能作为构造函数,不能使用new,不能使用argumetns,取而代之用rest参数…解决,箭头函数没有原型对象。
10.js数组常用方法
push() 向数组末尾添加一项
pop() 删除并返回数组的最后一个元素,若该数组为空,则返回undefined
unshift() 向数组的头部添加一项,并返回数组长度
shift() 删除数组的第一个并返回该删除项
concat() 合并两个数组返回一个新数组
join() 将数组的每一项用指定字符连接形成一个字符串。默认连接字符为 “,” 逗号
reverse() 让调用该方法的数组变成倒叙
sort() 排序,传入一个函数作为参数,函数 return a-b就是从小到大,b-a就是从大到小
map() 遍历该数组,遍历完后 返回一个新数组 return的值就是新数组的各个项
slice() 从start开始,end之前结束,不到end;如果不给end值,从start开始到数组结束
forEach() 遍历该数组
filter()过滤数组 遍历完毕后返回一个新数组 符合条件的才会返回成为新数组的一项
every()对数组中的每一项进行判断,若都符合则返回true,否则返回false
some()对数组中的每一项进行判断,若都不符合则返回false,否则返回true
reduce() 第一个参数为回调函数 第二个参数为初始值,不设置时参与累加的话默认为0,回调函数有4个参数,第一个为上一回合遍历的返回值 第二个是本回合遍历的项,第三个为坐标,第四个为调用该方法的数组
11.原型链
假设有一个P构造函数,那么这个构造函数的prototype就会指向P.prototype,
P.prototype的constructor又会指向P
P的实例对象proto就会向上查找 找到P的prototype
P的prototype也可以使用proto 那么就会向上查找 找到object的prototype 再向上就是null了
构造函数的proto指向大构造函数的prototype 大函数的proto指向 object的
大构造函数的proto和prototype都指向 大构造函数的prototype
object的构造函数 的proto指向了大构造函数的prototype
只有大构造函数和object的构造函数有proto 普通的构造函数没有这个属性
css
1.清除浮动
overflow:hidden
父元素直接设置高度
伪元素 使用clear both 也可以直接末尾写个div clear both 但是这样对性能不友好
::after {
content: '.';
height: 0;
display: block;
clear: both;
}
2.什么是BFC
根元素,即HTML标签就是一个最好的bfc,意思就是内部的布局不会影响到外部的布局,浮动元素会参与计算高度
生成条件 float, overflow 为 auto、scroll、hidden, display值为inline-block、table-cell、table-caption、table、inline-table、flex、,position值为 absolute、fixed
3.实现垂直水平居中
绝对定位或者flex记住2种即可
4.flex
flex-direction 主轴布局方式 colum为垂直布局 默认为row行模式
flex-wrap:换行 nowrap | wrap | wrap-reverse;
justify-content: 主轴的显示方式 center space-between space-around flex-start flex-start space-between为两边有空隙的,space-around为两边无空隙的。
align-items: 交差轴显示方式 默认为stretch即如果项目未设置高度或者设为 auto,将占满整个容器 flex-start flex-end center baseline: 项目的第一行文字的基线对齐
align-content: flex-start | flex-end | center | space-between | space-around | stretch; 定义了多根轴线的对齐方式,如果项目只有一根轴线,那么该属性将不起作用,当你 flex-wrap 设置为 nowrap 的时候,容器仅存在一根轴线,因为项目不会换行,就不会产生多条轴线。
5.position的
static(默认):按照正常文档流进行排列
relative(相对定位):不脱离文档流,参考自身静态位置通过 top, bottom, left, right 定位
absolute(绝对定位):参考距其最近一个不为static的父级元素通过top, bottom, left, right 定位
fixed(固定定位):所固定的参照对像是可视窗口
6.CSS3有哪些新特性
RGBA和透明度 background-image 和background-size text-shadow border-radius 媒体查询
7. 1rem、1em、1vh、1px各代表的含义
1rem:html的字体大小
1em:相对于父元素的字体大小
1vh/1vw:视窗宽高的1%
个人总结:
1.mvvm原理
双向绑定: 就是通过defineProperty去劫持数据
编译模板: 取出所有节点 用这个方法createDocumentFragment 然后遍历这些节点 判断是文本节点还是node,文本节点就需要用正则去掉大括号,再取出值去替换,node节点需要递归遍历 然后取出他们上面的指令 如果是指令 取出指令对应的值,给元素node设置上去
watch :观察者,编译模板时 检测到使用了data上的属性时(v-text model 或者文本节点上的{{}}),就会新建一个watch 在watch中 他会获取到对应属性的初始值,又因为所有的值都在被监听, 获取的时候 会执行defineProperty上的get方法,那么就可以在这个方法中把watch 添加到dep的数组里,然后wathc提供一个更新方法
// dep 设置target
Dep.target = this;
// 这时候 target 指向watch 在执行defineProperty 上的get方法时 就可以把watch添加到dep上了
let value = this.getVal(this.vm,this.expr);
// 用完释放
Dep.target = null;
dep:发布订阅 ,一个数据对应一个dep,当数据发生变化时 会执行defineProperty上的set方法,在这个方法里执行掉dep数组里的watch updata方法里会执行修改视图的回调 这个回调在编译视图的时候就绑定上去了
2.通过mixin来封装组件
首先需要把element的表格组件和分页器引入,然后通过props来接收具体的列数据 通过for循环来显示,然后在props里接收一些是否显示图片 是否显示选择框 是否显示图片 这种布尔值,再新建一个mixin,在这里面去引入封装过的表格组件,通过mixin 我就可以在这里面即使用表格提供的数据和方法 又可以使用引入页面的方法 只要页面按照我这个mixin的规范来书写方法即可,可以简化开发流程,也可以简化页面的代码。
3.vue权限校验
先在action里发起登录请求,然后拿到用户的信息和token,把token设置到localstore里,然后router有个方法onReady 就是router准备好了后只会执行一次的方法,然后查看是否有token 有就去请求后端获取权限表单,然后去和之前写好的路由列表做对比过滤 过滤处理的页面动态添加给router,登录的时候 设置token 然后直接去获取一次用户权限列表。 然后还有就是token刷新,一般后端给的是token有效期和token的刷新时常,有效期比较短,我们可以在axios请求里定义一个变量防止重复验证,然后 在请求中发现报错401 我就可以去执行刷新token接口,第一次执行的时候把变量设置为false,进入首页的时候一般都有多个请求,那么就需要创建一个数组 ,判断变量为false的时候 就把这些请求放到数组里,当token刷新完毕后 在回调里 把这些请求一一执行
4.vuex的实现原理
- 在install里通过mixin混入一个beforecreate钩子,在钩子里给vm实例挂载store 因为每个实例都会执行这个钩子 所以判断一下是否根组件 是就直接挂载 不是就通过获取父组件的store来挂载 之后就可以在模板里使用this.$store.xxx
- 获取到getters 然后把里面的key通过object.key提取出来 然后遍历 在遍历里把getters的函数名和函数提取出来 一一赋值给store类里的getter
- 其它两个同上 不过需要写一个原型链函数 commit 因为用户需要用这个函数才能改变状态 这个函数接收一个函数名 然后在里面调用store里的改变状态函数
5.大文件断点续传
1.首先需要一个type="file"的input 然后选中文件
2.选中文件后,浏览器会把这个文件转成一个Blob对象,这个对象有一个slice方法,通过该方法可以进行切片,切片后存入到一个数组中。
3.然后把这个数组map遍历一下 添加一些文件名 切割后的hash名属性,然后把这些切片数据封装到一个个formdata里。
3.5 hash可以通过spark-md5来生成 创建一个web-worker来生成hash免得造成ui卡顿,因为有同源策略 所以把spark-md5封装到public下。
4.把这些formdata存入一个数组中,然后把formdata当参数发给后端,然后在axios的onUploadProgress 回调中拿到进度条。
5.然后通过拿到的分片进度就可以算出总进度了 绑定到视图上就可以了,然后就是要用一下promise的all方法 当那组上传请求都发送完了后 请求后端的合并api,这样就完成上传了。
6.可以通过axios.CancelToken 来暂停传输,然后续传的话可以请求服务器获取到已经上传的文件的hash名字,然后过滤掉已经上传的文件,剩下的就可以继续执行上传了 。
7.进度条会有问题,那就是把之前上传成功的进度都计算为100% 然后计入总进度条,并设置进度条如果小于当前进度不给与更新。