通过Chrome devtool和面试题,深入理解JavaScript闭包(下)

·  阅读 92
通过Chrome devtool和面试题,深入理解JavaScript闭包(下)

「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战

在前面的文章中,我们分别介绍了

今天我们将通过Chrome devtool的PerformanceMemory来排查程序中的内存泄漏情况。然后我们将通过4道经典的闭包面试题来深入巩固闭包的知识。

使用Chrome devtool来排查内存泄漏情况

var array = []

function createNodes() {
  let div
  let i = 100
  let fragment = document.createDocumentFragment()
  for (; i > 0; i--) {
    div = document.createElement('div')
    div.appendChild(document.createTextNode(i))
    fragment.appendChild(div)
  }
  document.body.appendChild(fragment)
}

function badCode() {
  array.push([...Array(100000).keys()])
  createNodes()
  setTimeout(badCode, 1000)
}

badCode()
复制代码

上面代码递归调用了badCode函数,每次向array数组中写入100000个数字的新数组,变量array为全局变量并且没有手动释放内存的操作,垃圾回收机制也不会处array变量,这就导致了内存泄漏;同时badCode函数还调用了createNodes函数,每秒创建100个div元素插入到body尾部。

我们先把上面的代码保存到一个HTML文件中,然后在Chrome中打开。

这时我们打开Chrome devtool开发者工具,点击Performance

点击这个录制按钮

image.png

大约等待20-30秒后,点击Stop按钮

image.png

从图中我们可以看到,JS Heap和Nodes线随着时间一直在上升,并没有被垃圾回收机制回收。因此,可以判断这段代码中可能存在内存泄漏的风险。

而正常的网页可以看到随着时间的推移,JS Heap和Nodes都被垃圾回收正常的释放。

image.png

如果我们不知道出问题的代码的位置,我还需要使用Memory标签,对JS Heap中的每一项,尤其是Size较大的前几项进行排查。

我们可以很明显的看出这个array数组的问题。

image.png

一个array的变量占用了高达了180MB的内存。

image.png

通过上图我们可以发现问题是出现在全局Window中的array变量。

以上就是我们通过Chrome开发者工具来帮助我们排查可能出现的内存泄漏问题,在日常的工作中我们也可以经常用到。

实战面试题目

题一

下面代码将输出什么?

var foo = (function() {
  var v = 0
  return () => {
    return v++
  }
}())

for (let i = 0; i < 10; i++) {
  foo()
}

console.log(foo())
复制代码

这道题将输出10,下面我们来一步步分析

foo是一个立即自执行函数,当我们打印foo可以看到

var foo = (function() {
  var v = 0
  return () => {
    return v++
  }
}())

console.log(foo)

// 输出
/*
() => {
  return v++
} 
*/
复制代码

在执行foo函数时,变量v自增10次,最后执行foo时,得到10。

题二

下面代码将输出什么?

var foo = () => {
  var arr = []
  var i
  
  for (i = 0; i < 10; i++) {
    arr[i] = function(){
      console.log(i)
    }
  }
  
  return arr[0]
}

foo()()
复制代码

这道题将输出10,这道题也是一道常考题。

这道题类似上面的那道题,执行foo函数返回的是arr[0],那arr[0]就是下面的匿名函数

function(){
  console.log(i)
}
复制代码

当运行这个函数的时候,就需要去找变量i的值,那么for循环结束之后变量i就变成了10。

所以运行这个函数就将会输出10。

题三

下面代码将输出什么?

var fn = null

var foo = () => {
  var a = 2
  function innerFoo() {
    console.log(a)
  }
  fn = innerFoo
}

var bar = () => {
  fn()
}

foo()
bar()
复制代码

正常来说,根据调用栈的知识,foo函数执行完毕后,其执行上下文也会被释放。但是通过将innerFoo函数赋值给了全局变量fn,那么foo内的变量a也会被保留下来。所以函数fn在被执行时,依然可以访问到这个变量,所以将输出2

题四

我们将题三中的代码稍加修改,执行下面代码将输出什么?

var fn = null
var foo = () => {
  var a = 2
  function innerFoo () {
    console.log(c)
    console.log(a)
  }
  fn = innerFoo
}

var bar = () => {
  var c = 100
  fn()
}

foo()
bar()
复制代码

答案是报错:Uncaught ReferenceError: c is not defined

因为在执行fn函数时,fn已经被复制为innerFoo,变量c并不存在其作用域链上,c只是函数bar的局部变量。

我们可以打个断点清楚的看到,在其作用域中并没有变量c。

image.png

欢迎我的公众号【小帅的编程笔记】,让自己和他人都能有所收获!

分类:
前端
收藏成功!
已添加到「」, 点击更改