JavaScript闭包

预备知识:

变量
  • 全局变量
  • 局部变量
    Javascript语言特点:
    • 函数内部可以读取外部的全局变量
    • 函数外部无法读取内部变量
  var n = 1;
  var foo = function() {
    console.log(n);  
  }
  foo();  //1

Javascript的变量声明

见《let VS var》

闭包是什么

ECMAScript 中给闭包的定义是:闭包,指的是词法表示包括不被计算的变量的函数,也就是说,函数可以使用函数之外定义的变量。
有点晦涩,那就举例子。

var foo1 = function () {
  var n = 999;
  function foo2() {
    console.log(n); 
  }
  return foo2;
}
var result=foo1();
result(); // 999

正常情况下,在foo1外部是无法获取到foo1函数内部的局部变量的,因为var n 的作用域是foo1的函数作用域。但是通过在foo1中将foo2作为return出来,即可获取foo1的内部函数foo2,而对于foo2来说,var n是可见的,所以在foo2中操作n是可行的。所以,即实现了,在foo1的外部,通过foo2这个桥,实现了操作foo1内部变量的功能

小结(什么是闭包)

  • 闭包是一个函数,满足以下条件
  • 闭包使用其他函数定义的变量,使其不被销毁。如foo2操作n,使n不被回收
  • 闭包存在定义该变量的作用域中,两者是同一个作用域。如foo2和n是同一个作用域。

闭包为什么(存在意义)

一般有两个作用:

  • 函数外部获取函数内部变量的值(类比对象的public method&private variable)
  • 内存保持,不被垃圾回收机制释放。如在上图中,result不被释放,则foo1的函数作用域中的变量被全部保存在内存当中。
var result = foo1();

闭包怎么用

正常用法

没啥说的,抛代码吧。
eg1.

var foo1 = function () {
  var n = 999;
  function foo2() {
    console.log(n); 
  }
  return foo2;
}
var result=foo1();
result(); // 999

eg2.

for (var i = 0; i < 4; i++) {
    setTimeout(function () {
        console.log(i)
    }, 0)
}
//  4 4 4 4

参考《let VS var》中对var声明的全局变量部分对这段代码的分析。而通过闭包如下,在不适用let声明i的情况下实现预期的0-3的打印。

for (var i = 0; i < 4; i++) {
    (function (i) {
        setTimeout(function () {
            console.log(i)
        }, 0)
    })(i)
}  //0 1 2 3

setTimeout即成为了一个闭包,并被迭代调用4次,然后放在了loop的队列尾。每次的i都被setTimeout保持在内存中不被释放,直到console.log结束。

重点关注(可能导致的异常)
  • 内存管理
    • 正如闭包的作用,保持内存,这可能造成内存管理的混乱。导致局部变量无法被正常释放而一直像全局变量一样被占用内存。如果需要回收这些变量占用的内存,可以手动将变量设置为null。
    • DOM对象和JavaScript对象的循环依赖导致内存无法释放
      问题代码:
function func() {
    var test = document.getElementById('test');
    test.onclick = function () {
        console.log('hello world');
    }
}

问题分析
两个对象:JavaScript对象test,DOM对象简称为'testId'
在func方法中,test引用了DOM对象testId, 然后以test为中转,给testId这样一个DOM对象的onclick事件绑定了一个闭包。如果在被绑定闭包中,访问test这个JavaScript对象。
那么,就形成了互相依赖:

  • test依赖testId(引用)
  • testId(的点击事件handler)依赖test(在绑定事件中的handler中操作了test)

循环依赖的结果导致,这两个对象都没办法被释放。

解决方案1:

function func() {
    var test = document.getElementById('test');
    test.onclick = function () {
        console.log('hello world');
    }
    test = null;
}

解决思路:干掉作为“中转变量”只是为了绑定click handler的test,打破循环。(类比死锁解决方案中的检测与解除--剥夺)

解决方案2:

function func() {
    var test = document.getElementById('test');
    test.onclick = testClickHandler()
}
var testClickHandler = function() {
    console.log('hello world');
}

解决思路:打破闭包,使click的handler无法再满足闭包条件,来访问func内部变量,从根源(闭包的产生的三条件)上杜绝相互依赖的发生。(类比死锁解决方案中的预防--破坏形成死锁的先决条件)


补充

  • 待补充模块:
    • JavaScript Garbage Collection
    • JavaScript->this, self
    • JavaScript变量的声明、赋值、和引用(地址)
    • JavaScript的深浅拷贝
  • Refs:
    • http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
    • https://github.com/lin-xin/blog/issues/8

你可能感兴趣的:(JavaScript闭包)