what is闭包,闭包的应用场景

what is closure

闭包的定义: 闭包是指有权访问另一个函数作用域中的变量的函数。

创建闭包的常见方式:就是在一个函数内部创建另一个函数。

通俗解释: Javascript允许使用内部函数—即函数定义和函数表达式位于另一个函数的函数体内。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数和声明的其他内部函数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。

variable scope

Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。

var n = 999;
function f1(){
  alert(n);
}
f1(); // 999

另一方面,在函数外部自然无法读取函数内的局部变量。

function f1(){
  var n = 999;
}
alert(n); // error

这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!

function f1(){
  n = 999;
}
f1();
alert(n); // 999

Access to outer variables

在函数的内部,再定义一个函数。

function f1(){
  var n = 999;
  function f2(){
    alert(n); // 999
  }
}

在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的”链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

function f1(){
  var n = 999;
  function f2(){
    alert(n); 
  }
  return f2;
}
var result = f1();
result(); // 999

此处f2函数,就是闭包。

Benefits of using closures

使用闭包的好处:

  1. 希望一个变量长期驻扎在内存中
  2. 避免全局变量的污染
  3. 私有成员的存在

The Uses of Closures

one:可以读取函数内部的变量,

two:让这些变量的值始终保持在内存中。

function f1() {
    var i = 1;  
    function f2(){
        return i++
    }; 
    return f2 
}         
var fun = f1();  
fun();    // 1 执行后 i++,,然后i还在~  
fun();    // 2   
fun = null;  // i被回收!!

在这段代码中,fun实际上就是闭包f2函数。它一共运行了两次,第一次的值是1,第二次的值是2。这证明了,函数f1中的局部变量i一直保存在内存中,并没有在f1调用后被自动清除。

three: 代码封装、模块化

function a(){
  var n = 0;
  function inc(){
    n++; 
    console.log(n);
  }
  return inc;
}
var c = a();
c();  //控制台输出1
c();  //控制台输出2

Note the use of closure points

1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

闭包的应用场景scenario

one-setTimeout

//原生的setTimeout传递的第一个函数不能带参数
setTimeout(function(param){
    alert(param)
},1000)

//通过闭包可以实现传参效果
function func(param){
    return function(){
        alert(param)
    }
}
var f1 = func(1);
setTimeout(f1,1000);

two-网页事件【如:click、focus】需要传值时

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <link rel="stylesheet" href="">
</head>
<style>
    body{
        font-size: 12px;
    }
    h1{
        font-size: 1.5rem;
    }
    h2{
        font-size: 1.2rem;
    }
</style>
<body>

    <p>哈哈哈哈哈哈</p>
    <h1>hhhhhhhhh</h1>
    <h2>qqqqqqqqq</h2>

    <a href="#" id="size-12">12</a>
    <a href="#" id="size-14">14</a>
    <a href="#" id="size-16">16</a>

<script>
    function changeSize(size){
        return function(){
            document.body.style.fontSize = size + 'px';
        };
    }

    var size12 = changeSize(12);
    var size14 = changeSize(14);
    var size16 = changeSize(16);

    document.getElementById('size-12').onclick = size12;
    document.getElementById('size-14').onclick = size14;
    document.getElementById('size-16').onclick = size16;
    //我们定义行为,然后把它关联到某个用户事件上(点击或者按键)。我们的代码通常会作为一个回调(事件触发时调用的函数)绑定到事件上

</script>
</body>
</html>

three-用闭包模拟私有方法

var makeCounter = function () {
    var privateCounter = 0;
    function changeBy(val){
        privateCounter += val;
    };
    return {
        increment: function(){
            changeBy(1);
        },
        decrement: function(){
            changeBy(-1);
        },
        value: function(){
            return privateCounter;
        }
    }
};
var Counter1 = makeCounter();
var Counter2 = makeCounter();
Counter1.increment();
console.log(Counter1.value()); //1
console.log(Counter2.value()); //0

名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

以这种方式使用闭包,提供了许多与面向对象编程相关的好处 —— 特别是数据隐藏和封装。

four-在循环中创建闭包,找到对应的索引

function createFunctions(){ 
  var result = new Array(); 
  for (var i = 0; i < 10; i++){ 
    result[i] = function(){ 
      return i; 
    }; 
  } 
  return result; 
} 
var funcs = createFunctions(); 
for (var i = 0; i < funcs.length; i++){ 
  console.log(funcs[i]()); 
}

上面代码输出 10 个 10

上面的代码相当于

var result = new Array(), i; 
result[0] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! 
result[1] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! 
... 
result[9] = function(){ return i; }; //没执行函数,函数内部不变,不能将函数内的i替换! 
i = 10; 

funcs = result; 
result = null; 

console.log(i); // funcs[0]()就是执行 return i 语句,就是返回10 
console.log(i); // funcs[1]()就是执行 return i 语句,就是返回10 
... 
console.log(i); // funcs[9]()就是执行 return i 语句,就是返回10

five-Ajax请求成功回调

闭包原理

闭包与this

闭包and内存泄露

function f1() {  
    var i = 1;  
    function f2(){
        return i++
    }; 
    return f2 
}         
var fun = f1();  
fun();    // 1 执行后 i++,,然后i还在~  
fun();    // 2   
fun = null;  // i被回收!!

JS垃圾回收机制: 局部变量在函数执行完成之后就被回收,而全局变量不会被回收直到窗口关闭

闭包导致内存泄露, f1、i、f2 不被回收

为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

more