目录
- 一 vue 面试题
- keep-alive理解
- 父子组件传值
- $nextTick的理解和作用
- 虚拟dom和真实dom
- 二 vue 生命周期面试题
- vue生命周期理解
- 第一次加载页面会触发的钩子函数
- 常用钩子函数适用的场景
- vue 父子组件的渲染顺序
- vue父子组件销毁的顺序
- 三 vuex面试题
- vuex是什么
- vuex属性及使用方法
- 四 vue-router 面试题
- vue-router是什么
- vue-router 有哪些常用的组件
- vue-router的两种路由模式以及区别
- 路由跳转方式
- 导航守卫分类
- 五 javascript
- 深拷贝
- 排序函数
- 去重函数
- 常用的es6方法
- 什么事闭包
- 函数的this指向问题
- 六 计算机网络
- 常见的浏览器内核
- 浏览器引擎(内核)
- 输入ip到页面显示的过程
- 浏览器渲染的过程
- 三次握手
- 四次挥手
- 三次握手和四次挥手总示意图
- 七 HTML、CSS 相关内容
- 页面重绘和回流
一 vue 面试题
-
keep-alive理解
-
父子组件传值
2.1 子组件通过props接收父组件穿过来的数据
2.2 子组件通过$emit 触发父组件中的方法 -
$nextTick的理解和作用
3.1 nextTick 本质上是一个延时器。vue用三种方案实现nextTick。promise、setTimeOut
、HTML5中的APIMutationObserver
3.2 有时dom还没渲染结束,确想对dom进行一些操作时,可能实际结果与预期不符,可以将对dom的操作或者数据变化后的一些操作放在 $ nextTick函数中进行逻辑的处理。例如:查看切换后的结果,就可以在切换函数中使用$nextTick函数,一般是需要对dom进行操作
3.3 像对canvs一类的标签,无法监测到数据变化内容变化的标签,就需要在nextTick中进行重绘。因为对于虚拟dom来说,没有发生变化。methods: {// 函数执行顺序 点击事件函数(数据data发生变化)——》beforeUpdate函数——》updated函数——》nextTick函数// 更新的本质是数据发生了变化changeMsg(){this.form.username = 'lxf'const that = thisthis.$nextTick(function() {// 仅在整个视图都被重新渲染之后才会运行的代码console.log(that.$refs['userName'].innerHTML + 'changeMsg')})}}
-
虚拟dom和真实dom
- 本质上 Virtual Dom 是一个 JavaScript 对象,通过采用对象的格式来表示真实的dom结构。
可以有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能
- 在数据发生变化前,虚拟dom都会缓存一份,在数据发生变化时,新生成的虚拟DOM与缓存的虚拟DOM进行比较(采用diff算法比较),精准更新发生变化的节点,而没有发生变化的直接通过原先的数据进行渲染。
- 可以减少对dom的操作,提高程序性能;减少页面重绘和回流;跨平台,虚拟DOM本质上是javascript的一个对象,不依赖真实的平台环境。
- 虚拟DOM形成过程 template模板——》被编译器 compiler编译成渲染函数 ——》在挂载前(beforeMount)调用render函数并返回虚拟DOM对象,在beforeMount后、mounted前通过patch函数转为真实DOM。相关知识ast语法树
- 本质上 Virtual Dom 是一个 JavaScript 对象,通过采用对象的格式来表示真实的dom结构。
二 vue 生命周期面试题
-
vue生命周期理解
-
第一次加载页面会触发的钩子函数
beforeCreate created beforeMount mounted -
常用钩子函数适用的场景
- created :此时数据和方法已经配置完毕,可以进行方法调用和数据使用。
- mounted : 此阶段dom元素已经挂载结束,可以获取页面中的元素进行操作。如果有子组件建议和
vm.$nextTick
搭配使用。 - deforeDestroy :实例销毁前可以进行一些善后处理,如:清除计时器、清除非指令绑定的事件等等…’
-
vue 父子组件的渲染顺序
父组件beforeCreated ——》 父组件created ——》父组件beforeMount ——》第一个子组件beforeCreate ——》第一个子组件created ——》第一个子组件beforeMount ——》第N个子组件beforeCreate ——》第N个子组件created ——》第N个子组件beforeMount ——》第一个子组件mounted ——》第N个子组件mounted ——》父组件mounted -
vue父子组件销毁的顺序
父组件beforeDestroy -——》第一个子组件beforeDestroy ——》第一个子组件destroyed -——》第N个子组件beforeDestroy ——》第N个子组件destroyed ——》父组件destroyed
三 vuex面试题
-
vuex是什么
答:vuex是Vue框架的状态管理。需要创建store.js文件,在main.js中引入并 注入。下面是main.js 文件 -
vuex属性及使用方法
四 vue-router 面试题
-
vue-router是什么
vue-router是Vue管理路由的一个插件,即记录组件和路径的映射关系。
-
vue-router 有哪些常用的组件
- router-link :是一个路由导航的一个组件,和a标签的功能一样。当路由被激活时有一个激活的css类名(router-link-active),可以做导航高亮显示
- router-view :是一个function组件,将路由匹配到的视图组件渲染在router-view标签的位置。
-
vue-router的两种路由模式以及区别
- 两种模式 hash和history。默认是hash
- 共同点:url改变无需重新加载页面
- 区别
- hash路径前带#,history不带#
http://localhost/components/tabs (history)
http://localhost/#/components/noticeBar (hash值为 #/components/noticeBar)- hash模式下的http请求不会包含hash值
- hash支持低版本浏览器,history不是那么友好
- history模式部署需要后端配置,
当用户刷新页面时会向真的向服务器请求资源,并携带有url
,后端不做处理的话会报404。需要后端配置一下apache或是nginx的url重定向,重定向到入口文件(index.html)。 - 路由的哈希模式是利用了window.onhashchange事件,当url中的哈希值(#后面的值)发生变化,就会自动调用hashchange的监听事件,在hashchange的监听事件内可以得到改变后的url,这样能够找到对应页面进行加载
window.addEventListener('hashchange', () => {// 把改变后的url地址栏的url赋值给data的响应式数据current,调用router-view去加载对应的页面this.data.current = window.location.hash.substr(1)})
- history利用的是HTML5 新增的 pushState() 和 replaceState() 方法,在原有的back、forward、go 的基础上,这两个方法提供了对历史记录进行修改的功能。pushState方法、replaceState方法,只能导致history对象发生变化,从而改变当前地址栏的 URL,但浏览器不会向后端发送请求,也不会触发popstate事件的执行
- 传统的路由指的是:当用户访问一个url时,对应的服务器会接收这个请求,然后解析url中的路径,从而执行对应的处理逻辑。这样就完成了一次路由分发;
而前端路由是不涉及服务器的,是前端利用hash或者HTML5的history API来实现的,一般用于不同内容的展示和切换
-
路由跳转方式
-
导航守卫分类
5.1
全局守卫导航
5.1.1 全局前置守卫 beforeEach。路由每次跳转前都会执行此函数。一般用于跳转前进行判断拦截,例如:是否登录const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,scrollBehavior: () => ({ y: 0 }),routes: []})router.beforeEach((to, from, next) => {// to:即将要进入的目标 路由对象// form:当前导航正要离开的路由// next :执行下一步操作// 判断是否登录if (getToken()) {/* has token*/if (to.path === '/login') {next({ path: '/' })NProgress.done()} else {next()}} else {// 没有tokenif (whiteList.indexOf(to.path) !== -1) {// 在免登录白名单,直接进入next()} else {next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页NProgress.done()}}})
5.1.2 全局后置守卫 afterEach。路由每次跳转结束都会执行此函数。
router.afterEach((to, from) => {// ...})
5.2
组件守卫导航
5.2.1 beforeRouteEnter 导航确认之前调用,新的组件(页面)还没被创建。可以针对不同页面进行特殊处理。
5.2.2 beforeRouteUpdate 动态路由发生变化时调用
5.2.3 beforeRouteLeave 用户离开前调用beforeRouteEnter(to, from, next) {// 在渲染该组件的对应路由被 confirm 前调用// 不!能!获取组件实例 `this`// 因为当守卫执行前,组件实例还没被创建console.log('beforeRouteEnter')next(vm => {// 通过 `vm` 访问组件实例})},beforeRouteUpdate(to, from, next) {// 在当前路由改变,但是该组件被复用时调用// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。// 可以访问组件实例 `this`next()},beforeRouteLeave(to, from, next) {// 导航离开该组件的对应路由时调用// 可以访问组件实例 `this`console.log('beforeRouteLeave')next()},
5.3
路由独享守卫
const router = new VueRouter({routes: [{path: '/foo',component: Foo,beforeEnter: (to, from, next) => {// ...}}] })
五 javascript
-
深拷贝
- 为什么需要深拷贝
希望在改变新数组(对象)时,不改变原数组(对象)。 - 造成需要深拷贝的原因
数据类型有两种:基本数据类型和引用数据类型。所有的变量都存在栈内存中,引用数据类型的变量存在栈内存,实际数据存在堆内存
。引用数据类型在栈内存中存储的只是一个内存地址。赋值时也是把这个地址赋值给另一个变量。 - 深拷贝的思路
- 1 判断数据源的数据类型,是数组还是对象,定义出返回的变量finalResult
- 2 循环数据源(for in 循环,数组对象都可循环)
- 3 判断每一项的数据类型
- 4 如果是数组或者对象,就再次调用函数,将当前数据当参数传入函数
- 5 如果是引用类型的其他数据,如Date、Error、RegExp、Set等,通过使用constructor属性创建对应的数据类型(
new 数据.constructor(数据)
)并进行赋值 - 6 剩余的直接进行赋值
- 7 最后将开始定义的变量finalResult 返回
- 实现深拷贝的方案
使用JSON.stringify和JSON.parse
,若有undefined、function,拷贝后丢失,Date、Error、RegExp等引用类型的数据会与初始数据不一样。let obj = {username: "cat",age: 10,color: 'gary',sex: undefined } // 将对象转换为json字符串形式 let tempObj = JSON.stringify(obj); // 将转换而来的字符串转换为原生js对象 let newObj = JSON.parse(tempObj ); console.log(newObj) // {username: "cat",age: 10, color: 'gary',} newObj.age = 5; console.log(obj.age); // 10 console.log(newObj.age); // 5
- 使用插件lodash 相关代码
- 递归
- 使用Jquery的extend函数
$.extend( [deep ], target, object1 [, objectN ] )
deep表示是否深拷贝,为true为深拷贝,为false,则为浅拷贝
target Object类型 目标对象,其他对象的成员属性将被附加到该对象上。
object1 objectN可选。 Object类型 第一个以及第N个被合并的对象let obj = {username: "cat",age: 10,color: 'gary',sex: undefined,arr: [1,2,3,4]}let newObj = $.extend(true,{},obj);newObj.arr[0] = 5;console.log(obj.arr[0]); // 1console.log(newObj.arr[0]); // 5
- 使用concat函数 只能是数组的拷贝,不能拷贝对象
- 为什么需要深拷贝
-
排序函数
- 冒泡排序
-
去重函数
-
常用的es6方法
-
什么事闭包
-
函数的this指向问题
六 计算机网络
-
常见的浏览器内核
- IE浏览器内核:
Trident
内核,也是俗称的IE内核; - Chrome浏览器内核:统称为Chromium内核或Chrome内核,以前是Webkit内核,现在是
Blink
内核 - Firefox浏览器内核:
Gecko
内核,俗称Firefox内核; - Safari浏览器内核:
Webkit
内核 - Opera浏览器内核:最初是自己的
Presto
内核,后来是Webkit,现在是Blink
内核 - 360浏览器、猎豹浏览器内核:IE+Chrome双内核
- 搜狗、遨游、QQ浏览器内核:Trident(兼容模式)+Webkit(高速模式);
- 百度浏览器、世界之窗内核:IE内核;
- 2345浏览器内核:以前是IE内核,现在也是IE+Chrome双内核;
- IE浏览器内核:
-
浏览器引擎(内核)
- 浏览器内核原来是指
渲染引擎
和js引擎
,随着js引擎越来越独立,现在内核更倾向于渲染引擎。 - 浏览器内核负责HTML解析、布局、渲染等相关工作,JavaScript引擎负责解析、执行JavaScript代码。
- 浏览器地址栏输入 chrome://version 进行查看js引擎和浏览器内核(部分可以看见)
- 浏览器内核原来是指
-
输入ip到页面显示的过程
- 用户输入域名(IP地址),过DNS解析找到对应的ip地址
- 根据ip地址向服务器发送请求
- 建立TCP连接(三次握手)
- 连接建立完成后,发送http请求
- 服务器根据请求作出http响应
- 浏览器得到响应的内容,进行解析渲染并展示
- 断开连接(四次挥手)
-
浏览器渲染的过程
- 解析html构建DOM树(DOM Tree)
- 解析css构建CSS规则树(CSSOM Tree)
- 将DOM树和CSS规则树结合在一起构建成渲染树(Render tree)
- 根据生成的渲染树,进行回流(reflow),得到节点的几何信息(大小、位置等)
- 重绘(repaint):根据回流得到的几何信息,得到节点的绝对像素
- display:将像素发送给GPU,最后通过调用操作系统Native GUI的API绘制,展示在页面上
- 遇到<script>则暂停渲染,优先执行js,然后再继续渲染(因为js执行和渲染引擎公用一个进程,原因是js可 能做了一些dom操作,一般会把js放到页面的底部)(发生回流,重绘)
-
三次握手
- 5.1 三次握手过程
- 客户端给服务端发送一个同步(SYN)报文请求建立连接,此时客户端进入
SYN_SENT
状态 - 服务器接收到后会给客户端发送一个确认(SYN - ACK)报文,服务端进入
SYN_RCVD
状态 - 客户端接收到后又发送一个确认(ACK)报文。此时客户端进入
ESTABLISHED
状态.当服务端接收到客户端的第三次握手后也就进入ESTABLISHED状态
- 客户端给服务端发送一个同步(SYN)报文请求建立连接,此时客户端进入
- 5.2 三次握手的作用:
确认双方的接收和发送功能是否正常,即传输线路是否正常
服务端接收到客户端的报文表明客户端端的发送功能和服务端的接收功能正常;浏览器接收到服务端的报文表明浏览器的发送接收、服务器的发送接收功能都正常;服务端不知道自己发送功能是否正常,所以需要浏览器再发送一次应答报文给服务器。三次握手第三次可以携带数据,前两次不行
。 - 5.3 两次握手造成的后果:
会造成服务端的系统资源浪费
。
如果客户端发送连接请求,因报文丢失而未收到确认,客户端就会再次发送请求。收到确认后建立连接,数据传输结束,断开连接。可能第一次的请求因在某些网络节点长时间滞留,导致在释放连接后到达服务端,此时服务端以为是客户端再次请求连接,服务端向客户端发送确认报文,等待客户端发送数据,客户端迟迟不发送数据,就会造成服务端无法正常关闭。 - 5.4 半连接队列:存放未建立起连接状态的请求
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。 - 5.5 全连接队列: 完成三次握手建立起连接的放在全连接队列。 全连接队列满了会造成丢包问题。
- 5.6 服务端SYN - ACK包重发次数问题
在服务端发送完SYN - ACK包后,没有收到客户端的确认包,服务端就会进行首次重传,一段时间内还是未收到确认包,进行第二次重传,直到重传达到系统设置的最大次数,系统才会把这些连接信息从半连接队列中删除。一般重传间隔时间为指数增长,例如:1s 2s 4s 8s… - 5.7 SYN攻击:
服务器端的资源分配是在第二次握手分配的,客户端的资源分配是在第三次握手时分配的。
所以服务器端容易受到SYN泛洪攻击。就是在短时间内伪造大量不存的IP地址向Server(服务端)发送SYN包,Server则发送确认包,等待client确认,由于IP地址不存在,Server需要不断的重发确认包,直至超时。这些伪造的SYN包长时间占用半连接队列,造成正常的SYN请求因队列满而被丢弃。 - 5.8 常见的防御SYN攻击方法:
- 缩短超时(SYN Timeout)时间,通过缩短重传超时时间
- 增加半连接状态数量
- 采用SYN cookies 技术: 半连接队列被塞满,采用SYN cookies可以使SYN请求不被丢弃,而是用加密的方式标记半连接状态
- 过滤网关(防火墙、路由器)防护:网关超时设置、SYN网关、SYN代理
- 5.1 三次握手过程
-
四次挥手
-
6.1 四次挥手过程
- 客户端发送一个FIN报文给服务端,告诉服务端我要断开连接,客户端进入终止等待1(FIN-WAIT1)
- 服务端接收到FIN报文,会给客户端发送一个ACK报文,表述我接收到你的请求了,服务器端进入CLOSE_WAIT状态
- 客户端收到服务的ACK包后进入终止等待2(FIN-WAIT2),等到服务端发送断开连接的报文
- 服务器进入CLOSE_WAIT后说明服务器准备关闭连接(但是需要处理完之前的未处理的数据),当服务器调用close方法真正准备关闭连接时,会向客户端发送FIN,这时服务器进入 LAST_ACK状态(等待最后一个ACK到来)。
- 客户端收到服务端的FIN报文,会向服务端发送一个ACK报文,此时客户端进入TIME_WAIT状态。
- 当服务器收到来自客户端的ACK(对自己提出FIN请求的回应)后,就会彻底关闭变为CLOSED状态。
-
6.2 MSL是什么
MSL是Maximum Segment Lifetime的缩写,翻译过来就是
最长报文段寿命
。它是任何报文在网络中存在的最长时间,超过这个时间报文就会被丢弃。 -
6.3 为什么要等2MSL后才能关闭连接
- 为了确保客户端发送的最后一个ACK报文能够正常到达服务端。
客户端发送的最后一个ACK包可能丢失,如果服务端在LAST_ACK状态收不到客户端发来的ACK确认包,服务端就会重传FIN - ACK 包,客户端就会重新发送ACK包。重新计时,过2MSL时间关闭。如果客户端不等待2MSL,可能造成服务端接收不到客户端的ACK报文,导致服务端无法正常进入CLOSED状态。TIME_WAIT状态就是为了重发可能丢失的ACK报文
- 防止“已失效的连接请求段报文”出现在新的连接中
客户端发送完最后一个ACK报文,经过2MSL时间,网络中关于此次连接的报文都会被丢弃,就不会出现在下一个新的连接中出现旧的连接报文请求。
- 为了确保客户端发送的最后一个ACK报文能够正常到达服务端。
-
-
三次握手和四次挥手总示意图
七 HTML、CSS 相关内容
-
页面重绘和回流
- 1.1 什么是重绘和回流
- 回流(reflow):也叫重排,指的是当渲染树中的节点信息发生了
大小、边距等问题,需要重新计算各节点和css具体的大小和位置
。需要走完整的渲染过程。 - 重绘(repaint):当节点的部分属性发生变化,但不影响布局,只需要重新计算节点在屏幕中的绝对位置并渲染的过程。比如:改变元素的背景颜色、字体颜色等操作。重绘少布局和分层
- 重绘不一定会引发回流,回流必然导致重绘。
- 重绘的执行效率比重排的执行效率高
- 回流(reflow):也叫重排,指的是当渲染树中的节点信息发生了
- 1.2 什么操作引起重绘
- 修改以下属性会引起重绘操作
color
border-style
border-radius
text-decoration
box-shadow
outline
background
- 修改以下属性会引起重绘操作
- 1.3 什么操作引起回流
- 改变元素的位置和大小操作
- 添加删除可见的DOM元素
- 元素尺寸改变
- 内容变化(如 input文本内容发送变化)
- 浏览器窗口改变。元素的大小和位置都是以窗口为参照的
- 设置style的属性值
- 激活css伪类,如:hover
- 获取布局的属性或者方法。浏览器对回流和重绘有一定的优化机制,就是浏览器会将回流放在一个队列中,经过一定时间或者操作达到了临界值,就会挨个执行。如果获取属性值,需要是最新的布局信息,浏览器就会强制清空队列(强制回流)。
- css 相关(几何属性:布局、尺寸)
display
flex
position
float
left
right
top
bottom
width
height
margin
padding
border
font-size
text-align
overflow:有滚动条和无滚动条位置大小有所改变
vertical-align
line-height
- 改变元素的位置和大小操作
- 1.4 获取布局的属性和方法
offsetTop
offsetLeft
offsetWidth
offsetHeight
scrollTop
scrollLeft
scrollWidth
scrollHeight
clientTop
clientLeft
clientWidth
clientHeight
getComputedStyle() - 1.5 性能优化
- 样式合并修改
- 将修改的样式用一个类名代替,动态添加删除类名
<style>.test{padding:5px; border:1px solid #000; margin:5px;} </style> <script>let oDiv = document.getElementById("test")oDiv.classList.add('test'); </script>
- 使用style的cssText属性
const oDiv = this.$refs.userName // += 是在原来的元素上添加cssText的样式,原来的样式也保留 // ;padding 前添加一个分号是为了兼容IE,IE不能实现样式累加 oDiv.style.cssText += ';padding:5px; border:1px solid #000; margin:5px;';
- 将修改的样式用一个类名代替,动态添加删除类名
- 批量操作DOM:核心是使元素脱离标准流,操作结束再放回标准流中。(不在标准流中的元素修改不会触发回流)
隐藏元素
:在隐藏ul和显示ul的时候,触发了两次回流,给ul添加每个li的时候没有触发回流let oUl = document.getElementById("test") oUl.style.display = 'none'; for(var i=0;i<data.length;i++){var oLi = document.createElement("li");oLi.innerText = data[i].name;oUl.appendChild(oLi); } oUl.style.display = 'block';
使用文档碎片:
创建文档碎片,将所有li先放在文档碎片中,等都放进去以后,再将文档碎片放在ul中let oUl = document.getElementById("test") // 创建文档碎片 let fragment = document.createDocumentFragment(); for(var i=0;i<data.length;i++){var oLi = document.createElement("li");oLi.innerText = data[i].name;fragment.appendChild(oLi); } oUl.appendChild(fragment);
拷贝节点:
将ul拷贝一份,将所有li放在拷贝中,等都放进去以后,使用拷贝替换掉ullet oUl = document.getElementById("test") // 拷贝节点 let newUL = oUl.cloneNode(true); for(var i=0;i<data.length;i++){var oLi = document.createElement("li");oLi.innerText = data[i].name;newUL.appendChild(oLi); } oUl.parentElement.replaceChild(newUl, oUl);
- 其他
- 避免设置多层的内联样式。样式层级过多会影响重绘重排效率。
- 避免使用table布局。很小的变动会引起table重排。可用ul li span等标签生成table表格
- 对于复杂的动画,尽量将元素设置为绝对定位。操作元素的定位属性可以只使这一个元素回流,如果不是,容易引起其父元素和子元素的回流。
- 样式合并修改
- 1.1 什么是重绘和回流
6 冒泡和捕获
- 定义
- 事件冒泡:事件由子元素传递给父元素的过程就做事件冒泡。
- 事件捕获:事件由父元素传递给子元素的过程叫做事件捕获。
- 区别
- 冒泡和捕获就是事件的执行顺序不一样。
- 冒泡是先子元素,在层层向外层触发(false)
- 捕获是先外层触发,再层层向内层触发,直到触发的元素(true)
- 阻止事件冒泡的方式
- event.stoppropagation() 阻止事件冒泡
- addEventListener(()=>{}, false)
- vue 修饰符 .stop
- 阻止默认行为
- event.preventDefault()
- vue 修饰符 .prevent
- button的默认行为
- button的 type = submit 默认提交表单
- button的type = reset 默认行为表单重置
- button的type = button 就会执行do nothing
- 未定义button type的,IE浏览器默认的是button类型,其他浏览器默认的是submit类型的
7 v-if和v-for能否同时用以及可能出现的问题
- 尽量不用同时使用
- vue2 中v-for优先级高于v-if, 造成性能上的浪费,vue3 v-if比v-for的优先级高,导致v-if 使用不了v-for中的变量
- 使用computed 过滤出需要展示的数据
- 使用template 将for 循环包裹
- 使用v-show
8 什么是闭包,以及是否可以在闭包里面用await
- 定义
- 闭包:可以在外部访问函数内部的变量,一般是返回一个函数,函数内部引用了变量
- 内存泄漏:指程序在申请内存时,没有足够的内存空间供其使用,出现 out of memory
- 内存溢出:指程序在申请内存后,无法释放已申请的内存空间,内存泄露堆积会导致内存被占光
- 闭包的特点
-
持久性存储:当内存中的值失去引用时便会被垃圾回收机制处理释放内存,但因为闭包导致某块内存永远存在。
let fn = null; function f1() {let a = 666;function f2(){console.log(a);}fn = f2; } f1() fn()//-------666
正常情况f1() 执行结束后,其生命周期就结束了,外部无法访问到变量a,但是因为f2的引用赋值给了fn,所以f2被保留了下来,又因为f2中的a依赖f1,所以f1也被保留了下来。因此在f1执行结束后还可以访问到变量a
-
导致内存泄漏:闭包会将数据持久性存储,每次外部函数触发,都会开辟一块新的内存,函数调用的那块内存会被长久占用而不能被垃圾回收机制释放,就会导致内存泄漏。
function init() {var a = 0;return function (){console.log(++a);} } let resA = init(); let resB = init(); resA()//----1 resA()//----2 resB()//----1 resB()//----2
每次调用都是重新开始,重新开辟的空间,之前的也不会被释放
-
9 怎样提高vue的性能
12 数据请求方式
13 post 和get 的优缺点