闭包

闭包是js的一个难点也是它的一个特色,是我们必须掌握的js高级特性,那么什么是闭包呢?它又有什么用呢?

闭包有3个特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收

js的作用域分两种,全局和局部,基于我们所熟悉的作用域链相关知识,我们知道在js作用域环境中访问变量的权利是由内向外的,内部作用域可以获得当前作用域下的变量并且可以获得当前包含当前作用域的外层作用域下的变量,反之则不能,也就是说在外层作用域下无法获取内层作用域下的变量,同样在不同的函数作用域中也是不能相互访问彼此变量的,这被称作 链式作用域"结构。

那么我们想在一个函数内部也有限权访问另一个函数内部的变量该怎么办呢?出于种种原因,我们有时候需要获取到函数内部的局部变量。正常情况下,这是办不到的!只有通过变通的方法才能实现。闭包就是用来解决这一需求的,闭包的本质就是在一个函数内部创建另一个函数。

闭包就是能够读取其他函数内部变量的函数。

由于在javascript中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

function a() {
  var name = 'dov';
  return function(){
    return name;
  }
}
 var b =a();
 console.log(b());

在这段代码中,a()中的返回值是一个匿名函数,这个函数在a()的作用域内部,所以它可以获取a()作用域下变量name的值,将这个值作为返回赋值给全局作用域下的变量b,实现了全局变量下获取到局部变量中的变量的值

function f(){
   var num = 3;
   return function(){
       var n = 0;
       console.log(++n);
       console.log(++num);
   }
}

var a = f()
a()
a()

一般情况下,在函数fn执行完后,就应该连同它里面的变量一同被销毁,匿名函数作为fn的返回值被赋值给了a,这个时候就相当于a = function(){var n = 0 ...},并且匿名函数内部引用这a里的变量num, 所以num变量无法被销毁,而变量n是每次被调用是重新创建,所以每次a执行完后它就把属于自己的变量连同自己一起销毁,于是乎最后就剩下孤零零的num,于是就产生了内存消耗的问题。

这些变量的值始终保持在内存中,不会在匿名函数调用后被自动清除。

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

使用闭包的注意点

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

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

1什么是内存泄露?

1. 定义:一块被分配的内存既不能使用,也不能回收。从而影响性能,甚至导致程序崩溃。

 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占有的内存。然而由于一些原因导致在这种机制下内存管理器不能正确解读JavaScript变量的生命周期,从而没有释放其内存,而也没有再被使用。

 循环引用是导致以上情况的主要原因之一。

2 解决办法

常用的解决方法就是在JavaScript代码段运行完之时将形成循环引用的JavaScript对象手动设置为空,切断引用。

再来看一个经典例子-定时器与闭包

写一个for循环,让它按顺序打印出当前循环次数

for (var i = 0;i<5; ++i){
setTimeout( function() {
                   console.log(i + ' ');
                   },100
                 );
}

按照预期他应该一次输出1 2 3 4 5, 而结果它输出了五次 5,这是为什么呢?原来由于单线程的,所以在执行for循环的时候定时器setTimeout被安排到人物队列中排队等待执行,而在等待过程中for循环就已经在执行了,等到setTimeout可以执行的时候,for循环已经结束,i的值也已经编程到5,所以打印出来五个5,那么为了实现预期结果,怎么更改这段代码呢?

引入闭包来保存变量i,将setTimeout放入函数中,将for循环中的循环值i作为参数传递,

for(var i = 0; i<5; ++i){
 ( function(i){
    setTimeout(function(){
      console.log(i+' ');
    },100)
    })(i)
}

闭包作为参数传递

var num =15;
var a = function(x){
  if(x>num){
    console.log(x)
  }
}

void function(bb){
  var num=100
  bb(30)
}(a)

在这段代码中,函数a作为参数传入立即执行函数中,在执行到bb(30)的时候,30作为参数传入a中,这时候if(x>num)中的num取的并不是立即执行函数中的num,而是取创建函数的作用域中的num。这里函数创建的作用域是全局作用域,所以num取的是全局作用域中的值15,即30>15,打印30

下面这个JS程序的输出是什么:

function Foo() {
    var i = 0;
    return function() {
        console.log(i++);
    }
}
 
var f1 = Foo(),
    f2 = Foo();
f1();
f1();
f2();
0 1 0
0 1 2
0 0 0
0 0 2

正确答案:  0 1 0

这道题考察闭包和引用类型对象的知识点:
1.一般来说函数执行完后它的局部变量就会随着函数调用结束被销毁,但是此题foo函数返回了一个匿名函数的引用(即一个闭包)它可以访问到foo()被调用产生的环境,而局部变量i一直处在这个环境中,只要一个环境有可能被访问到,它就不会被销毁,所以说闭包有延续变量作用域的功能。这就好理解为什么:

f1();//0
f1();//1

其实foo()返回的是一个匿名函数,所以f1,f2相当于指向了两个不同的函数对象

f2();//0

查了红宝石,要点如下:
(1)Function是引用类型:保存在堆中,变量f1,f2是保存在栈中;
(2)闭包:一个函数(产生新的作用域)定义的局部变量、子函数的作用域在函数内,
         但是一旦离开了这个函数,局部变量就无法访问,所有通过返回子函数到一个变量f1的方法,让
         f1指向堆中的函数作用域,这样可以使用局部变量i.
(3)   过程:
   第一次f1()  :f1=Foo()中,先执行Foo(): i = 0,return值返回给f1
 (f1指向子函数   f1()=function(){.....},因为子函数没有 定义i,所以向上找到父函数定义的 i:  )并执行子函数 输出i=0,再自加 i =1(覆盖了父函数Foo 的 i值);
  第二次f1() : 执行的是子函数 Function(){  ..},输出的是父函数 的 i=1,再自加 i =2;
  第一次f2():同第一次f1(),不同的是 f2指向堆中一个新的对象 function(){ ...},所有此i非彼i,输出i=0;如果
               如果再次f2(),那么和第二次f1(),一样输出i=1; 
 
 
最后总结一下闭包的好处与坏处
好处:
      保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突。
坏处:
      被引用的私有变量不能被销毁,增大了内存的消耗,造成内存泄漏,解决方法是在使用之后手动维他赋值null
      其次由于闭包涉及跨域访问,所以会导致性能损失,我们可以把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻执行速度的影响。
未完,待续......
原文地址:https://www.cnblogs.com/zhishiyv/p/12101772.html