Css问题
1. 什么是外边距合并,如何解决?
根据规范,一个盒子如果没有添加 BFC,那么它的上边距应该和其父元素的上边距重叠。
2. 什么是双飞翼布局和圣杯布局?
两种布局形式上是一样的,实现起来双飞翼布局稍微简单一点,利用
1.开启浮动+清除浮动
2.主区域两侧留白【margin】
3.两侧上去【margin负值】
圣杯布局稍微复杂一点
1.开启浮动+清除浮动
2.主区域两侧留白【padding】
3.两侧上去【margin负值+相对定位】
3. 垂直水平居中方案
1.flex弹性盒
2.子绝父相定位+踹死房母
3.子绝父相上下左右四个方位坐标都设置为0+margin外边距:auto
4.定位+margin外边距负真身宽高的一半
4. 什么场景引起重绘,什么场景引起重排
重绘和回流是渲染步骤中的一小节,但是这两个步骤对性能影响很大
1. 重绘是当节点需要更改外观而不影响布局的,比如改变`color`会引起重绘
2. 回流是布局或者几何数据需要改变就会引起回流。
回流必定会发生重绘,重绘不一定会引发回流。回流所需的成本比重绘高的多,改变深层次的节点很可能导致父节点的一系列回流。
5.visibility【VS哔哩弟】:hidden和display:none的区别
display:none是css的隐藏不占位
visibility:hidden 隐藏但是占位。
6. 清除浮动方法
1. 伪元素清除浮动::after::befor{content:""、display:block、clear:both}
2. 使用before和after双伪元素清除浮动
3. 父元素添加overflow属性(不推荐,不会自动换行导致内容被隐藏掉)
4. 父元素设置高度
7. ::before 和 :after 的双冒号和单冒号有什么区别?
:单冒号是伪类,::双冒号是伪元素
8. Flex布局语法:
- flex-direction【德乳癌科神】属性决定主轴的方向
- justify-content【加斯弟fai-】属性定义了盒子在主轴上的对齐方式
- flex-wrap决定是否换行
- align-items属性定义项目在交叉轴上如l何对齐。
9. BFC块级格式化上下文的理解:
**BFC可以理解为:**是一个独立的布局容器,这个容器中按照自己的的规则进行物品摆放,并且不会影响其他环境中的物品。如果一个元素符合触发BFC的条件,则当前容器中的元素布局不受外部影响。
其他重点
1.websoket是什么
websocket是一种网络通信协议
它是一种做即时性聊天的技术,这个东西其实和Ajax比较像,有固定的格式步骤,只不过这种是长链接,Ajax技术是短链接,局部刷新。
websocket既可以由客户端发起,也可以由服务端发起
在用的过程中,首先初始化一个Websocket实例
当我要发送数据的时候触发onopen【昂鸥鹏】方法的send【散的】方法,携带发送的内容
接受服务器的数据是onmessage【昂买瑟纸】方法
关闭本次长链接用onclose【昂可镂Z】方法
我的项目中使用socket.io这个库
-
socket.io 是一个基于 WebSocket 的 CS(客户端-服务端)的实时通信库
-
总之:它是一套基于 websocket 前后端即时通讯解决方案
使用客户端js库
pnpm add socket.io-client
import io from 'socket.io-client'
// 参数1:不传默认是当前服务域名,开发中传入服务器地址
// 参数2:配置参数,根据需要再来介绍
const socket = io()
socket.on('connect', () => {
// 建立连接成功
})
// chat message 发送消息事件,由后台约定,可变
socket.emit('chat message', '消息内容')
// chat message 接收消息事件,由后台约定,可变
socket.on('chat message', (ev) => {
// ev 是服务器发送的消息
})
// 离开组件需要使用
socket.close()
HTTP协议无法实现服务器主动向客户端发起消息,websocket连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。websocket只需要建立一次连接,就可以一直保持连接状态
2.为什么要对组件库的组件二次封装?
vue的组件大致可以分为两种:基础组件、业务组件 基础组件不涉及到任何的业务,比如element所提供的组件,在系统中,它能用作某种特定的功能navigator 业务组件是为了实现某种特定业务存在的,业务是它设计和实现的主要考虑因素之一,它往往会依赖基础组件去实现navigator.msSaveBlob 优点:组件化开发能大幅度提高应用开发效率、测试性、复用性等;能让web前端代码实现“高内聚”和“低耦合”,使得前端开发的过程变成搭积木的过程
3.你封装过组件吗?说一下你是在vue项目里如何封装组件的?
1 分析项⽬的所有⻚⾯结构和业务功能,抽离出相同的⻚⾯结构和业务功能 2 在src⽬录下创建⼀个components【肯刨藤斯】这个的⽂件夹 3 在这个⽂件夹内创建可复⽤的组件 4 在需要的⽤的组件⾥⾯引⼊创建的这个可复⽤的组件,并进⾏注册,以标签的形式写在对应的地⽅ 5 接下来就需要对可复⽤的组件内容要进⾏封装,那么在封装的时候我们要注意组件的封闭性和开放性以及粗细粒度 6 所谓的这个封闭性就是当我们在组件内部定义好之后外部是⽆法进⾏修改的,⽐如当前有⼀个关闭的符号,或者有⼀个箭 头,我们需要不管任何⼈使⽤该组件时候都能够显示这个箭头,⽆法从外部进⾏修改 7 所谓的开放性就是我们需要将动态的内容以及组件通信的⽅式进⾏数据传递,保证组件的可扩展性 8 ⽽所谓的粗细⼒度就是我们可以把⼀整个⻚⾯当作⼀个组件去进⾏封装,也可以⼀个⻚⾯包含了多个组件,⾄于到底选择 哪种呢,这个是没有⼀个标准的,我们需要根据⾃⼰的业务需求去进⾏判断 9 总结来说,所谓的如何封装可复⽤组件其实技术核⼼就是通过我们vue提供的组件通信在结合slot插槽来进⾏分装MVC,MVP与MVVM模式:
4.前端路由的理解:
首先就是路由分为前端路由和后端路由,前端路由是指不跳转URL的情况下,进行页面中组件的跳转,主要针对于Vue,React等SPA项目,即单页面应用,而他的原理大概是以onhashchang,监听hash值变化进行跳转,其中#后面的哈希值,不进入后台请求,这是前端路由和后端路由最大的区别【下一题可能会问路由的两种模式,讲完后循序渐进的将话题引入到路由鉴权的处理上!】
history模式:HTML5中新增了pushState方法,该方法应用于浏览器的历史记录,提供了对历史记录进行修改的功能,使用黑丝腿瑞模式会对前端和后端的URl地址进行判断如果不一致会报404错误
hash模式:默认的是hash,地址栏的url有#,不会包含在http请求中,对后端没有任何影响,因此改变hash不会重新加载页面
4-1路由原理 history 和 hash 两种路由方式的特点
hash 模式
location.hash的值实际就是URL中#后面的东西 它的特点在于:hash虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面- 可以为 hash 的改变添加监听事件
window.addEventListener("hashchange", funcRef, false);
@程序员poetry: 代码已经复制到剪贴板
- 每一次改变
hash(window.location.hash),都会在浏览器的访问历史中增加一个记录利用hash的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了。 - 特点:兼容性好但是不美观
history 模式
利用了
HTML5 History Interface中新增的pushState()和replaceState()方法
- 这两个方法应用于浏览器的历史记录站,在当前已有的
back、forward、go的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前URL改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础 - 特点:虽然美观,但是刷新会出现
404需要后端进行配置
5.浏览器缓存机制
我们都知道当我们在浏览器中打开一个页面时,浏览器会根据你输入的URL到对应的服务器上请求你想要的数据资源.这个过程中可能页面需要等待一段时间(白屏时间)才能渲染到你的页面中
当你想要提高用户体验时,那就不得不提各种缓存技术了,例如:DNS缓存,CDN缓存,浏览器缓存,页面本地缓存等等,有一个良好的缓存策略可以降低重复资源的请求,降低服务器的开销,降低用户页面的加载速度
6.什么是堆栈溢出?
Js的数据存储分为堆和栈,代码运行都需要计算存储空间。
栈遵循先进后出的原则,所以程序从栈底开始计算,程序内部函数的调用和返回值会不停的执行进栈和出栈的操作,栈内被占用的资源也在不断的对应变化,一旦调用即进栈操作太多,返回即出栈不够,这时候就会导致栈满,再进栈就会溢出来。
是指内存空间已经被申请完,没有足够的内存提供了
7.什么是内存泄漏?
申请的内存执行完之后没有及时的清理和销毁,占用空闲内存,既不能使用也不能回收。
几种会导致内存泄露的情况:
- 意外的全局变量
- 被遗忘的计时器或回调函数
- 脱离DOM的引用(分离的DOM节点)
8.什么是内存溢出?
- 一种程序运行出现的错误。
- 当程序运行时需要的内存超过剩余的内存时,就会内存溢出的错误。
9.平时如何管理你的项目(你如何维护老旧项目)
-
先期团队必须确定好全局样式(
globe.css),编码模式(utf-8) 等; -
编写习惯必须一致(例如都是采用继承式的写法,单样式都写成一行);
-
标注样式编写人,各模块都及时标注(标注关键样式调用的地方);
-
页面进行标注(例如 页面 模块 开始和结束);
-
CSS跟HTML分文件夹并行存放,命名都得统一(例如style.css); -
JS分文件夹存放 命名以该JS功能为准的英文翻译。 -
图片采用整合的
images.png png8格式文件使用 - 尽量整合在一起使用方便将来的管理 -
规定全局样式、公共脚本
-
严格要求代码注释(html/js/css)
-
严格要求静态资源存放路径
-
Git 提交必须填写说明
10.卡顿问题解决
- CSS动画效率比JS高,
css可以用GPU加速,3d加速。如果非要用JS动画,可以用requestAnimationFrame - 批量进行DOM操作,固定图片容器大小,避免屏幕抖动
- 减少重绘重排
- 节流和防抖
- 减少临时大对象产生,利用对象缓存,主要是减少内存碎片
- 异步操作,
IntersectionObserver,PostMessage,RequestIdleCallback
Vue性能优化
1.vue首屏加载优化有哪些方案么
Vue-Router路由懒加载(利用Webpack的代码切割)- 使用
CDN加速,将通用的库从vendor进行抽离 Nginx的gzip压缩Vue异步组件- 服务端渲染
SSR - 如果使用了一些
UI库,采用按需加载 Webpack开启gzip压缩Service Worker缓存文件处理- 使用
link标签的rel属性设置prefetch(这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低,prefetch通常用于加速下一次导航)、preload(preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度)
2.编码阶段
- 尽量减少
data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher; - 如果需要使用
v-for给每项元素绑定事件时使用事件代理; SPA页面采用keep-alive缓存组件;- 在更多的情况下,使用
v-if替代v-show; key保证唯一;- 使用路由懒加载、异步组件;
- 防抖、节流;
- 第三方模块按需导入;
- 长列表滚动到可视区域动态加载;
- 图片懒加载;
3.用户体验:
- 骨架屏;
- 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启
gzip压缩等。
4.长列表优化
优化长列表vue-virtual-scroll-list优化长列表
虚拟列表的实现原理:只渲染可视区的 dom 节点,其余不可见的数据卷起来,只会渲染可视区域的 dom 节点,提高渲染性能及流畅性,优点是支持海量数据的渲染;
github地址:github.com/tangbc/vue-…
5.打包优化
-
压缩代码;
-
Tree Shaking/Scope Hoistingscope hoisting是webpack3的新功能,直译过来就是「作用域提升」。熟悉 JavaScript 都应该知道「函数提升」和「变量提升」,JavaScript 会把函数和变量声明提升到当前作用域的顶部。「作用域提升」也类似于此,webpack 会把引入的 js 文件“提升到”它的引入者顶部
-
使用cdn加载第三方模块;
-
多线程打包
happypack; -
splitChunks抽离公共文件; -
sourceMap优化;
面试100%问到
1-1.在实际工作中,你对Vue做过哪些优化
-
v-if和v-show
v-if彻底销毁组件v-show使用dispaly切换none- 实际工作中大部分情况下使用
v-if就好,不要过渡优化
-
v-for使用key
key不要使用index
-
使用computed缓存
-
keep-alive缓存组件
- 频繁切换的组件
tabs - 不要乱用,缓存会占用更多的内存
- 频繁切换的组件
-
路由懒加载
- 项目比较大,拆分路由,保证首页先加载
-
异步组件
-
针对体积较大的组件,如编辑器、复杂表格、复杂表单
-
拆包,需要时异步加载,不需要时不加载
-
减少主包体积,首页会加载更快
-
演示
<!-- index.vue --> <template> <Child></Child> </template> <script> import { defineAsyncComponent } from 'vue' export default { name: 'AsyncComponent', components: { // child体积大 异步加载才有意义 // defineAsyncComponent vue3的写法 Child: defineAsyncComponent(() => import(/* webpackChunkName: "async-child" */ './Child.vue')) } } </> <!-- child.vue --> <template> <p>async component child</p> </template> <script> export default { name: 'Child', } </script>
-
1-2.你在使用Vue过程中遇到过哪些坑
- 内存泄露
- 全局变量、全局事件、全局定时器没有销毁
- 自定义事件没有销毁
- Vue2响应式的缺陷(vue3不在有)
data后续新增属性用Vue.setdata删除属性用Vue.deleteVue2并不支持数组下标的响应式。也就是说Vue2检测不到通过下标更改数组的值arr[index] = value
- 路由切换时scroll会重新回到顶部
- 这是
SPA应用的通病,不仅仅是vue - 如,列表页滚动到第二屏,点击详情页,再返回列表页,此时列表页组件会重新渲染回到了第一页
- 解决方案
- 在列表页缓存翻页过的数据和
scrollTop的值 - 当再次返回列表页时,渲染列表组件,执行
scrollTo(xx) - 终极方案:
MPA(多页面) +App WebView(可以打开多个页面不会销毁之前的)
- 在列表页缓存翻页过的数据和
- 这是
- 日常遇到问题记录总结,下次面试就能用到
1.Vue2和Vue3都有哪些区别?
1.首先就是响应式原理不同:
Vue2是通过Object.defineProperty数据劫持结合发布者订阅者模式,检测要数据发生变动就会触发一系列监听回调从而实现响应式原理,但是不能检测出数组长度的变动,遇到这种情况使用this.$set方法去解决。
而Vue3是使采用了ES6的Proxy数据代理方法来替换defineProperty,在其中使用Reflect【瑞拂莱特】反射函数得到代理对象的原数据和要检测的目标数据,还有新属性的值实现数据的双向绑定。
我们用ref对基本数据类型进行数据双向绑定,reactive对引用数据类型进行数据双向绑定
ref用于对基本数据类型的双向绑定,reactive【瑞阿科踢乌】用于对复杂数据类型的双向绑定。
2.Vue3可以更好的支持TypeScript
3.Composition API(组合式API)【康普贼神】setup配置,
ref和reactive【瑞阿科提乌】,
watch与watchEffect【握持亿筏特】,
provide【pro歪的】与inject【阴杰克特】
4.新的内置组件Fragment碎片【父爱歌们特】,Teleport【踢梨普特】,Suspense【涩斯半斯】
5.新的生命周期函数
2.Setup是什么?
setup 是一个接收 props 和 context 的函数,通过return将内容暴露出去,setup位于beforeCreated之前,由于setup函数执行的时候项目还没有进行初始化(没有执行生命周期钩子),所以不能访问data或者methods中的数据,所以this指向为undefind。
setup的加入就是为了让vue3使用组合式API。使用组合式API更适合大型项目的开发,将每段业务的逻辑代码从声明到如何处理都写在一起,语法更贴近React。
3.组合式API和选择式API都是什么?
首先在我们Vue2写选择Api组件的时候我们会创建.vue文件,它的格式都是固定的,响应式数据写在data中,操作方法都写在methods配置项中,还有watch监听computed计算属性等等,这种代码结构比较适合阅读,理解,但是一旦单个组件涉及到的功能过多,代码行数上千上万,简直就是噩梦,找方法,找变量,来来回回的翻找,非常麻烦,这个时候如果可以把相关代码逻辑集中起来管理,情况一定会好很多,还好的是在Vue3中尤雨溪已经帮我们考虑到这个问题,所有有了组合式Api,就是平时所说的setup函数。
options API选择式 左图
它的特点是容易理解,因为各个选项都有固定的书写位置,比如响应式数据就写到data选择中,操作方法写到methods配置项中等,应用大了之后,都需要上下找代码。
composition API组合式 右图
特点是特定功能相关的所有东西放在一起维护,比如功能A相关的响应式数据与操作方法等放在一起,这样不管应用有多大,都可以快速定位到某个功能的所有相关代码,维护方便,如果功能复杂,代码量大,还可以进行逻辑拆分处理。
4.说一说你对promise的理解
promise 解决回调地狱问题 把异步代码 使用同步写法去写
Promise 是一个构造函数 通过new 创建实例返回 promise对象,
参数是一个回调函数,该回调函数有两个参数resolve【瑞绕】和reject【瑞杰K特】
异步请求成功执行resolve方法 请求失败执行reject方法
有三个状态:
pending【潘腚】等待
fulfilled【搜费油】成功
rejected【瑞杰克的】失败
状态值都会通过resolve和reject改变当前状态
但是状态一旦改变 就不能再次更改
promise对象常用的方法是:then方法和catch两种方法
.then有两个参数,都是以回调函数为参数的方式去接收resolve,和reject成功和失败的参数
.catch捕获整个的错误信息,大部分的时候就是then的第二个参数的语法糖,它不会堵塞代码块,并且会捕获then的成功函数的错误
.then和.catch的区别:绝大多数是没区别的 但是 catch可以捕获then resolve的报错信息
.finally【乏那里】方法,无论成功与否都会执行
.race([p1,p2,p3])【瑞斯】竞赛/竞速,就是多个promise同时请求的时候,以第一个请求完成的promise状态作为整个promise的状态,结束的终点直到resovel出现
.all【奥】方法以数组的方式接受一个或者多个Promise实例作为参数,如果全部成功,状态为resolve,有一个失败则状态就变成reject,返回值组成一个数组传给回调。
promise能取消吗?
可以取消,使用new AbortController【阿保特肯戳乐】实例的abort()方法
5. async和await
async,await是异步请求的终结方案,也就是Promise的终极解决方案
在js中,可以通过async关键字来快速创建异步函数,异步函数也就意味着该函数的执行不会堵塞后面代码的执行。
async函数也是函数,平时我们怎么使用函数就怎么使用它,直接加括号调用就可以了。async函数返回的是一个promise对象,如果要获取promise返回值,我们应该用then方法
调用异步函数的时候,可以直接在函数前使用await关键字来对其进行调用
调用await的时候,他会等待Promise执行出结果后将结果返回,可以通过变量进行接受结果。
注意:await并不是将异步函数变成同步函数,只是改变了异步函数的调用方式。
async和await的缺点,怎么处理await返回的错误,
因为在awite在转换promise对象时,会吞并promise的错误,我们可以利用原生JS的try,catch进行抛出异常 抛出异常 throw new Error
async/await的优缺点
它是把异步请求变成同步执行的.async放在最近函数外调用,返回的是promise对象 1.方便级联调用:即调用依此发生的场景; 2.同步代码编写方式:Promise使用then函数进行链式调用,一直....; async/await从上到下,顺序执行,就像写同步代码一样,更符合代码编写习惯
缺点: 没有catch错误处理,需要js原生的try【踹爱】,catch进行错误处理
6.JS事件循环机制(事件轮循 Event Loop):
同步任务 异步任务: (微任务 宏任务)
JavaScript是一门单线程语言,同一时间内只能执行一个任务,实现不堵塞的方法,就是依靠事件循环
事件循环 分为同步任务和异步任务
同步任务指的是非耗时任务
异步任务指的耗时任务
异步任务又分为宏任务和微任务
优先执行同步任务,遇到异步任务推入任务列表中,等同步任务执行完再执行任务队列中的异步任务,异步任务又分宏任务和微任务,先执行微任务,再执行宏任务
①微任务
- Promise.then,.catch,.finally
- process.nextTick
②宏任务
- 异步ajax请求
- setTimeout,setInterval
- js的事件监听
7.深拷贝和浅拷贝区别,递归如何实现深拷贝
假设A赋值给了B,当修改了A的值,看B是否会发生改变,如果改变就是浅拷贝 , 未发生改变就是深拷贝。
基本数据类型名字和值都会存储在栈内存中
引用数据类型名字存储在栈当中,值存在堆内存中,
栈内存会提供一个引用地址指向堆内存中的值。
浅拷贝只是拷贝一层,对更深层次对象级别的只拷贝引用(地址);深拷贝拷贝多层,每一级别的数据都会拷贝
浅拷贝实现
- Object.assign(target,...sources)
- 展开运算符
深拷贝实现
-
lodash 插件的 _.cloneDeep(拷贝的对象)
-
递归的方式实现
-
可以通过
JSON.parse(JSON.stringify(object))来解决该方法是由局限性的
- 会忽略
undefined - 不能序列化函数(不能转换函数 直接报错)
- 不能解决循环引用的对象
let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE - 会忽略
8.递归实现深拷贝步骤:
使用深拷贝之前,判断是否为数组或对象,如果是数组和对象使用for循环遍历每一项,赋值给事先准备好的空数组和空对象,如果是一个多维数组或者多维对象,会选择使用递归去赋值,递归主要目的
9.使用递归深拷贝需要注意什么?
无论是不是使用深拷贝使用递归,在平时使用递归也要考虑循环引用的情况,在深拷贝中,我们可以将每一次递归记录存储在weckmap中,在每一次递归先去查找是否存在相同记录,如果存在返回该记录
10.平时写深拷贝手写吗?
在工作中一般去使 用插件去使用深浅拷贝,因为插件封装的更完整,为了提高效率,降低开发风险,我一般都去使用插件去实现深浅拷贝,防抖节流,但是我自己也会实现深浅拷贝,防抖节流。
11.递归概念:
递归它一种解决方案,一种逻辑思想,递归调用是属于特殊的嵌套调用,主要目的是为了处理不确定层级关系的相同数据结构的数据处理。 优点:代码更加清晰简洁,可读性更好。 缺点:时间和空间消耗比较大,很多计算式重复的,调用栈可能会溢出
(1)程序自我调用。目的是为了处理不确定层级的相同数据结构的数据处理。
(2)优点:代码更简洁清晰,可读性更好;
缺点:时间和空间消耗比较大、很多计算都是重复的、调用栈可能会溢出
递归调用是一种特殊的嵌套调用,是某个函数调用自己或者是调用其他函数后再次调用自己的,只要函数之间互相调用能产生循环的则一定是递归调用,递归调用是一种解决方案,一种逻辑思想
11.项目上线流程, 后台管理系统和电商的特点:
项目上线流程:
1,产品经理接过项目
2,找需求经理对接
3,需求大会(开始分工)
4,UI设计原型图
5,前端开发
6,测试进入
7,灰度环境发布
8,项目打包上线
后台管理系统的特点:
1,安全性和权限性能
2,通过内网访问,私密的
3,管理内部人员的
4,样式单一
5,数据多(逻辑强)
电商(淘宝,京东都属于电商)特点:
1,样式好看,有各自的风格
2,线上运行,外网可查
3,上亿条数据庞大
4,难维护,迭代快
13.防抖节流以及使用场景
在滚动事件中需要做个复杂计算或者实现一个按钮的防二次点击操作。可以通过函数防抖动来实现
对于按钮防点击来说的实现
- 开始一个定时器,只要我定时器还在,不管你怎么点击都不会执行回调函数。一旦定时器结束并设置为 null,就可以再次点击了
- 对于延时执行函数来说的实现:每次调用防抖动函数都会判断本次调用和之前的时间间隔,如果小于需要的时间间隔,就会重新创建一个定时器,并且定时器的延时为设定时间减去之前的时间间隔。一旦时间到了,就会执行相应的回调函数
// 防抖
function debounce(fn, delay = 300) {
//默认300毫秒
let timer;
return function () {
const args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, args); // 改变this指向为调用debounce所指的对象
}, delay);
};
}
// 测试
window.addEventListener(
"scroll",
debounce(() => {
console.log(111);
}, 1000)
);
// 节流
// 设置一个标志
function throttle(fn, delay) {
let flag = true;
return () => {
if (!flag) return;
flag = false;
timer = setTimeout(() => {
fn();
flag = true;
}, delay);
};
}
window.addEventListener(
"scroll",
throttle(() => {
console.log(111);
}, 1000)
);
节流:防抖动和节流本质是不一样的。防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。
* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了2秒
// 但是他需要2.2秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
14.Javascript垃圾回收方法
- 标记清除(mark and sweep)
- 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”
- 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
- 引用计数(reference counting)
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个 变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间
15.内存泄漏
定义:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。
js中可能出现的内存泄漏情况
结果:变慢,崩溃,延迟大等,原因:
- 全局变量
dom清空时,还存在引用ie中使用闭包- 定时器未清除
- 子元素存在引起的内存泄露
避免策略
- 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;
- 注意程序逻辑,避免“死循环”之类的 ;
- 避免创建过多的对象 原则:不用了的东西要及时归还。
- 减少层级过多的引用
16. 原型与及原型链的理解?
总结:
什么是原型?
原型是一个构造函数的静态成员 原型是函数对象的一个属性
作用是定义了构造函数创建出对象的公共祖先
通过该构造函数创建出的对象(proto_)可以继承使用共同的属性和方法,达成数据共享,提高了对象的使用效率
什么是prototype? 显示原型
所有函数必有prototype属性。并且prototype是一个对象,指向了当前构造函数的原型对象
什么是 __ proto _?隐式原型,或者叫作连接点。
对象才有—proto—,而且所有对象必有—proto—属性(这里对象除了我们理解的对象还包括了函数,数组等对象)。当用构造函数实例化一个对象时候,会将新对象的—proto—指向构造函数的prototype。
什么是构造函数?
构造函数其实是一种特殊的函数,主要用来初始化对象,也就是为对象成员变量赋初始值,它总与new关键字一起使用
什么是原型链
当我们访问一个实例(例如:对象名.属性名)的属性或方法时,会先在实例上查找,若查找不到,会去原型上查找,若原型上查找不到,就到原型的原型上查找,若还是查找不到就指向null
通过_proto__向上查找
为什么要设计js原型呢?
如果单纯使用new构造函数生成对象的话,有一个很明显的缺点,即属性和方法不能共享,且浪费大量的内存
每次实例一个对象,都需要开辟一个空间,造成内存严重浪费
每一个对象的属性和方法都是独立的,不会互相影响,这就无法做到数据共享
如果方法不放在原型上继承的缺点:
如果方法不放在原型上面的话,构造函数创建对象的时候,当构造函数的方法很多的时候,其结构很冗余复杂
每次实例一个对象,都需要开辟一个空间,造成内存严重浪费
每一个对象的属性和方法都是独立的,不会互相影响
原型思想的继承的优点:
可以让构造函数的数据结构变得简单
可以节省内存的空间
每一个实例可以共享原型上的方法和属性,达成数据共享
原型的缺点
因为数据存在共享,所以可以被修改或者删除
javascript原型与原型链
- 每一个对象都有自己的原型,而每个原型也是对象,也有自己的原型,从而形成链式结构,称为原型链,终点为null
- 对象访问原型链规则: 就近原则
原型:
原型是一个构造函数的静态成员
通过 构造函数名.protype访问
作用可以让构造函数创建的对象使用共同的属性和方法 提高了对象的使用效率
原型链:
获取对象属性时 如果对象没有这个属性,就会去他的原型proto上去找
如果还查找不到 就去找原型的原型 一直到最顶层(Object.protype)为止
这里需要注意的是Object是属于原型链顶层,所有构造函数的protype都指向Object.protype
17. 继承
ES5共有6种继承
-
第一种:盗用构造函数
设两个构造函数,各自都创建一些成员属性,
将父类构造函数的this指向强行修改为子类构造函数的this指向
这样一来子类构造函数所创建出来的实例化对象会拥有两个构造函数共同的成员属性。
缺点:无法继承原型上的属性和方法,构造函数的的代码不简介
function Parent() { this.name = "parent"; this.age = 22; } Parent.prototype.aa = 2; function Child() { Parent.call(this);将副类的this指向强行修改为当前的this指向 this.type = "child"; } console.log(new Child()); -
第二种:原型链继承(原型继承)
设两个构造函数,各自都创建一些成员属性,
将父类构造函数的实例赋值给子类构造函数的原型上面
子类所创建出的实例原型拥 有父类的成员属性包括原型
缺点:由于原型链是所有实例的公共祖先,所以容易引发当原型更改,实例全部更改的结果
function Parent1() { this.name = "parent1"; this.play = [1, 2, 3, 4]; } Parent1.prototype.aa = 1; function Child1() { this.type = "type"; } Child1.prototype = new Parent1(); let s1 = new Child1(); let s2 = new Child1(); // console.log(s1.play,s2.play) s1.play.push(5); console.log(s1.play, s2.play); -
第三种:组合继承(构造函数继承 + 原型继承)
设两个构造函数,各自都创建一些成员属性,
将父类构造函数的this指向强行修改为子类构造函数的this指向
将父类的实例(或者父类的原型)赋值给子类的原型上面
子类构造函数创建出的实例对象既有父类的成员属性,原型上也有父类原型的属性
对成员属性做出修改 不会影响到其他实例的成员属性
缺点:父类被调用两次,浪费资源
function Parent3() { this.name = "parent3"; this.play = [1, 2, 3, 4]; } Parent3.prototype.aa = 1; function Child3() { Parent3.call(this); this.type = "type"; } Child3.prototype = new Parent3(); //原型链继承 let s11 = new Child3(); let s22 = new Child3(); console.log(s11); s11.play.push(5); console.log(s11.play, s22.play);
4.第四种:原型式继承
5.第五种:寄生式继承
6.第六种:寄生式组合继承!
(1)利用Object.create()创建一个对象,将父类的原型赋值给新对象,并将此对象给子类原型
(2)将子类的构造函数更改回自己
需要做的是子类的构造函数指向子类,原型指向父类
设两个构造函数,各自都创建一些成员属性,
将父类构造函数的this指向强行修改为子类构造函数的this指向
利用Object.create()创建一个对象
将父类的原型赋值给新对象,并将此对象给子类原型
但是这样做子类实例中的构造函数是父类本身
需要将子类本身重新赋值给子类原型的构造函数
这样子类实例的构造函数指向子类本身
function Parent4() {
this.name = "parent4";
this.play = [1, 2, 3, 4];
}
Parent4.prototype.aa = 1;
function Child4() {
Parent4.call(this);
this.type = "type";
}
Child4.prototype = Object.create(Parent4.prototype); //原型链继承
Child4.prototype.constructor = Child4;
let s111 = new Child4();
let s222 = new Child4();
console.log(s111.__proto__.constructor);
console.log(s111);
s111.play.push(5);
console.log(s111.play, s222.play);
继承概括:
//es5 类其实就可以认为是构造函数
function Animal() {
this.name = "name";
}
let dog = new Animal();
console.log(dog);
//es6
class Animal1 {
constructor() {
this.type = "type";
}
}
let cat = new Animal1();
console.log(cat);
ES6中的Class类:
一句话概括:Class类是基于es5的构造函数+原型的继承关系封装的,可以实现封装,多态和继承
(详情见ES6的Class)
Vue面试题
1.vuex为啥mutations不能异步,只能同步
原因是state的数据是实时更新的,在mutations进行异步操作的时候这个过程还没执行完,可能state在其他地方被修改了。
因为state是实时更新的,mutations无法进行异步操作,而如果直接修改state的话是能够异步操作的,当你异步对state进行操作时,还没执行完,这时候如果state已经在其他地方被修改了,这样就会导致程序存在问题了。
2.v-model原理
v-model 原理是 :value和@input="handleChange"绑定的一个语法糖
如果在组件上使用v-model,那么子组件中接收的数据名字需要定义为value, 触发的事件为input
3. vue是什么以及vue的优点
vue是渐进式的前端框架,单项数据流
1:轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十kb; 2:双向数据绑定 3:组件化开发 4:视图,数据,结构分离 5:虚拟DOM 6:运行速度更快
4. 虚拟DOM和真实DOM的区别?虚拟 DOM 是什么? 有什么优缺点?
说说你对虚拟DOM的理解:
我对虚拟DOM的理解:现在所有的框架几乎都引进了虚拟Dom来对真实Dom进行抽象,在我们频繁操作DOM的同时,虚拟DOM也会进行频繁修改,然后一次性比较并修复真实DOM中需要修改的部分,然后再真实的DOM中达到只渲染局部,从而减少过多DOM节点排版与重绘的消耗。
文档对象模型,它是一个结构化文本的抽象(Document Object Model)
由于在浏览器中操作 DOM 是很昂贵的。频繁的dom操作容易引起页面的重绘和回流,所以在vue中将真实的DOM节点通过特定的方法将它抽离成⼀个虚拟的DOM树,这个虚拟的DOM树就是虚拟DOM。数据更新的时候,与上一次得到的数据进行diff算法对比,最后在真实DOM中只渲染得到的新的数据,实现同步更新。
优缺点:
优点:
1.保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产⽣的操作,它的⼀些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;
2.⽆需⼿动操作 DOM: 我们不再需要⼿动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,
3.跨平台: 虚拟 DOM 本质上是 JavaScript 对象,⽽ DOM 与平台强相关,相⽐之下虚拟 DOM 可以进⾏更⽅便地跨平台操作
缺点:
⾸次渲染⼤量 DOM 时,由于多了⼀层虚拟 DOM 的计算,会⽐ innerHTML 插⼊慢
2.Vue的diff算法原理是什么?
Vue的diff算法是平级⽐较,不考虑跨级⽐较的情况。内部采⽤深度递归的⽅式+双指针⽅式⽐较
1、先⽐较两个节点是不是相同节点
2、相同节点⽐较属性,复⽤⽼节点
3、先⽐较⼉⼦节点,考虑⽼节点和新节点⼉⼦的情况
4、优化⽐较:头头、尾尾、头尾、尾头
5、⽐对查找,进⾏复⽤
3. MVVM模式的优点以及与MVC模式的区别?
MVVM模式的优点:
1、低耦合: 视图(View)可以独⽴于 Model 变化和修改,⼀个 ViewModel 可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
2、可重⽤性: 你可以把⼀些视图逻辑放在⼀个ViewModel⾥⾯,让很多 view 重⽤这段视图逻辑。
3、独⽴开发: 开发⼈员可以专注于业务逻辑和数据的开发(ViewModel),设计⼈员可以专注于⻚⾯设计。
4、可测试: 界⾯素来是⽐较难于测试的,⽽现在测试可以针对ViewModel来写。
MVVM 和 MVC 的区别:
mvc 和 mvvm 其实区别并不⼤。都是⼀种设计思想。
主要区别
mvc 中 Controller演变成 mvvm 中的 viewModel,
mvvm 通过数据来显示视图层⽽不是节点操作。
mvvm主要解决了:
mvc中⼤量的DOM 操作使⻚⾯渲染性能降低,加载速度变慢,影响⽤户体验。
4. MVVM中,v,m,vm分别是什么?说一下什么是MVVM模式
视图模型双向绑定,是Model-View-ViewModel的缩写
在vue中,mvvm模式分别是模型层(m)、视图层(v)和ViewModel视图模型(vm)
什么是MVVM模式?
MVVM是把MVC的Controller【肯戳乐】和MVP的Presenter【婆森特】改成了ViewModel。
View变化会自动更新到ViewMode,ViewModel的.变化也会自动同步到View上显示,这种自动同步是因为ViewModel中的属性实现了Observer【阿婆涩玩儿】观察者,当属性变更时都能触发对应的操作
属于是数据驱动视图
Vue底层实现原理
Observer(数据监听器) : Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher【窝池儒】
Watcher(订阅者)【窝池儒】 : Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个update()方法
- 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
Compile(指令解析器) : Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图
4. vue常⽤的修饰符有哪些?
.trim 【踹木】去除⾸尾多余的空格
.stop 阻⽌事件冒泡
.once 只渲染⼀次
.self【少妇】 事件只作⽤在元素本身
.number 将值转化为number类型
.capter【卡普特】 组件之间捕获
.prevent 【陪万特】阻⽌元素的默认⾏为
.native 【内特有】 事件穿透,让我们可以在⾃定义组件上定义事件和⽅法
5. vuex的概念?执⾏机制是什么?怎么使用?
vuex的概念:
vuex是采用集中式状态管理的一个Vue插件,对于vue应用的多个组件进行集中式的状态管理,是一种组件之间通信的方式,并适用任意组件之间的通信
执行机制:
我在项⽬当中如果要改变state的状态,我们⼀般是在组件⾥⾯调⽤this.$store.dispatch⽅式来触发actions⾥⾯的⽅法,在actions⾥⾯的⽅法通过commit来调⽤mutations⾥⾯定义的⽅法来改变state,同时这也是vuex的执⾏机制
使用方法分为两种
基本使用的方式读取vuex的数据:
this.$store.state.数据名 读取state的数据
this.$store.dispatch('方法名',要传递的数据) 读取actions的方法
this.$store.getters.数据名 读取gitters的数据
this.$store.commit('方法名',要传递的数据) 读取mutations的方法
使用映射函数读取vuex的数据:
这两种写在computed中
...mapState方法映射state的数据
...mapGetters方法映射getters中的数据
这两种写在methods当中
...mapActions方法映射actions的数据
...mapMutations方法映射mutations的方法
Vuex的注意事项:
states => 基本数据
getters => 通过基本数据做出计算再返回出的新数据
在Actions中可以异步操作,可以进行逻辑处理,不可以操作state中的数据
在mutations中可以进行同步操作,可以直接操作state的数据,尽量不要写逻辑代码
在vc中如果没有一些逻辑操作,以及异步操作就可以直接使用commit调用mutations中的方法
modules => 模块化Vuex
6. Vue2双向数据绑定原理?/数据代理/响应式原理(数据劫持)
说一说你对Vue响应式理解?
1.双向绑定是vue的特色之一,开发中必然会用到的知识点。
2.所谓数据响应式就是能够是数据变化的时候做出响应的机制。
3.MVVM框架中要解决的核心问题是连接数据层和视图层,通过数据驱动应用,数据变化,视图更新,要做到这点就需要对数据做响应式处理,一旦数据发生变化就可以立即做出更新处理。
4.以vue实例说明,通过数据响应式加上虚拟Dom和patch算法,开发人员只需要操作数据,关心业务,完全不用接触繁琐的DOM操作,从而大大提升开发效率,降低开发难度。
patch算法【趴吃】
patch过程是一个递归过程,遵循深度优先、同层比较的策略,本质是将新旧vnode【虚拟节点】进行比较,创建、删除或者更新DOM节点/组件实例。
数据劫持:Object.definePropert方法有三个参数分别是:
参数一:要修改或添加的对象名
参数二:要修改或者添加的键名
参数三:一个对象,对象中有get获取方法这个方法需要去return返回一些内容,还有set设置方法,默认参数是做出改动的内容。
vue.js 则是采⽤ 数据劫持 结合 发布者-订阅者 模式的⽅式,
vue采用数据劫持 object.defineProperty()来劫持各个属性的setter ,getter,结合 发布者-订阅者 模式的方式
在数据变动时候发布消息给订阅者 触发相应的监听回调
完成数据数据的双向绑定
6/1. Vue2.0响应式的缺点
当Object.defineProperty劫持的是整个对象或者数组,它是无法监听或检测到数组或原型上面的方法比如push,unshift这种修改数组长度的方法都无法检测到
解决方法:重写数组或者对象原型上的方法;
如果劫持的是对象或者数组中的某一个元素或属性,可以使用递归
当我们遇到这种情况的时候,比如在data属性中没有这条属性的时候,要发生更改可以用this.$set()
Vue3中使用的是Proxy 创建对象的代理
6/2. this.$set()方法
语法:
this.$set(对象名,键名,键值)
更新同步数据 常在双向数据绑定不能够及时相应到视图当中使用
8. 怎样理解 Vue 的单向数据流
父子组件 有严格的嵌套关系,以及数据传递关系,父组件可以自上下的将数据给子组件,反之,子组件不可以随意更改父组件的数据,只能通知父组件对原始数据进行更改,这就是我理解的单项数据流。
数据总是从⽗组件传到⼦组件,⼦组件没有权利修改⽗组件传过来的数据,只能请求⽗组件对原始数据进⾏修改。这样会防⽌从⼦组件意外改变⽗级组件的状态,从⽽导致你的应⽤的数据流向难以理解。
注意:在⼦组件直接⽤ v-model 绑定⽗组件传过来的 prop 这样是不规范的写法 开发环境会报警告**
如果实在要改变⽗组件的 prop 值 可以再 data ⾥⾯定义⼀个变量 并⽤ prop 的值初始化它 之后⽤$emit 通知⽗组件去修改
9. 组件写name有什么好处
1、增加name属性,会在components【肯抛藤s】属性中增加组件本身,实现组件的递归调⽤。
2、可以表示组件的具体名称,⽅便调试和查找对应的组件。
10. Vue内置组件keep-alive的理解,以及生命周期解读
keep-alive是vue的内置组件,我们都知道在浏览器中切换组件的时候,原页面的内容会默认进行销毁,这个组件的作用就是能在组件切换过程中将状态保留在内存中,防止重复渲染DOM,如果有需求,某个组件切换后,内容不进行销毁,而是保留之前的状态,比如刚刚填写好的表单数据,那么就可以利用keep-alive来实现。
- 有三个常用的动态参数include,exclude,max
include【阴科撸的】 - 字符串或正则表达式。只有名称匹配的组件会被缓存
exclude【A科撸的】 - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
max - 数字。最多可以缓存多少组件实例
keep-alive的生命周期:
受keep-alive的影响,组件都具有两个生命周期钩子函数分别是activated和deactivated,退出触发deactivated钩子函数,再次进入触发activated钩子函数
组件一旦被 缓存,那么再次渲染的时候就不会执行 created、mounted 等钩子函数。
10/1. keep-alive 原理
在具体实现上,keep-alive在内部维护了一个key数组和一个缓存对象
//keep-alive 内部声明周期函数
created () {
this.cache = Object.create(null)
this.keys = []
},
key数组记录目前缓存的组件key值,如果组件没有指定key值,会自动生成一个唯一的key值
cache对象会以key值为键,vnode为值,用于缓存组件对应的虚拟DOM
在keep-alive的渲染函数中,其基本逻辑是判断当前渲染的vnode是否有对应的缓存,如果有,会从缓存中读取到对应的组件实例,如果没有就会把它缓存。
当缓存的数量超过max设置的数值时,keep-alive会移除key数组中的第一个元素
10/2. Vue生命周期10个生命周期介绍
什么是生命周期?
每个Vue实例被创建后都会经历一系列初始化步骤,它需要数据观测,模板编译,挂载实例到Dom上,以及数据变化时更新Dom,这个过程统称为生命周期函数,以便用户在每个特定的阶段都有添加各自代码的机会。
vue2和vue3生命周期解读:
beforeCreate 创建前— setup
Create 创建后— setup
beforeMount 挂载前— onBeforMount【比佛盲特】
Mount 挂载后— onMount【盲特】
beforeUpdate 更新前— onBeforUpdate
updated 更新后— onUpdated
beforeDestroy 销毁前— onBeforUnmount【on比佛暗忙特】
destroyed 销毁后— onUnmounted【on暗忙忒特】
创建前/后(1.beforeCreate,2.created)只执行一次
beforeCreate 创建前 在这个钩子函数身上只有实例本身的一些钩子和事件,data中的数据是不能使用的。这个时候,数据还没有挂载呢,只是一个空壳,无法访问到数据和真实的dom,一般不做操作
created 创建完成 data中的数据已经能用了 这是最早可以使用data数据的钩子,一般可以在这里做初始数据的获取
挂载前/后(3.beforeMount,4.mounted)只执行一次
beforeMounte 挂载之前 在内存中已经形成dom树,但是还没有渲染到页面上
mounted 挂载完成 内存和页面的数据已经同步 是最早可以操作dom元素的钩子
更新前/后(5.beforeUpdate,6.updated)可执行0-无限次
beforeUpdate data中的数据被修改的时候调用 执行这个钩子的时候内存的数据是新的页面是旧的
updated 修改完成 页面和内存已经同步
销毁前/后(7.beforeDestroy,8.destroyed)只执行1次
beforeDestroy 销毁之前的组件 data中的数据还可以执行
destroyd 销毁之后的组件 浏览器中对应的DOM已被完全销毁
激活时/未激活时(9.activated【啊提V的】,10.deactivated【德啊提V德】)
路由退出触发退deactivated[的啊提V的]
当再次进入时,触发activated[啊提V的]
在项目开发过程中,在生命周期中做过哪些功能(优化)
如在mounted中请求数据,它需要一边渲染DOM数据,一边请求数据,降低页面性能。
所以在create里请求数据可以节省页面渲染DOM时间,只请求数据,增强页面性能。
11. Vue组件通信的方案
整理vue中8种常规的通信方案
-
通过 props 传递
-
通过 $emit 触发自定义事件
-
使用 ref
-
EventBus(非父子)
-
parent 【趴软T】或root【乳特】
-
attrs【啊特S】 与 listeners【李森讷】
-
Provide【pro外的】 与 Inject【阴杰克特】
-
Vuex
13. Vue如何二次封装axios(重点)
在vue.config.js中进行跨域,devServer【带V涩玩】中找到proxy属性在里面配置代理名称,代理地址,开启跨域和重写路径。
env.js【配置环境】开发环境development,线上环境productor,测试环境test
创建http文件夹,在期中在创建三个文件夹分别为:
api.js封装开发中需要使用的全部接口
request.js封装axios实例和请求拦截和响应拦截,再抛出实例。
request.js文件夹的axios流程:
下载axios,在文件中import引入,再通过axios.create创建实例赋值给一个常量。常量挂载拦截器和响应拦截器,最后再抛出这个常量。
请求拦截--常量.interceptors.request.use() 拦截机:【因特卡铺特斯】
响应拦截--常量.interceptors.response.use() 响应:【瑞斯棒斯】
14. vue里ref是什么
基本用法一般来获取本页面的Dom元素,ref进行设置,refs进行获取
也可以用来进行组件通信(拿到子组件中的data数据和去调用子组件中的方法)
this.$refs 获取了所有设置ref属性的元素(如有重名那么会被覆盖) 如果是普通的元素那返回的是一个普通的dom 如果ref放在vue组件里,那么返回的是一个vue对象,这个对象包含了这个组件的各种信息
16. 为什么需要路由懒加载?
因为Vue是属于单页面应用(SPA),在加载首屏的时候通常会挂载全部路由,使得首屏加载速度变慢,这时候可以通过路由的按需加载进行优化首屏加载速度,这种模式又称为路由的懒加载。
那如何实现路由的懒加载呢?
在配置路由的时候,我们可以通过箭头函数执行回调函数里的模块化来加载路由,从而达到路由的按需加载,只有函数调用的时候,才会加载对应的组件内容。
整个网页默认是刚打开就去加载所有页面,路由懒加载就是只加载当前点击的那个模块。
优点:按需去加载路由对应的资源,提高首屏加载速度
实现原理:路由相关的组件不再直接导入, 而是改写成异步组件的写法,只有当函数被调用的时候,才去加载对应的组件内容。
17. vue中内置的组件用过吗
比如:
router-view
router-link
transition
keep-alive
slot
19. mixin混入【密科思信】(重点!!!!!!)
理解:
将共用的功能代码以对象的方式存储在mixin中,提高代码复用性,又避免了多继承的复杂。
使用起来分为全局混入和局部混入:
全局混入在main.js文件中先import导入mixin再挂载到Vue.mixin(xxx),就可以全局使用了。
局部混入在需要使用的组件中导入mixin文件,再挂载到组件的mixin节点的数组中,就可以局部使用了。
需要注意的是:
1.当混入的数据和方法与组件中的属性和方法名字一样时,组件自身的属性和方法优先级更高。
2.当混入的生命周期和组件的生命周期一样的时候,两者都会保留,但混入的生命周期优先执行。
理解:
1.`Mixin`是**面向对象**[程序设计语言]()中的类,提供了方法的实现。其他类可以访问`mixin`类的方法而不必成为其子类
2.`Mixin`类通常作为**功能模块**使用,在需要该功能时“混入”,有利于**代码复用**又避免了**多继承的复杂**
3.`mixin`(混入),提供了一种非常灵活的方式,来分发 `Vue` 组件中的**可复用功能**。
4.本质其实就是一个js对象,它可以包含我们组件中**任意功能选项**,如data、components、methods、created、computed等等
5.我们只要将**共用的功能**以**对象**的方式传入mixins选项中,当组件使用 mixins对象时,所有mixins对象的选项都将被混入**该组件本身的选项中**来
**注意事项**
当组件存在与`mixin`对象有**相同选项**的时候,在进行**递归合并**时,组件的选项会覆盖`mixin`的选项
但如果相同选项为[生命周期钩子])的时候,会合并成一个数组,先执行`mixin`的钩子,再执行组件的钩子
使用场景
在日常的开发中,我们会遇到在不同的组件中经常会用到一些相同或者相似的代码,这些代码的功能相对独立,这时,可以通过`Vue`的`mixin`功能将相同或者相似的代码提出来
20. vue2和vue3的区别 自己挑个两三个简单的 能输出具体的区别
1.性能提升
更小巧,更快速;支持摇树优化。支持 Fragments 和跨组件渲染;支持自定义渲染器。
2.API 变动
除渲染函数 API 和 scoped-slot 语法之外,其余均保持不变或者将通过另外构建一个兼容包来兼容 2.x。
模板语法的 99% 将保持不变。除了 scoped-slot 语法可能会有一些微调之外变动最大的部分将是渲染函数 (render) 中的虚拟 DOM 的格式。
3.重写虚拟 DOM (Virtual DOM Rewrite)
随着虚拟 DOM 重写,减少 运行时(runtime(软太母))开销。重写将包括**更有效的代码来创建虚拟节点。
1. vue3可以在组件视图template中可以设置多个节点 而vue2就能一个
2. 旧的选项型API在代码里分割了不同的属性: data,computed属性,methods,等等。新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,`这样代码会更加简便和整洁`。
21.$nextTick()方法有什么作用
异步更新队列的方法,主要作用是等待元素加载完毕之后才会执行的回调函数,我们会在$nextTick方法里面获取dom元素。
应用场景:
-
在vue生命周期的create中进行Dom操作的一定要使用nextTick(),因为created执行的时候DOM还未进行任何渲染。
方法里面操作Dom,由于Dom元素还没有更新,因此打印出来的还是未改变之前的值,而通过this.$nextTick()获取到的值为dom更新之后的值。
3. 在使用某个第三方插件时 ,希望在vue生成的某些dom动态发生变化时重新应用该插件,也会用到该方法,这时候就需要在 $nextTick 的回调函数中执行重新应用插件的方法,例如:应用滚动插件better-scroll时
22. Vue中的数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作
当你把一个普通JS对象传入Vue实例作为data的时候
Vue先遍历这个对象的property【pro婆忒】
将使用Object.definePropety将这些property全部转换为get/set’
原理
Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
23. 什么是自定义指令?【德ruai科踢乌】
我们常见的v-if v-for都是内置指令,由Vue官方提供的 全部的指令都是由底层js逻辑来实现的,自定义指令也是一样 指令注册的方式和过滤器是一样的,都是分为全局注册和局部注册
1.全局注册:
全局有效
首先在src目录下创建directives文件
在文件夹中创建自定义指令的js文件,文件中导入Vue实例
再Vue.directive('名字',{配置配置项}),再导出
怎么使用呢?
在组件中先import导入对应的指令js文件
再去组件内部去注册它
注册之后,就可以开始使用
2.局部注册:
局部生效
在组件中在directives节点生命局部指令
v-xxx="{配置项}"
无论全局注册和局部注册在他们的配置项中都有三个常用的回调函数
(1)bind:指令与元素成功绑定时调用。
(2)inserted【in涩太的】:指令所在元素被插入页面时调用。
(3)update:页面数据发生变化触发。
当自定义指令挂载好,触发bind回调函数
然后如果我们对视图进行了操作就会触发update回调函数
三个回调函数都会有三个参数
(1)el:获取DOM元素本身
(2)binding:该自定义对象的相关信息
(3)vnode:虚拟节点
需要注意的
1.指令定义时不加v-,但使用时要加v-
2.指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
例:
局部自定义指令:
directives: {
color: {
bind(el, binding) {
el.style.color = binding.value;
},
},
},
24. 你知道vue中key的原理吗?说说你对它的理解?
key的作用主要是为了更高效的更新虚拟Dom,给每一个节点vnode添加唯一值
不定义的话对比过程比较低效,影响性能。
key是给每⼀个节点(vnode)的唯⼀id,
也是diff的⼀种优化策略,可以根据key,更准确更快的找到对应的节点(vnode)
grtyut
1.节省DOM渲染时间
2.提高页面性能 没有key的时候当部分DOM发生变化 他会降低diff算法的对比速度
3.key用ID去标识 如果用索引的话 当DOM节点添加删除做出改动的时候 会出现diff的对比不准确。
25. Vue中计算属性和监听属性区别与总结
计算属性computed【康姆Q腾特德】
在Vue使用computed来定义计算属性,每个计算属性就是一个函数,每个函数都需要一个返回值,返回结果就是通过已知属性计算得到的新属性,它只依赖于,计算出它的属性的变化而变化,如果依赖的属性没有变化,计算结果将从缓存中取出,属于多对一的关系
监听属性watch
在Vue中使用watch属性来定义一个监听器函数,watch没有缓存性,监听data中的属性,属性值只要发生变化就会立刻执行,属于一对多的关系,可以利用它的特性来做一些异步的操作如(函数防抖,Ajax异步获取数据,甚至DOM操作)
内部常用三个属性分别为:
(1) deep 布尔值控制开启深度监听
(2) immediate【一米date】 布尔值控制开启立即触发
(3) handler 需要执行的具体操作,函数是变化后的属性。
26. Vue中的路由守卫是什么?分为几种?都是什么?
路由守卫就是当我们进入页面跳转的时候会触发的钩子函数,我们把它称之为vue路由守卫。
Vue一共给我们提供了三种路由守卫,分别是
全局路由守卫,挂载到router实例上面的
组件内部守卫,这个是写在路由.vue里面的。
路由独享守卫,写在路由配置项中的。
全局路由守卫:
在全局路由守卫中有beforEach,beforeResolve,afterEach三个钩子函数
分别代表着跳转前触发的钩子函数,以及进入路由的时候,还有进入路由之后会触发的钩子函数。
这个几个钩子函数都有三个相同的参数to,from,next()分别是要进入的路由,离开之前的路由,next进入写一个路由。
路由独享守卫:
只有beforEnter()【比佛安特】
组件内部守卫:
beforeRouteEnter【比佛乳特安特】 进入路由前
beforeRouteUpdate 同一页面,刷新不同数据时调用
beforeRouteLeave【比佛乳特力v】 离开当前路由时
27. Vue路由有几种模式?有什么区别,路由重定向是什么redorect重定向【瑞第ruai特】
hash模式:地址栏的url有#,默认的是hash,不会包含在http请求中,对后端没有任何影响,因此改变hash不会重新加载页面
history模式:
history模式:HTML5中新增了pushState方法,该方法应用于浏览器的历史记录,提供了对历史记录进行修改的功能,使用黑丝腿瑞模式会对前端和后端的URl地址进行判断如果不一致会报404错误。
路由跳转方式
1. router-link
2. this.$router.push()
3. this.$router.replace()
4. this.$router.go()
5. this.router.back()
28. 为什么data属性是一个函数而不是一个对象?
如果在组件中data直接定义为一个对象浏览器会报错
所以只能是一个函数,⽬的是为了防⽌多个组件实例对象之间共⽤⼀个 data,产生数据污染,每次返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染
29.Vue异步更新队列
只需要将渲染操作推迟到本轮事件循环最后或者下一次事件循环。也就是说,只需要在本轮事件循环的最后,等前面更新状态的语句都执行完之后,执行一次渲染操作就,它就可以无视前面各种更新状态的语法,无轮前面写了多少条更新状态的语句,只在最后渲染一次就可以了
这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作上非常重要。
Vue在更新Dom时是 异步执行的。只要侦听到数据变化,Vue将开启一个队列,并缓存在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作非常重要。
微信小程序
1.微信小程序有几个文件
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述WXML的组件样式,js逻辑处理,网络请求json小程序设置,如页面注册,页面标题及tabBar。app.json必须要有这个文件,如果没有这个文件,项目无法运行,因为微信框架把这个作为配置文件入口,整个小程序的全局配置。包括页面注册,网络设置,以及小程序的window背景色,配置导航条样式,配置默认标题。app.js必须要有这个文件,没有也是会报错!但是这个文件创建一下就行 什么都不需要写以后我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。app.wxss配置全局css
2.微信小程序怎样跟事件传值2 微信小程序怎样跟事件传值
给
HTML元素添加data-*属性来传递我们需要的值,然后通过e.currentTarget.dataset或onload的param参数获取。但data -名称不能有大写字母和不可以存放对象
3.小程序的-wxss-和-css-有哪些不一样的地方3 小程序的 wxss 和 css 有哪些不一样的地方?
wxss的图片引入需使用外链地址- 没有
Body;样式可直接使用import导入
4.小程序关联微信公众号如何确定用户的唯一性
使用
wx.getUserInfo方法withCredentials为true时 可获取encryptedData,里面有union_id。后端需要进行对称解密
5.微信小程序与vue区别
- 生命周期不一样,微信小程序生命周期比较简单
- 数据绑定也不同,微信小程序数据绑定需要使用
{{}},vue直接:就可以 - 显示与隐藏元素,
vue中,使用v-if和v-show控制元素的显示和隐藏,小程序中,使用wx-if和hidden控制元素的显示和隐藏 - 事件处理不同,小程序中,全用
bindtap(bind+event),或者catchtap(catch+event)绑定事件,vue:使用v-on:event绑定事件,或者使用@event绑定事件 - 数据双向绑定也不也不一样在
vue中,只需要再表单元素上加上v-model,然后再绑定data中对应的一个值,当表单元素内容发生变化时,data中对应的值也会相应改变,这是vue非常 nice 的一点。微信小程序必须获取到表单元素,改变的值,然后再把值赋给一个data中声明的变量。
三 JavaScript面试题
0. const定义的数据可以修改吗?
const定义的数据是一个简单数据类型的时候,是不能修改的
但如果是一个复杂数据类型是可以修改的,
因为数据的存放是有栈,堆组成的
当存简单数据类型的时候,是存放再栈中
而对于复杂数据类型,是将它的地址存在栈内存中
真正的数据存放在堆内存当中
当我们的数据发生改变 并不会去修改栈的地址 所以const可以修改对象中的值
1. 什么是防抖和节流
本质是优化执行代码的一种手段
防抖:就是定义一个定时器,添加时间在定义的时间内只执行一次他的事件倒计时结束在执行
节流:防止额外的请求,就是定义一个变量判断是否在请求数据,请求之前开启节流阀 请求之后在关闭
防抖
形象的说就是防止抖动 例如:
(1) 一个搜索框 用户不停的输入内容(这个时候就是抖动的过程),等用户输入停止后,再触发搜索
防抖函数在每一次都有内容后进行清除定时器,是为了保证当前执行的函数就是当前规定的时间内执行的最后一次操作
节流
形象的或就是节省交互沟通 例如:
(1) 点击按钮请求ajax数据 多次点击就是请求多次数据 创建节流阀(布尔值控制)
进入请求关闭节流阀 请求完毕开启节流阀
重复点击进入判断 上一次请求未结束(节流阀==false)不能再次重复请求
区别:
防抖 保证当前执行的函数就是当前规定时间内执行的最后一次操作
节流 保证在规定时间只会执行一次操作
场景
节流:滚动加载更多、搜索框搜的索联想功能、高频点击、表单重复提交……
防抖:搜索框搜索输入,并在输入完以后自动搜索、手机号,邮箱验证输入检测、窗口大小 resize 变化后,再重新渲染。
2. JavaScript的数据类型都有什么?
基本数据类型:Number、String、Boolean、Null、Undefined
复杂数据类型:Object(Function、Array、Date、RegExp)
3. 什么是变量提升?(预解析)
变量提升是在JS代码执行前,浏览器会将带有var, function关键字的变量提前进行声明(值默认就是 undefined),定义 (就是赋值操作),这种预先处理的机制就叫做变量提升机制也叫预定义。
函数提升:
创建函数有两种形式:
一种是函数声明 function Fn(){}
另外一种是函数字面量 var ba = function(){}
只有函数声明才有变量提升
由此可见函数提升要比变量提升的优先级要高一些,且不会被变量声明覆盖,但是会被变量赋值之后覆盖!!
4.带 var 和不带 var 的区别?
(1)全局作用域中不带var声明变量虽然也可以但是建议带上 var声明变量,不带 var 的相当于给window对象设置一个属性罢了。
(2)私有作用域(函数作用域),带 var 的是私有变量。不带 var 的是会向上级作用域查找,如果上级作用域也没有那么就一直找到 window 为止,这个查找过程叫作用域链。
全局作用域中使用 var 申明的变量会映射到 window 下成为属性。
6.js检测数据类型五种办法
(1)typeof对于基本数据类型判断是没有问题的,但是遇到引用数据类型是不起作用的 直接返回object
【阴斯藤斯OF】
(2)instanceof可以用来判断数组和对象,返回布尔值,但不能用于基础数据类型。
【啃死欻客特】
(3)constructor来判断数据的类型,但是除了null、undefined,因为他们不是由对象构建。返回构造函数
【酷~】
(4)Object.prototype.toString.call();
任何类型都可以精准检测出来
返回的是一个中括号包裹的类型
(5)Object.getProtityoeOf()
获取原型,和想要的原型做对比
console.log(Object.getPrototypeOf([]) === Array.prototype);
// true
console.log(Object.getPrototypeOf({}) === Object.prototype); // true
7.说说你对闭包的理解?闭包使⽤场景?
我认为闭包是语言特性
函数嵌套函数的形式就是闭包
内部函数可以访问外部函数作用域中的变量,参数
变量或参数不会被垃圾回收机制回收
闭包的原理其实还是作用域。
优缺点:
优点:1.变量长期驻扎在内存当中
2.避免全局变量的污染
3.私有成员的存在
缺点: 缺点是由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在低版本IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除
适用场景: 防抖,节流,柯里化函数的使用。
一句话:闭包是指有权访问另一个函数作用域中变量的函数
7/5. 执行上下文
一些变量是在全局执行上下文中声明的。我们称之为全局变量
执行上下文是一个抽象的环境,JavaScript 代码在其中执行。当全局代码执行时,它将在全局执行上下文中执行,而函数代码将在函数执行上下文中执行。
每次只能有一个执行上下文处于运行状态,因为 JavaScript 是单线程语言,它由执行栈或者调用栈来管理。
执行堆栈是具有 LIFO(后进先出)结构的堆栈,只能从堆栈顶部添加或删除项目。
当前正在运行的执行上下文将始终位于堆栈的顶部,并且当当前正在运行的函数完成时,其执行上下文将从堆栈中弹出,并且指针将指向堆栈中位于其下方的执行上下文。
8. js 中的 栈堆
存储变量数据的方式分为栈和堆
简单数据类型存储在栈内存当中
引用数据类型的名字存储在栈内存当中,值存储在堆内存当中
栈内存会提供一个引用地址指向堆内存中的值.
(1)栈(stack)存储基本数据类型 栈可以自动释放内存
(2)堆(heap)存储复杂数据类型 需要手动释放内存
10. Js中的break,continue和return的用法以及区别
return 应用在函数中
函数的返回语句 返回的同时也将函数停止
break【布瑞科】 应用在循环中 输出结果:12345
立即结束循环,并退出循环(当前循环不再执行),进行下个语句执行
continue【肯踢牛】 应用在循环中 输出结束:1234578910
停止当前语句,并从头执行该语句
和break语句相似 不同的是他不是退出一个循环 而是开始循环的一次新迭代。
11. 数组,字符串方法
数组方法:
(1)合并数组:concat
(2)数组转字符串:join('') & toString()转换字符串","连接
(3)添加元素操作: push()尾部 和unshift()头部
(4)删除元素操作: shift() & pop()
(5)sort() 数组排序, 改变原数组
(6)reverse() 反转数组,改变原数组
(7)slice() 截取数组, 不改变原数组,第二个参数是下标
(8)splice() 更新数组,改变原数组,第二个参数是数字
(9)indexOf() & lastIndexOf() 获取满足条件内容的下标
(10) find() & findIndex() 根据函数内的判断返回找到的数组内的第一个元素。不改变原数组。 (es6新增方法)
(11)forEach()、map()、filter()、some()、every() 迭代方法,不改变原数组。
(12)reduce()、reduceRight() 归并方法,不改变原数组
(13)keys()、values()、entries() 遍历数组方法,不改变原数组。 (es6新增方法)
(14)includes() 判断是否包含某一元素,返回true或者false表示是否包含元素,对NaN一样有效。不改变原数组。 (es6新增方法)
字符串方法:
(1)length 表示字符串长度
(2)charAt() 返回指定位置的字符串
(3)concat()【空砍特】 拼接两个或者多个字符串
(4)split() 字符串转换数组 [...str]也可以转换为数组 不改变原字符串
(5)lastIndexOf()& indexOf() 自右向左查找和自左向右 返回满足条件内容的下标 不满足返回-1
(6)substr() 字符串截取 参数一index,参数二Number 不改变原内容
(7)substring() 字符串指定位置截取 俩参数都是index 不改变原内容
(8)slice() 截取 参数一index,参数二Number 不改变原内容
(9) toLowerCase() & toUpperCase()大写字母转换小写,小写字母转大写字母
(10)字符串转数字:parseInt转整数、parseFloat转小数 不改变原内容
(11)replace() 替换 两个参数 1是要替换的元素 2是新字符串
(12)startsWith()和endsWith()表示该字符串参数是否在某个字符串头部和尾部,返回布尔值
every和some的区别
some是只要有一个内容满足条件才能返回true 没有返回false
every是全对才对,一个错就错了
12. 创建对象的三种方式
(1)创建对象的三种方式:
①var obj = {} 字面量
②New Object() 构造函数方式
③Object.create() 对象方法创建
(2)Object.entries(obj): 把对象转成键值对的数组
(3)Object.definedProperty() 监听对象属性的变化,vue2的数据响应式原理。
(4)Object.assign() 合并对个对象为一个对象
(5)Object.values() 把对象的值序列化为数组
(6)Object.keys() 把对象的属性名序列化为数组
13.JavaScript的基本规范?
(1)不要在同一行声明多个变量
(2)使用 ===或!==来比较true/false或者数值
(3)switch必须带有default分支
(4)函数应该有返回值
(5)for if else 必须使用大括号
(6)语句结束加分号
(7)命名要有意义,使用驼峰命名法
14. Javascript本地存储的⽅式有哪些?区别及应⽤场景
答: javaScript 本地存储(也称本地缓存)的⽅法我们主要讲述以下四种:
一 cookie:在每次请求中都会被发送,如果不使⽤ HTTPS 并对其加密,其保存的信息很容易被窃取,导致安全⻛险。举个例⼦,在⼀些使⽤ cookie 保持登录态的⽹站上,如果 cookie 被窃取,他⼈很容易利⽤你的 cookie 来假扮成你登录⽹站
二 sessionStorage:⼀旦⻚⾯(会话)关闭, sessionStorage 将会删除数据
三 localStorage:HTML5 新⽅法,持久化的本地存储,除⾮主动删除数据,否则数据是永远不会过期的
四 indexedDB:
15.什么是window对象? 什么是document对象?
(1)window对象代表浏览器中打开的一个窗口。
(2)document对象代表整个html文档。实际上,document对象是window对象的一个属性。
17.JS垃圾回收机制?
在说垃圾回收机制前,要弄清除JS中的垃圾是什么:
我们先从外行人看一下垃圾的定义(人们不再需要的物品),在JS中也是一样。不再被需要的变量就是垃圾。
标记清除:
在Js中,最常用的垃圾回收机制就是标记清除:当变量进入执行环境,被标记为进入环境,当变量离开执行环境时,会2被标记为离开环境。垃圾回收器会销毁那些带标记的值并回收它们所占用的内存空间。
引用计数:
查找引用浏览器不定时去查找当前内存的引用,被引用一次就+1,移除就-1,减到0时,浏览器会回收它。
内存泄漏:
在Js中,常见的内容泄露主要有四种,全局变量,闭包,DOM元素的引用,定时器。
1.意外的全局变量::不使用变量声明的aa=1的变量,JS就会默认将他变成全局变量,这样在页面关闭之前都不会被释放
**2.dom清空时,还存在引用:**DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的
**3.定时器中的内存泄漏:**如果没有清除定时器,那么就不会被释放
**4.不规范地使用闭包:**相互循环引用.这是经常容易犯的错误,并且有时也不容易发现.
哪些变量是不被需要的呢?我通过分类来简单介绍一些
全局变量:在全局作用域下声明的变量都不是垃圾,因为你有可能再次使用到,如声明一个全局变量,当时没有使用到,在1000行代码后使用到了这个变量,那么这个变量就有被使用的可能,这样的变量不回被垃圾回收机制回收
局部变量:如果在函数中声明变量,这个函数多次执行,且每次变量都不会回收的话,是不是会产生很多变量,这明显不合理。所以JS中有这样的机制:在函数中的变量,一旦函数退出,会将局部变量当作垃圾清除。
单引用,双引用,环引用
总结:
-
全局变量:全局变量存在被使用的可能性,所以不能当做垃圾
-
局部变量:函数执行完之后变量当作垃圾清除
-
单引用,双引用,环引用,这三种情况下,只能将所有指向该变量的对象清除才能够将变量作为垃圾处理
垃圾回收算法:
一:这是最基本的垃圾扫除算法,首先我们从global也就是window出发,开始去找它每一个箭头,然后找到所有能根据箭头找到的变量,我们给这样的变量一个:√。其他找不到的标记为:×。最后清除所有为×的变量
这个基础算法有很多的缺点:
- 标记的效率太慢了,我们的垃圾回收需要时时刻刻去遍历检查每一个引用,如果你的js代码中对象太多,会导致非常严重的效率问题
- 浏览器在标记的过程中js是不能执行的,我们不能让垃圾回收拖慢我们js运行的时间
这里针对标记扫除有三个优化点:
- 分代垃圾回收:我们可以将对象分为新生代和老生代,两者我们采用不同的回收策略。比如说window对象,就像个老祖宗一样,你这些后代是不是要时常像老祖宗请教?所以我们对于老一代可以回收的不那么勤快,隔几秒钟去遍历一次就行啦。对于新生代,我们需要马上标记马上回收。
- 增量执行:不会一次就全部遍历完,可能一次遍历个十分之一,提高性能
- 空余时间执行,比如我可以在你js执行完了之后再执行,不会影响到js的执行效率
二:引用计数,我们使用计数器在每个对象被新建,引用,删除引用的时候更新计数,如果计数器的值为0则直接删除,这种方法的优点很明显,暂停时间短
优点:
- 可即时回收垃圾
- 最大暂停时间短
- 不需要去沿指针去一遍遍的找
缺点:
- 计数器的增减处理任务繁重
- 计数器需要占位
- 实现起来很繁琐,没个赋值操作都得替换为引用更新操作
- 循环引用的无法回收
(1)标记清除:这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
①这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象。从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
(2)引用计数:这是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。该算法有个限制:无法处理循环引用。两个对象被创建,并互相引用,形成了一个循环。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
18. null,undefined的区别?
(1)null表示一个对象被定义了,但存放了空指针,转换为数值时为0。
(2)undefined表示声明的变量未初始化,转换为数值时为NAN。
(3)typeof(null) -- object;
(4)typeof(undefined) -- undefined
19.请描述一下 cookies,sessionStorage 和 localStorage 的区别?
(1)共同点:都是保存在浏览器端,且同源的。
(2)区别
①cookie 数据始终在同源的 http 请求中携带(即使不需要),即 cookie 在浏览器和服务器间来回传递。
②而 sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。
③cookie 数据还有路径(path)的概念,可以限制 cookie 只属于某个路径下。
④存储大小限制也不同,cookie 数据不能超过 4k,同时因为每次 http 请求都会携带 cookie,所以 cookie 只适合保存很小的数据,如会话标识。
⑤sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。
⑥数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie 只在设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭。
⑦作用域不同,sessionStorage 在不同的浏览器窗口中不共享,即使是同一个页面;cookie 和 localStorage 在所有同源窗口中都是共享的。
21. 原型链--函数与对象的关系
(1) Function函数是所有函数的祖先函数
(2) 所有构造函数都有一个prototype属性,叫原型对象,也称为显式原型
(3) 所有原型对象都有一个constructor属性,指向被new的构造函数
(4) 被new出来的对象叫实例对象,它们都有一个__proto_对象,叫隐式原型,它指向构造函数的prototype对象
(5) 原型链就是当从对象上获取属性的时候,它的查找顺序先从对象自身找,如果没有就沿着__proto__一直找,这个寻找过程叫原型链
23. JavaScript中的toString方法详解
toString() 方法返回一个表示该对象的字符串
每个对象都有一个 toString() 方法,toString() 方法被每个 Object 对象继承。
24. Js数据类型转换 (隐式转换,显示转换)
Js中的数据类型转换有两种 隐式转换,显示转换
显示转换:(手动进行强制转换)
Number(),String(),parselnt(),paeseFloat()
隐式转换:
20 - '2' = 18 === 20 - Number('2') = 18
'1' == 1 true
a = '10'; i++; i=11
25. 柯里化概念
JavaScript中的柯里化是什么?
柯里化简单地说就是计算具有多个参数的函数,并将它们分解为具有单个参数的函数序列
换句话说:
柯里化是一种函数的转换思想
比如我想实现一个多参数函数的简捷式编程
就可以用到柯里化
如Fn(1,2,3)可以利用闭包的思想,每次接受一个参数,返回一个继续接受参数的函数
即Fn(1)(2)(3)
这种转换即为柯里化
优点说明:
为啥要用柯里化?/..022...////////
可以把函数式编程变得简洁,没有冗余【rong】。
可以将函数作为返回值输出,提前返回。
一个简单累计数实例:
// 非柯里化
const add = (a, b, c) => {
return a + b + c
}
console.log(add(2, 3, 5)) // 10
// 柯里化
const addCurry = (a) => {
return (b) => {
return (c) => {
return a + b + c
}
}
}
console.log(addCurry(2)(3)(5)) // 10
以上实现柯里化更简便的方法是,使用 ES6 的箭头函数,它可以帮助我们编写更少的代码:
const addCurry = (a) => (b) => (c) => a + b + c
console.log(addCurry(2)(3)(5)) // 10
26. 纯函数
一个函数的返回结果只依赖其参数,并且执行过程中没有副作用。
纯函数满足的的条件:
相同的输入总会返回相同的输出
不会产生副作用
不依赖外部状态
27. v-if和v-for哪个优先级更高
在vue2中v-for的优先级高于v-if,先执行循环再判断条件,每次渲染的时候遍历整个列表,比较浪费。每次循环都遍历一次v-if,浪费资源
vue3中则相反v-if的优先级高于v-for,所有在v-if执行的时候,它调用的变量还不存在,就会导致异常
28. async捕获不了报错信息,你是如何解决?try
利用原生Js的try【踹】语句进行抛出异常。
首先执行try包裹的代码块,根据代码块执行结果判断是否进入catch的代码块,如果catch的代码块的代码块有返回结果,则抛出异常时会覆盖try语句的执行结果。
try里面是正常逻辑
chtch里是错误信息 。
29.如何判断对象是否为空?
一:JSON.stringify()转换成字符串 === '{}'
二: Object.keys(对象名).lenght === 0
四 es6新增哪些特性?
ES全称ECMAScript
1.Es6的class类?
(1)Class类是基于es5的构造函数+原型的继承关系封装的,可以实现封装,多态和继承。
(2)类有constructor函数作为初始化类时调用的第一个构造函数
(3)类使用extends【欸克斯叹词】实现类之间的继承
(4)super【苏pei】方法 实现子类获取父类元素
(5)abstract【安特死抓科特】 可以定义抽象父类 自己当爸爸 定义抽象方法在抽象类中,子类被絮对抽象方法重写
2. 模板字符串
(1) 使用一对反引8百号包裹字符串,使用${}放置变量
(2) 优点:可以多行字符串换行书写,可以使用变量和表达式,不用用+拼接了
3. 解构赋值简介
(1) 解构赋值:按照一定的模式,从数组 / 对象中提取值,并对变量进行赋值
(2) 数组解构按位置解构
(3) 对象解构按属性名解构
4. new Set方法
Set是ES6提供的一种数据结构,它和数组很像,但是它里面的数据是不可重复的
- 初始化 var newArr = new Set(arr)
- 添加数据 newArr.add(8848)
- 删除数据 newArr.delete(8848)
- 包含数据 newArr.has(8848)返回布尔
- 清除数据 newArr.clear()
5. 函数重载
(1) 函数名相同,函数的参数不同 (包括参数个数和参数类型),根据参数的不同去执行不同的操作。
6. js 函数尾调用
(1) 在函数的最底部发起另一个函数的调用就是函数尾调用
(2) 目的时为了节约性能
(3) 一个函数返回的是另一个函数的调用结果,那么就被称为尾调用
7. 链式调用
(1) 在构造函数中创建方法时,return this 返回当前调用方法的对象,可以实现链式调用方法
8. 箭头函数的this指向,箭头函数和普通函数的区别
主要是用来改变this的指向,就是说如果创建一个构造函数,我们在里面再加一个函数,它们两个的this指向不同,第一个指向实例的本身,而第二个默认指向window
因为箭头函数有一个特性,就是不绑定this,会捕获其定义时所在的this指向作为自己的this
**箭头函数和普通函数的区别**
1. 普通函数存在着变量的提升,箭头函数没有
2. 普通函数的this指向,谁调用指向谁,箭头函数是在哪定义就指向谁
3. 普通函数可以当成构造函数,而箭头函数是不可以的
4. 箭头函数没有arguments,要接受所有的参数用...rest
10. 面向对象与面向过程
程序设计语言中的编程思想分为:面向对象,面向过程
(一),面向过程:
就是指完成某个需求的时候,先分析出完成该需求时所需要经历的步骤有哪些,然后按照步骤依此执行,最终实现我们想要的效果。这种编程思想就是面向过程。
(二),面向对象
就是在完成某个需求的时候,先分析完成该需求所需要涉及的对象有哪些,然后找出这些对象所具有的属性和方法,利用这些属性和方法帮助我们完成需求。这种编程思想就是面向对象。
面向对象的特征:封装,继承,多态
关于面向对象的概念
1. 面向对象和面向过程是一种编程的思维
面向过程强调的是步骤 面向对象强调的是封装
2. 什么是对象
1. 万物皆为对象(笼统的概念)
对象是具体的实例 实例是可以拿过来直接干活的
3. 对象与类之间的关系
类:具有相同的属性(特性 变量)和方法(能干啥啊)对象的集合
一个类可以有n个对象 每个对象之间都是相互独立的
15. token值有什么作用?怎么生成和使用?会过期吗?怎么处理?
token的作用:
1.保存用户私人信息 密码
2.保存用户角色
3.在请求的时候可以携带token,达成和后端进行权限交互
token的生成和使用:
1.token一般是由后端启用,用户在登入的时候向后端发送请求接口,拿到该角色的token,作为个人信息。
2.利用token存储在vuex和本地,达到系统的鉴权。
分发并解析token:
登入成功后,得到token值,存储到本地,请求拦截器中将token挂载到请求头当中headers.Authorization【奥福ruai贼神】,利用jwt-decode【抵扣的】解码器解析存储的token值,开发中要考虑到业务扩展和代码的健壮性,再将解析好的token通过dispatch调用vuex中的actions异步方法,在异步方法里使用commit调用mutations中的方法,最后在mutations方法中将解析好的token赋值给先准备好state空对象,最后再分发给各个组件中使用。数据持久化可以在app.vue的create钩子函数调用vuex方法每次刷新页面都可以刷新token。
过期如何处理?
token有失效时间,在请求的响应拦截器中去判断状态码是否为401,如果是401 token失效,清空本地存储的token,弹出警告框告知用户并跳转到登录页面重新登录。
16. 动态路由的实现方式,怎么获取传过来的动态参数?
在路由表中使用动态字段来实现(占位符/:id)
路由守卫进行判读是否传值。
后端会根据当前登录人的角色返回相应的权限菜单,我们会在全局路守卫中进行判断,如果当前进入的路径在这个权限菜单中,则可以跳转,如果不在的话我们就跳转到首页中
18. html5和css3新增加了哪些
css3新特性
1. 选择器
2. 背景和边框
3. 文本效果
4. 2D/3D 转换 — 变形(transform)、过渡(transtion)、动画(animation)
5. 媒体查询 @media
6. 弹性盒子flex
h5新增特性:
1. 语义化标签:header、footer、section、nav、aside、article
增强型表单:input 的多个 type
新增表单属性:placehoder、required、min 和 max
音频视频:audio、video
canvas 画布 kan wa si
地理定位
拖拽
本地存储:
localStorage 没有时间限制的数据存储;
sessionStorage, session 的数据存储,当用户关闭浏览器窗口后,数据会被删除
新事件:onresize、ondrag、onscroll、onmousewheel、onerror、onplay、onpause
WebSocket:建立持久通信协议
19、新增了let const关键字
**let var const的区别**
- let 是代码块有效 var是全局有效
- let 是不能重复声明的 var是可以多次声明
- let不存在变量的提升 var存在变量的提升
- const(肯斯特)存储简单数据类型存储的是常量
20、es6新增了模块化
根据功能封装模块 通过import导入 ,可以使用 export 导出也可以使用export default导出
export 和 export defualt的区别
1. export 可以导出多个属性或者方法 ,需要用{}括起来 在用import接受的时候也得用{}接受
1. export default是以整体的方式抛出,接受的时候只接一个
22、es6新增哪些特性?
1. es6新增了promise(标题) 需要说什么是promise
2. es6新增了模块化的导入(impory)导出(export default,export) 需要说什么是模块化
3. 新增了class关键字 需要解释
4. 新增了箭头函数 再说箭头函数与普通函数的区别
5. 新增了解构赋值 需要解释什么是解构赋值
6. 新增了let const关键字 需要说let const var的区别
7. 新增了简单数据类型symbol
最后一定要加上我平时常用的就这些。
24、说一下js的事件机制(事件流)事件代理
js中存在两种事件机制,
1.冒泡事件机制
2.事件捕获机制
冒泡事件机制是先触发事件的的直接元素,然后向外扩散就像冒泡一样
捕获型就是事件从外向里执行。
js中的事件监听addEventListener的第三个参数默认的为false 是冒泡 为true是捕获
我们可以通过event.stopPropagation(泡捕gay神)来阻止了冒泡,捕获的进一步传播
25、rem布局的原理
1rem的大小就是根元素<html>的font-size的值,通过设置 根元素<html>的font-size的大小,来控制整个html文档内的字体大小、元素宽高、内外边距等
26、如何实现响应式布局
响应式布局可以让网站同时适配不同分辨率和不同的手机端,让客户有更好的体验
响应式布局实现的方案:
1. 百分比布局
2. 媒体查询
3. rem布局
4. vw vh布局(视口高宽的单位)
5. flex弹性盒布局
27.0 路由注意点
- 路由组件通常存放在views中,组件放在components文件夹
- 通过切换,销毁了路由组件,需要的时候再去挂载。
- 每个组件都有$route属性,存储这个路由对象信息
- 整个项目只要一个router,通过组件的$router属性获取
27.1 < router-link >的push和replace属性都是干什么的?
压栈push和replace
浏览器的历史记录有两种写入方式:
分别为push和replace,
-
push是追加历史记录,路由跳转时候默认为push换当前记录。
2.
replace是替换当前历史记录。
27、¥route和$router的区别
$router是VueRouter对象包含了全部的路由配置表,模式
每个组件都有自己的$route属性,里面存储自己的路由信息
$route是一个跳转的路由对象,每一个路由都会有一个$route对象,是一个局部的对象,可以获取对应的name,path,params,query等
$router是VueRouter的一个对象,通过Vue.use(VueRouter)和Vue构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他包含了所有的路由,和许多关键的对象、属性。
路由也可以通过name跳转(命名路由)
28、params和query传参的区别
1. params传值的参数是路由的一部分,所以跳转必须加参数值才能跳转 query传参和路由配置没有关系
2. 获取方式是不一样的 query this.$route.query.参数名 params是 this.$route.params.参数名
跳转方式1
跳转方式2
29、mpa和spa的区别
mpa 多页面应用 首屏幕
一套系统有多个页面组成,页面之间的切换是由a标签的href属性和script的location.href来实现的
spa是单页面应用 首屏加载慢
一套系统就由一个页面来承载,数据的切换是由路由来实现
mpa与spa的优缺点:
- 对于切换来说,路由的切换肯定比页面的切换更顺畅 所以用spa的切换好
- spa的首屏加载慢,mpa的首屏加载快
31.事件委托的原理和作用
原理:给父元素注册事件,利用事件冒泡,当子元素的事件触发,会冒泡到父元素,然后去控制相应的子元素
作用:
只操作了一次 DOM ,提高了程序的性能。
动态新创建的子元素,也拥有事
五 跨域
1.同源策略
设置同源策略主要是为了安全,如果没有同源策略限制,在浏览器中的cookie等其他数据可以任意读取,不同域下的DOM任意操作,ajax任意请求其他网站的数据,包括隐私数据
同源指的是三个相同,这个三个相同才能正常的通信:
1.协议相同
2.域名相同
3.端口相同
2. 什么是跨域
协议域名端口一致是同源策略,如果违反了同源策略,则会造成跨域。
3. 解决跨域的方式【vue反向代理,JsonP】
-
Jsonp:可以利用script标签的src属性不受同源策略限制的特点,来调用后端接口,从而解决跨域问题。缺点是必须定义一个全局的方法。
-
Vue反向代理配置:
-
本地跨域:通过webpack为我们起一个本地服务器作为请求的代理对象, 通过vue.config.js【康废鸽】文件里面devServer【待V涩玩】属性里面的proxy配置,一共配置四个属性,分别市代理名称,代理地址,开启跨域,和重写路径
-
线上跨域是在nginx.conf【镜克斯.啃废】文件里面配置,代理名称是通过【楼K神】loaction,代理地址是【pro瞌睡_怕死】proxy_pass
-
Vue2和3代理配置文件不同,Vue2是在config文件夹中的index.js中proxyTable配置代理,而Vue3是在vue.config.js中使用proxy配置代理。
-
-
中间服务器代理:通过服务器和服务器之间不会产生跨域的特点,由后端搭建的中间服务器转发客户端向服务端发送的请求。
-
cors【铐市】后端在响应头里添加允许跨域的头。
六 面试必问的项目类问题
1. 什么是RBAC?
为了达成不同的帐号登陆系统后能看到不同的页面,能执行不同的功能的目标,我们有很多种解决方案,RBAC(Role-Based Access control)权限模型 ,也就是基于角色的权限分配解决方案。
2. 前端怎么处理鉴权?
前端鉴权我们主要通过RBAC鉴权进行控制,主要分为页面鉴权和UI鉴权
其中页面鉴权分为两点:
一,是指用户有没有权限访问该页面,具体实现流程是通过路由守卫结合后端返回的token进行页面跳转前的拦截,查看token是否过期,并查看是否拥有访问该页面的权限
二,是指用户有没有查看该页面路由的权限,具体实现流程大致分为两种:
(1)纯前端实现,在配置路由表的时候,在需要进行鉴权的路由下添加meta属性,写入可以访问该路由的角色信息,然后通过meta下的信息使用addRouter方法来控制该路由的显示与隐藏
(2)前后端配合实现,每次登入后,将响应的token信息进行解码,再存储到vuex中,然后用addRouter方法动态的渲染该token下的路由表。
UI鉴权:
处理UI鉴权一般来说UI鉴权指的是按钮鉴权,简单的方法就是v-if绑定你存储的token信息下的角色信息与按钮需要的角色信息进行判断,满足条件可以正常使用,不满足条件触发隐藏,但是这样做并不方便统一管理,为了提高代码的可扩展性,可以使用自定义指令中,在自定义指令中集中处理鉴权的逻辑,再分发给每个需要鉴权的按钮上。
3. 封装组件的大致思想
先找到封装组件的可配置项(不同点),每次复用封装的组件的时候由父组件传入子组件可配置项的值,这样每次使用的时候都会生成新的实例,达到每个组件独立且通用。
工作中封装过哪些组件?
模态框,其中模态框的标题,宽高,按钮,以及表单样式都是可以配置的,一份两用,编辑用,添加用
4. 工作中分页怎么处理?
1.一般来说分页是由后端进行处理,我们只需要传入相关的数据(每页条数)去后端请求数据即可,这样的好处可以节省请求数据的事件,减少系统的压力。
2.在个别情况前端也可以分页功能,具体做法是第一次请求数据的时候加载全部数据,利用获取页码和步长去过滤请求回来的数据,再加载到相应的页面上,这样的缺点是,第一次请求的数据过大,会对页面,服务器造成压力,一般来说都是后端来进行处理分页功能。
5. 为什么要在项目中封装axios
因为为了方便统一处理请求或者处理接口,并且归纳请求时和响应时的回调函数,我们来统一管理axios的内置钩子函数,达到代码简洁,易于管理
具体实现步骤:
第一步,import引入axios,
第二步创建axios实例,用axiosCreate创建,
第三步在axios实例中配置基准地址和延时时间,
第四次封装axios请求拦截和响应拦截并处理promise的.then和.catch方法。其中token配置在请求拦截中,并加载在axios请求头中,随请求拦截加载在请求头里
请求拦截调用intercettors.request并返回传入的信息
响应拦截调用【因特卡铺特斯】.【瑞斯绑死】.use比并返回响应拦截参数
七 webpack问题:
1. 什么是webpack
webpack是目前最流行的静态资源打包工具,本身功能特别局限,只能处理js,json文件,但是他有很多生态资源(loader,plugins)使得他可以打包各类文件。
**作用:**压缩代码 处理浏览器兼容 提升代码性能,将代码编译成浏览器认识的语言。
**静态资源打包工具:**将一个或多个文件作为打包入口,将我们整个项目的所有文件编译组合合并一个或多个文件传输出去 传输的文件接受编译好的文件 就可以在浏览器运行。 Vue 输出Main.js 输出dist文件
2. 你在项目中做过哪些优化:(重点)项目优化
- 利用webpack5自身丰富的生态圈提供的很多插件,可以达到文件、图片分割,比如code split插件,他可以对输出的文件进行工程化分割,从而达到分割js和按需引入js的目的。
- webpack压缩图片(减小图片大小)一般在vue项目中用webpack打包时,会根据webpack.base.conf.js中url-loader中设置limit【李密特】大小来对图片处理,对小于limit的图片转换为base4格式,其余的不做操作。对那些大图片资源请求时,加载会很慢,可以用image-webpack-loader来压缩图片。
- 在打包的时候运行npm run build --report 打包完成后,会打开一个页面,展开各个依赖包的大小,查看哪些第三方依赖包的体积大,就干掉他,从CDN上引入,从而达到减少项目体积,提高加载速度。
- 图片使用图片精灵或懒加载,或cdn引入。三种方式优化。
- vueRouter路由懒加载,按需引入,减少首屏加载时间
- 封装可以反复调用的模块(比如http请求), 封装可复用的组件(UI库)可以提高代码简洁性
- 变量名,函数名 ,模块名 ,组件名 按照vue官方推荐命名格式进行命名。
- 在编写代码的时候要进行备注即注释,尤其是封装接口的时候,要注释各个字段的详细信息。
- keep-alive缓存不活动的组件。
- v-for正确设置key值可以优化diff算法的对比速度,key尽量设置成ID或者键值名,
3. webpack 五大核心概念
entery: 入口文件
output: 输出文件
loader: 加载器 用于解析并引入不同的文件
plugins: 插件 扩展webpack功能的插件
mode: 模式 规定了webpack的两种模式
4. 打包出现空白页面怎么办?
webpack打包之后 dis 是一个静态文件,默认路径是/,我们需要在打包之前更改变量publicPath【公共路径】为 ./ 即可访问静态资源。
路由模式在本地开发调试的时候,我们可以使用哈希模式
但是项目打包上线需要更改为【黑丝腿瑞】,需要后端"重定向”一下
打包优化,利用webpack图形化插件可以直观的看到打包后每个文件的大小,我们可以针对体积较大的文件进行cdn引入,达到缩小dist文件夹的体积的作用。
webpack打包速度优化:
一 . 性能优化:
① _ 在处理打包图片资源的时候可以配置相关loader 设置足够小的图片变成base64 形式 以减少请求次数 当然这样的缺点会使体积变得更大。
② _ 可以使用代码分割技术 code split 对输出的文件 进行工程化分割,从而达到分割js 和按需引入js的目的。
二 . 开发体验优化
① _ 我们利用SourceMap【扫死迈普】生成代码映射文件 可以准确的看到行映射和列映射的关系从而直观的判断出BUG
(开发模式下用行映射 生产模式下用行映射 加列映射 -- 生产模式在dist文件 文件压缩代码量大 )
三 . 打包构建速度优化
① _ 开启HMR/热更新,
② _ 可以用oneOf 减少loader的匹配次数
③ _ 通过Include / Exclude 进行筛选 可以大大提升处理js 文件的时间。
④ _ 使用 无损或有损压缩插件进行图片的压缩,来减小图片体积。
⑤ _ Babel 会为每个文件插入辅助代码,从而使得代码体积过大 可以用相应插件 进行辅助代码抽离达到减小代码体积。
(HMR/热模块替换)
四 .减少代码体积
① _ 升级为webpack5 利用 Tree Shaking 移除js上没有使用的代码 达到缩小体积的目的。
② _ 将打包之后的 依赖库的大小进行对比 体积大的第三方插件库可以选择使用CDN方式引入从而减少打包体积。
③ _ 抽离并合并 配置文件中的相同代码段。