one标题
- 1.说说你对react的理解?有哪些特性?
- 2.说说Real DOM和Virtual DOM的区别?优缺点?
- 3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
- 4.说说React中setState执行机制?
- 5.说说react的事件机制?
- 6.React组件之间如何通信?
- 7.说说你对受控组件和非受控组件的理解?应用场景?
- 8.说说你对fiber架构的理解?解决了什么问题??
- 9.说说react diff的原理是什么?
- 10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
- 11.如何使用css实现一个三角形?
- 12.什么是强缓存和协商缓存?区别?
- 13.说说React jsx转换成真实DOM的过程?
- 14. 说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
- 15.React render方法的原理,在什么时候会触发?
- 16. React性能优化的手段有哪些?
- 17. 如何通过原生js实现一个节流函数和防抖函数?
- 18.说说webpack中常见的loader?解决了什么问题?
- 19. 说说如何借助webpack来优化前端性能?
- 20.说说javascript内存泄漏的几种情况?
1.说说你对react的理解?有哪些特性?
理解
React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效
使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流
特性
- JSX语法
- 单向数据绑定
- 虚拟DOM
- Component
- 声明式编程
特点
- 声明式设计
- 组件化
- 高效
- 灵活
- JSX语法
2.说说Real DOM和Virtual DOM的区别?优缺点?
区别
虚拟DOM不会进行重绘和回流,而而真实DOM会频繁重排和重绘
优缺点
真实dom优点:直接操作HTML易用
真实dom缺点:解析速度慢,效率低,内存占用量高;性能差:频繁操作真实DOM,导致重绘、回流
虚拟DOM优点:减少真实dom的频繁更新,减小重绘回流、占用内存少;跨平台
虚拟DOM缺点:页面首次渲染时,由于多一层虚拟DOM的计算,速度比正常慢
3.说说React生命周期有哪些不同的阶段?每个阶段对应的方法是?
初始化阶段
由ReactDOM.render()触发-----初次渲染
1. constructor()
2. componentWillMount()
3. render() 必须的
4. componentDidMount() 常用,一般在这个钩子做一些初始化,如:开启定时器、发送网络请求、订阅消息
更新阶段
由组件内部this.setState()或父组件render触发
1. shouldComponentUpdate()
2. componentWillUpdate()
forceUpdate()强制更新用得少
3. render() 必须的
4. componentDidUpdate()
卸载阶段
卸载阶段:
componentWillUnmount() 常用,一般在这个钩子做一些收尾的事情,如:关闭定时器、取消订阅消息
其他生命周期
componentWillReceiveProps:父组件与子组件进行数据传递时触发
getDerivedStateFromProps: static getDerivedStateFromProps(nextProps, prevState),
这是个静态方法,当我们接收到新的属性想去修改state,可以使用getDerivedStateFromProps,
衍生新的state,此方法在更新个挂载阶段都可能会调用
getSnapshotBeforeUpdate: getSnapshotBeforeUpdate(prevProps, prevState),
这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,
表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,
如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
4.说说React中setState执行机制?
(1)钩子函数和合成事件中
在react的生命周期和合成事件中,react仍然处于他的更新机制中,这时isBranchUpdate为true。
按照上述过程,这时无论调用多少次setState,都会不会执行更新,而是将要更新的state存入_pendingStateQueue,将要更新的组件存入dirtyComponent。
当上一次更新机制执行完毕,以生命周期为例,所有组件,即最顶层组件didmount后会将isBranchUpdate设置为false。这时将执行之前累积的setState。
(2)异步函数和原生事件中
由执行机制看,setState本身并不是异步的,而是如果在调用setState时,如果react正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。
在生命周期,根据JS的异步机制,会将异步函数先暂存,等所有同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕,isBranchUpdate被设置为false,根据上面的流程,这时再调用setState即可立即执行更新,拿到更新结果。
(3)partialState合并机制
我们看下流程中_processPendingState的代码,这个函数是用来合并state暂存队列的,最后返回一个合并后的state。
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
我们只需要关注下面这段代码:
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
如果传入的是对象,很明显会被合并成一次:
Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)
如果传入的是函数,函数的参数preState是前一次合并后的结果,所以计算结果是准确的。
(4)componentDidMount调用setstate
不推荐直接在componentDidMount直接调用setState,由上面的分析:componentDidMount本身处于一次更新中,我们又调用了一次setState,就会在未来再进行一次render,造成不必要的性能浪费,大多数情况可以设置初始值来搞定。
当然在componentDidMount我们可以调用接口,再回调中去修改state,这是正确的做法。
当state初始值依赖dom属性时,在componentDidMount中setState是无法避免的。
(5)componentWillUpdate componentDidUpdate
这两个生命周期中不能调用setState。
由上面的流程图很容易发现,在它们里面调用setState会造成死循环,导致程序崩溃。
(6)推荐使用方式
在调用setState时使用函数传递state值,在回调函数中获取最新更新后的state。
5.说说react的事件机制?
(1)react的事件机制
- react自身实现了一套事件机制,包括事件的注册、事件的存储、事件的合成及执行等。
- react 的所有事件并没有绑定到具体的dom节点上而是绑定在了document 上,然后由统一的事件处理程序来派发执行。
- 通过这种处理,减少了事件注册的次数,另外react还在事件合成过程中,对不同浏览器的事件进行了封装处理,抹平浏览器之间的事件差异。
(2)对合成事件的理解
- 对原生事件的封装
react会根据原生事件类型来使用不同的合成事件对象,比如: 聚焦合成事件对象SyntheticFoucsEvent(合成事件对象:SyntheticEvent是react合成事件的基类,定义了合成事件的基础公共属性和方法。合成事件对象就是在该基类上创建的) - 不同浏览器事件兼容的处理
在对事件进行合成时,react针对不同的浏览器,也进行了事件的兼容处理
(3)事件机制的流程
1、事件注册
在组件挂载阶段,根据组件内声明的事件类型-onclick,onchange 等,给 document 上添加事件 -addEventListener,并指定统一的事件处理程序 dispatchEvent。
2、事件存储
完成事件注册后,将 react dom ,事件类型,事件处理函数 fn 放入数组存储,组件挂载完成后,经过遍历把事件处理函数存储到 listenerBank(一个对象)中,缓存起来,为了在触发事件的时候可以查找到对应的事件处理方法去执行。
开始事件的存储,在 react 里所有事件的触发都是通过 dispatchEvent方法统一进行派发的,而不是在注册的时候直接注册声明的
回调,来看下如何存储的 。
react 把所有的事件和事件类型以及react 组件进行关联,把这个关系保存在了一个 map里,也就是一个对象里(键值对),然后在事件触发的时候去根据当前的 组件id和 事件类型查找到对应的 事件fn
3、事件执行
1、进入统一的事件分发函数(dispatchEvent)
2、结合原生事件找到当前节点对应的ReactDOMComponent对象
3、开始 事件的合成
- 根据当前事件类型生成指定的合成对象
- 封装原生事件和冒泡机制
- 在 listenerBank事件池中查找事件回调并合成到 event(合成事件结束)
4.处理合成事件内的回调事件(事件触发完成 end)
(4)合成事件、原生事件之间的冒泡执行关系
结论:
原生事件阻止冒泡肯定会阻止合成事件的触发。
合成事件的阻止冒泡不会影响原生事件。
原因:
浏览器事件的执行需要经过三个阶段,捕获阶段-目标元素阶段-冒泡阶段。
节点上的原生事件的执行是在目标阶段,然而合成事件的执行是在冒泡阶段,所以原生事件会先合成事件执行,然后再往父节点冒泡,所以原生事件阻止冒泡会阻止合成事件的触发,而合成事件的阻止冒泡不会影响原生事件。
6.React组件之间如何通信?
(1)父传子
⽗组件可以通过向⼦组件传 props 的⽅式来实现
(2)子传父
可以采用props+回调的方式
(3)兄弟组件通信
可以通过兄弟节点的共同父节点,由父节点转发信息,实现兄弟间通信
(4)跨层级通信
context状态树
(5)发布者订阅者模式
发布者发布事件,订阅者监听到事件后做出反应,可以通过引⼊ event 模块进⾏
(6)全局状态管理工具
可以借助 Redux 或 Mobx以及react-redux 等全局状态管理⼯具进⾏通信,它们会维护⼀个全局状态中⼼(Store),并可以根据不同的事件产⽣新的状态
(7)hooks中帮我们封装好的
useContext和useReducer
7.说说你对受控组件和非受控组件的理解?应用场景?
受控组件
就是受我们控制的组件,组件的状态全程响应外部数据
我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是input标签是一个可读的状态
如果想要解除被控制,可以为input标签设置onChange事件,输入的时候触发事件函数,在函数内部实现state的更新,从而导致input框的内容页发现改变
受控组件我们一般需要初始状态和一个状态更新事件函数
非受控组件
就是不受我们控制的组件
一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态
应用场景
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理
如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少
8.说说你对fiber架构的理解?解决了什么问题??
理解
React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认,React Fiber 在React 16 版本发布
在react中,主要做了以下的操作:
- 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
- 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
- dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
从架构角度来看,Fiber 是对 React核心算法(即调和过程)的重写
从编码角度来看,Fiber是 React内部所定义的一种数据结构,它是 Fiber树结构的节点单位,也就是 React 16 新架构下的虚拟DOM
解决的问题
Fiber把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行
即可以中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为 React Element 对应的 Fiber节点
实现的上述方式的是requestIdleCallback方法
window.requestIdleCallback()方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应
首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。
该实现过程是基于 Fiber节点实现,作为静态的数据结构来说,每个 Fiber 节点对应一个 React element,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。
作为动态的工作单元来说,每个 Fiber 节点保存了本次更新中该组件改变的状态、要执行的工作。
每个 Fiber 节点有个对应的 React element,多个 Fiber节点根据如下三个属性构建一颗树:
// 指向父级Fiber节点
this.return = null
// 指向子Fiber节点
this.child = null
// 指向右边第一个兄弟Fiber节点
this.sibling = null
9.说说react diff的原理是什么?
理解
跟Vue一致,React通过引入Virtual DOM的概念,极大地避免无效的Dom操作,使我们的页面的构建效率提到了极大的提升
而diff算法就是更高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处
原理
react中diff算法主要遵循三个层级的策略:
1.tree层级
2.conponent 层级
3.element 层级
tree层级
DOM节点跨层级的操作不做优化,只会对相同层级的节点进行比较
只有删除、创建操作,没有移动操作
component层级
如果是同一个类的组件,则会继续往下diff运算,如果不是一个类的组件,那么直接删除这个组件下的所有子节点,创建新的
element层级
对于比较同一层级的节点们,每个节点在对应的层级用唯一的key作为标识
提供了 3 种节点操作,分别为 INSERT_MARKUP(插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)
通过key可以准确地发现新旧集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将旧集合中节点的位置进行移动,更新为新集合中节点的位置
10.说说你对redux中间件的理解?常用的中间件有哪些?实现原理?
理解
中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的
类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。
Redux中,中间件就是放在就是在dispatch过程,在分发action进行拦截处理
常用的中间件
redux-thunk:通过action返回一个函数进行异步操作
redux-promise :通过action返回一个promise进行异步处理
redux-logger:用于日志记录
实现原理
中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
中间件函数必须返回一个dispatch创建函数
11.如何使用css实现一个三角形?
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>用CSS绘制一个三角形</title>
<link rel="stylesheet" href="">
<style>
.div{
width: 0px;
height: 0px;
border-width: 30px;
border-style: solid;
border-color: red transparent transparent transparent;
overflow: hidden;
font-size: 0;
line-height: 0;
}
</style>
</head>
<body>
<div class="div"></div>
</body>
</html>
12.什么是强缓存和协商缓存?区别?
强缓存
服务器通过设置http中hdader的Expires和cache-control字段告诉浏览器换存的有效期。这种方法会有一个固定时间,所带来的问题是如果服务器数据进行了更新,但是还没有到强缓存的过期时间,则数据无法更新
- Expires
Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
Expires的值是GMT格式的绝对时间,在设置的时间前浏览器会直接使用本地缓存。 - cache-control
cache-control有12个值,其中的max-age值定义缓存的有效期,单位是秒,例如:cache-control:max-age=700,它表示缓存有效期为700秒,以消息的生成日期为基准,也就是header中的Date字段。
cache-control与Expires的区别在于cache-control的值是相对时间,而Expires是绝对时间,如果我们人为的修改了本地的时间,那么此时本地的时间与服务器上的时间不一致,就会导致缓存不能正确的被使用;而如果用相对时间,不管怎么改变本地的时间,缓存的有效期都不会改变。
协商缓存
简单的说,协商缓存就是通过服务器来判断缓存是否可用。
- Last-Modified/If-Modified-Since 二者的值都是 GMT
格式的时间字符串,Last-Modified标记了文件最后的修改时间,然后在下一次请求时请求头带上If-Modified-Since,其值为本地保存的Last-Modified,告诉服务器我本地缓存最后的修改时间,服务器如果判断文件未修改,则命中协商缓存,返回304
Not Modified状态码,浏览器就不再请求资源,直接使用本底的缓存;如果文件修改,则返回资源内容,新的 Last-Modified
会在 response header 返回,并在下次请求之前更新本地缓存的
Last-Modified,下次请求时,If-Modified-Since会启用更新后的
Last-Modified。缺点是最小判断步长为1秒,无法判断1秒内的多次变化。 - Etag/If-None-Match,对于每一个文件,服务器根据文件本身算出一个哈希值(一般来说是哈希结构)并通过
Etag字段返回给浏览器,然后下一次请求后请求头带上 If-None-Match
字段,服务器会通过比较两者是否一致来判定文件内容是否被改变。与 Last-Modified 不一样的是,当服务器返回 304 Not
Modified 的响应时,由于在服务器上ETag 重新计算过,response header中还会把这个 ETag 返回,即使这个
ETag 跟之前的没有变化。缺点是计算Etag值有性能损耗。
区别
- 如果浏览器命中强缓存,则不需要给服务器发送请求,尽管会返回请求状态码200(from cache);
- 协商缓存有服务器决定是否使用缓存,客户端和服务器之间会发生一次通讯,如果命中协商缓存,则服务器返回304。
13.说说React jsx转换成真实DOM的过程?
使用react.createElement或者JSX编写react组件,实际上所有的JSX代码都会转换成react.createElement内容,babel帮助我们完成了转换的过程,createElement函数对key和ref等特殊的props进行了处理,并获取defaultProps对默认的props进行赋值,并且对传入的子节点进行处理,最终构造成一个dom对象
14. 说说你对@reduxjs/toolkit的理解?和react-redux有什么区别?
@reduxjs/toolkit
一个开箱既用的高效redux开发工具,旨在成为标准的readux逻辑开发模式
react-redux
将所有的组件分为了两大类,ui组件以及容器组件,其中容器组件包含着ui组件,容器组件负责和redux进行交互
15.React render方法的原理,在什么时候会触发?
在类组件和函数组件中,render函数的形式是不同的。
在类组件中render函数指的就是render方法;而在函数组件中,指的就是整个函数组件。
render的执行时机主要分成了两部分:
类组件调用 setState 修改状态:
函数组件通过useState hook修改状态
16. React性能优化的手段有哪些?
1、使用纯组件;(PureComponent)
2、使用 React.memo 进行组件记忆(React.memo 是一个高阶组件),对 于相同的输入,不重复执行;
3、如果是类组件,使用 shouldComponentUpdate(这是在重新渲染组件之前触发的其中一个生命周期事件)生命周期事件,可以利用此事件来决定何时需要重新渲染组件;
4、路由懒加载;
5、使用 React Fragments 避免额外标记;
6、不要使用内联函数定义(如果我们使用内联函数,则每次调用“render”函数时都会创建一个新的函数实例);
7、避免在Willxxx系列的生命周期中进行异步请求,操作dom等;
8、如果是类组件,事件函数在Constructor中绑定bind改变this指向;
9、避免使用内联样式属性;
10、优化 React 中的条件渲染;
11、不要在 render 方法中导出数据;
12、列表渲染的时候加key;
13、在函数组件中使用useCallback和useMemo来进行组件优化,依赖没有变化的话,不重复执行;
14、类组件中使用immutable对象;
17. 如何通过原生js实现一个节流函数和防抖函数?
本质上是优化高频率执行代码的一种手段
防抖
当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间(非常短的时间);
当事件密集触发时,函数的触发会被频繁的推迟;
只有等待了一段时间也没有事件触发,才会真正的执行响应函数;
应用场景:
输入框中频繁的输入内容,搜索或者提交信息;
频繁的点击按钮,触发某个事件;
监听浏览器滚动事件,完成某些特定操作;
用户缩放浏览器的resize事件;
节流
当事件触发时,会执行这个事件的响应函数;
如果这个事件会被频繁触发,那么节流函数会按照一定的频率来执行函;
不管在这个中间有多少次触发这个事件,执行函数的频繁总是固的;
应用场景:
游戏中的一些设计--王者荣耀 英雄的普攻;
监听页面的滚动事件;
鼠标移动事件;
用户频繁点击按钮操作;
18.说说webpack中常见的loader?解决了什么问题?
是什么
loader 用于对模块的源代码进行转换,在 import 或"加载"模块时预处理文件
webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中
常见的loader
- style-loader: 将css添加到DOM的内联样式标签style里
- css-loader :允许将css文件通过require的方式引入,并返回css代码
- less-loader: 处理less
- sass-loader: 处理sass
- postcss-loader: 用postcss来处理CSS
- autoprefixer-loader: 处理CSS3属性前缀,已被弃用,建议直接使用postcss
- file-loader: 分发文件到output目录并返回相对路径
- url-loader: 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
- html-minify-loader: 压缩HTML
- babel-loader :用babel来转换ES6文件到ES
解决的问题
在webpack内部中,任何文件都是模块,不仅仅只是js文件
默认情况下,在遇到import或者load加载模块的时候,webpack只支持对js文件打包
像css、sass、png等这些类型的文件的时候,webpack则无能为力,这时候就需要配置对应的loader进行文件内容的解析
19. 说说如何借助webpack来优化前端性能?
随着前端的项目逐渐扩大,必然会带来的一个问题就是性能
尤其在大型复杂的项目中,前端业务可能因为一个小小的数据依赖,导致整个页面卡顿甚至奔溃
一般项目在完成后,会通过webpack进行打包,利用webpack对前端项目性能优化是一个十分重要的环节
通过webpack优化前端的手段有:
• JS代码压缩
• CSS代码压缩
• Html文件代码压缩
• 文件大小压缩
• 图片压缩
• Tree Shaking,Tree Shaking 是一个术语,在计算机中表示消除死代码
• 代码分离
• 内联 chunk
关于webpack对前端性能的优化,可以通过文件体积大小入手,其次还可通过分包的形式、减少http请求次数等方式,实现对前端性能的优化
20.说说javascript内存泄漏的几种情况?
是什么
内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存
并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费
程序的运行需要内存。只要程序提出要求,操作系统或者运行时就必须供给内存
对于持续运行的服务进程,必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃
垃圾回收机制
Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存
原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存
通常情况下有两种实现方式:
- 标记清除
- 引用计数
意外的全局变量
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'); // 解除引用