Javascript闭包

62 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第19天,点击查看活动详情

一句话概括闭包是如何形成的:外层函数调用后,它(外层函数)的作用域对象,被返回的内层函数的作用域链引用着,无法释放,就形成了闭包对象。

闭包缺点:由于闭包藏得很深,几乎找不到,所以,极容易造成内存泄露

解决方法:及时释放不用的闭包。

题目1:

function f1() {
  var sum = 0;
  function f2() {
    sum++;
    return f2;
  }
  f2.valueOf = function() { // 当一个对象试图转为数字时,默认会调用 .valueOf()
    return sum;
  }
  f2.toString = function() { // 当一个对象试图转为字符串时,默认会调用 .toString()
    return sum + "";
  }
  return f2;
}
console.log(+f1()); // 0
console.log(+f1()()); // 1
console.log(+f1()()()); // 2
  • 函数 f1 中有两个参数,sumf2
    • 其中 f2 引用了 sum
  • f1 返回了 f2

上述 f1 函数中,执行完成后内部参数本应该被销毁,但是由于返回了 f2,所以 f2 不会被销毁,并且 f2 中引用了 sum,所以 sum 也不会被销毁

这就产生了闭包

题目2:

动态生成 4 * 4 表格,每个表格中有坐标(0,0) - (3,3) 点击格增加点击次数,且每个格互不干扰,次数通过弹窗显示

<style>
  #container {
    width: 200px;
    height: 200px;
    border-radius: 5px;
    background-color: purple;
  }
  #container>div{
    width: 40px;
    height: 40px;
    background-color: #fff;
    margin-top: 8px;
    margin-left: 8px;
    float: left;
    border-radius: 4px;
    line-height: 40px;
    text-align: center;
  }
</style>
<body>
  <div id="container">
  </div>
  <script>
    ...
  </script>
</body>

方案一:

匿名函数自调用

缺点:每个n完全隔离,每个格子的 n 和 n 之间无法进行计算操作

var container = document.getElementById("container");
for(let r=0; r<4; r++) {
  for(let c=0; c<4; c++) {
    var cell = document.createElement("div");
    cell.innerHTML = `(${r}, ${c})`
    container.appendChild(cell);
    cell.onclick = (function() { // 外面套一层函数,闭包,匿名函数自调用
      var n = 0;
      return function() {
        n++;
        alert(`点了${n}次`)
      }
    })()
  }
}

方案二:

使用二维数组。每个按钮的单击事件处理函数中应该只保存自己对应的元素下标位置。当点击时,通过位置信息找到二维数组中自己的元素值,进行修改

var arr = [
  [0, 0, 0, 0],
  [0, 0, 0, 0],
  [0, 0, 0, 0],
  [0, 0, 0, 0],
]
var container = document.getElementById("container");
for(let r=0; r<4; r++) {
  for(let c=0; c<4; c++) {
    var cell = document.createElement("div");
    cell.innerHTML = `(${r}, ${c})`
    container.appendChild(cell);
    cell.onclick = (function(r, c) {
      return function() {
        arr[r][c]++;
        alert(`点击了${arr[r][c]}次`)
      }
    })(r, c) // 将外部循环过程中 r 和 c 的值传入
  }
}
// 优化:匿名函数自调用,确保全局无法访问 arr
(function() {
  var arr = [
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
    [0, 0, 0, 0],
  ]
  ...
})()