19、 说说javascript内存泄漏的几种情况?
一、是什么
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存
对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃
在C语言中,因为是手动管理内存,内存泄露是经常出现的事情。
char * buffer; buffer = (char*) malloc(42); // Do something with buffer free(buffer);
上面是 C 语言代码,malloc方法用来申请内存,使用完毕之后,必须自己用free方法释放内存。
这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为"垃圾回收机制"
二、垃圾回收机制
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:
- 标记清除
- 引用计数
标记清除
JavaScript
最常用的垃圾收回机制
垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉
在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了
随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存
举个例子:
var m = 0,n = 19 // 把 m,n,add() 标记为进入环境。 add(m, n) // 把 a, b, c标记为进入环境。 console.log(n) // a,b,c标记为离开环境,等待垃圾回收。 function add(a, b) { a++ var c = a + b return c }
引用计数
语言引擎有一张"引用表",保存了内存里面所有的资源(通常是各种值)的引用次数。如果一个值的引用次数是0
,就表示这个值不再用到了,因此可以将这块内存释放
0
,垃圾回收机制无法释放这块内存,从而导致内存泄漏
const arr = [1, 2, 3, 4]; console.log('hello world');
面代码中,数组[1, 2, 3, 4]
是一个值,会占用内存。变量arr
是仅有的对这个值的引用,因此引用次数为1
。尽管后面的代码没有用到arr
,它还是会持续占用内存
如果需要这块内存被垃圾回收机制释放,只需要设置如下:
arr = null
arr
为
null
,就解除了对数组
[1,2,3,4]
的引用,引用次数变为 0,就被垃圾回收了
小结
有了垃圾回收机制,不代表不用关注内存泄露。那些很占空间的值,一旦不再用到,需要检查是否还存在对它们的引用。如果是的话,就必须手动解除引用
三、常见内存泄露情况
意外的全局变量
function foo(arg) { bar = "this is a hidden global variable"; }
this
创建:
function foo() { this.variable = "potential accidental global"; } // foo 调用自己,this 指向了全局对象(window) foo();
上述使用严格模式,可以避免意外的全局变量
定时器也常会造成内存泄露
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000);
如果id
为Node的元素从DOM
中移除,该定时器仍会存在,同时,因为回调函数中包含对someResource
的引用,定时器外面的someResource
也不会被释放
包括我们之前所说的闭包,维持函数内局部变量,使其得不到释放
function bindEvent() { var obj = document.createElement('XXX'); var unused = function () { console.log(obj, '闭包内引用obj obj不会被释放'); }; obj = null; // 解决方法 }
DOM
元素的引用同样造成内存泄露
const refA = document.getElementById('refA'); document.body.removeChild(refA); // dom删除了 console.log(refA, 'refA'); // 但是还存在引用能console出整个div 没有被回收 refA = null; console.log(refA, 'refA'); // 解除引用
包括使用事件监听addEventListener
监听的时候,在不监听的情况下使用removeEventListener
取消对事件监听
20、 说说React生命周期中有哪些坑?如何避免?
避免生命周期中的坑需要做好两件事:1、不在恰当的时候调用了不该调用的代码;2、在需要调用时,不要忘了调用。
那么下面7种情况最容易造成生命周期的坑:
componentWillMount 在 React 中已被标记弃用,不推荐使用,主要的原因是因为新的异步架构会导致它被多次调用,所以网络请求以及事件绑定应该放到 componentDidMount 中
componentWillReceiveProps 同样也被标记弃用,被 getDerivedStateFromProps 所取代,主要原因是性能问题。
shouldComponentUpdate 通过返回 true 或者 false 来确定是否需要触发新的渲染。主要用于性能优化。
componentWillUpdate 同样是由于新的异步渲染机制,而被标记废弃,不推荐使用,原先的逻辑可结合 getSnapshotBeforeUpdate 与 componentDidUpdate 改造使用。
如果在 componentWillUnmount 函数中忘记解除事件绑定,取消定时器等清理操作,容易引发 bug。
如果没有添加错误边界处理,当渲染发生异常时,用户将会看到一个无法操作的白屏,所以一定要添加。
追问:React 的请求应该放到哪里?为什么?
对于异步请求,应该放到componentDidMount 中去操作,从生命周期函数执行顺序来看,除了componentDidMount 之外还有下面两种选择:
constructor 中可以放,但是不推荐,因为 constructor 主要用于初始化 state 和函数绑定,并不承载业务逻辑,而且随着类属性的流行,constructor 很少用啦componentWillMount 已被标记废弃,因为在新的异步渲染下该方法会触发多次渲染,容易引发bug,不利于 React 的后期维护和更新
所以React 的请求放在 componentDidMount 里是最好的选择。
21、 说说redux的实现原理是什么,写出核心代码?
前言
相信很多人都在使用redux作为前端状态管理库进去项目开发,但仍然停留在“知道怎么用,但仍然不知道其核心原理”的阶段,接下来带大家分析一下redux和react-redux两个库的核心思想和API
redux
1.为什么要使用redux?
随着互联网的高速发展,我们的应用变得越来越复杂,进行导致我们的组件之间的关系也变得日趋复杂,往往需要将状态父组件 -> 子组件 -> 子子组件 -> 又或者只是简单的 父组件与子组件之间的props传递也会导致我们的数据流变得难以维护,因为二次开发者不在熟悉项目的情况下无法第一时间确定数据来源是由谁发起的。使用redux之后,所有的状态都来自于store中的state,并且store通过react-redux中的Provider组件可以传递到Provider组件下的所有组件,也就是说store中的state对于Provider下的组件来说就是全局的。
2.redux的核心原理是什么?
1.将应用的状态统一放到state中,由store来管理state。
2.reducer的作用是返回一个新的state去更新store中对用的state。
3.按redux的原则,UI层每一次状态的改变都应通过action去触发,action传入对应的reducer 中,reducer返回一个新的state更新store中存放的state,这样就完成了一次状态的更新
4.subscribe是为store订阅监听函数,这些订阅后的监听函数是在每一次dipatch发起后依次执行
5.可以添加中间件对提交的dispatch进行重写
3.redux的api有哪些?
**1.**createStore 创建仓库,接受reducer作为参数
2.bindActionCreator 绑定store.dispatch和action 的关系
3.combineReducers 合并多个reducers
4.applyMiddleware 洋葱模型的中间件,介于dispatch和action之间,重写dispatch
5.compose 整合多个中间件
22、谈谈你对BFC的理解?
是什么?
我们在页面布局的时候,经常会出现下面几种情况
这个元素的高度怎么没了?
这两栏布局怎么没法自适应?
这两种元素的间距怎么有点奇怪的样子?
。。。
归根揭底是元素之间相互影响,导致了意料之外的情况,这里就涉及到了BFC概念。
BFC(Block Formatting Context),即块级格式化上下文,他是页面中的一块渲染区域,并且有属于自己一套的渲染规则。
1.内部的盒子会在垂直方向上一个接一个的放置。
2.对于同一个BFC的两个相邻盒子的margin会发生重叠,与方向无关。
3.每一个元素的左外边距与包含块的左边界相接触(从左到右),即使是浮动元素也是如此。
4.BFC区域不会与float的元素区域重叠。
5.计算BFC的高度时,浮动元素也参与计算。
6.BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响外面的元素,反之亦然。
触发条件
触发BFC条件包含但是不限于
根元素,即HTML元素。
浮动元素。float值为left、right
overflow的值不为visible,为auto、scroll、hidden
display的值为inline-block、inltable-cell、table-caption、table、inline-table、flex、inline-flex、grid、inline-grid
position的值为absolute或fixed
运用场景
利用BFC的特性,我们将BFC运用在以下的场景
1.防止margin重叠(塌陷)
<style> p { color: #f55; background: #fcc; width: 200px; line-height: 100px; text-align:center; margin: 100px; } </style> <body> <p>Haha</p> <p>Hehe</p> </body>
两个p元素之间的距离为100px,发生了margin重叠(塌陷),以最大的为准,如果第一个margin为80的话,两个p元素之间的距离也是100,以最大的为准。
前面讲到,同一个BFC的两个相邻盒子的margin会发生重叠。
可以在p外面包裹一层容器,并触发这个容器生成一个BFC,那么两个p不属于同一个BFC,则不会出现margin重叠。
<style> .wrap { overflow: hidden;// 新的BFC } p { color: #f55; background: #fcc; width: 200px; line-height: 100px; text-align:center; margin: 100px; } </style> <body> <p>Haha</p> <divclass="wrap"> <p>Hehe</p> </div> </body>
这时候,则不会发生重叠
清楚内部浮动
<style> .par { border: 5px solid #fcc; width: 300px; } .child { border: 5px solid #f66; width:100px; height: 100px; float: left; } </style> <body> <div class="par"> <div class="child"></div> <div class="child"></div> </div> </body>
页面显示如下
而BFC在计算高度的时候,浮动元素也会参与,所以我们可以触发.par元素才能触发BFC,则内部浮动元素计算高度也会计算。
.par { overflow: hidden; }
实现出来的效果如下
适应多栏布局
这里举例一个两栏布局
<style> body { width: 300px; position: relative; } .aside { width: 100px; height: 150px; float: left; background: #f66; } .main { height: 200px; background: #fcc; } </style> <body> <divclass="aside"></div> <divclass="main"></div> </body>
前面讲到,每一个元素的左外边距与包含块的左边界相接触。
因此,虽然.aslide为浮动元素,但是main的左边依然会与包含块的左边相接触而BFC区域不会与浮动盒子重叠
所以我们可以通过触发main
生成BFC
,以此适应两栏布局
.main { overflow: hidden; }
这时候,新的BFC
不会与浮动的.aside
元素重叠。因此会根据包含块的宽度,和.aside
的宽度,自动变窄
效果如下
小结
可以看到上面几个案例,都体现了BFC实际上就是一个页面的独立容器,里面的子元素不影响外面的元素