学习闭包

1. 闭包的定义

外层函数嵌套内层函数, 内层函数使用外层函数的局部变量,把内层函数做为外层函数的返回值。面试

function A() {
   let a = 1
   function B() {
      console.log(a)
  }
  return B
 }
复制代码

2.闭包的应用

用闭包解决递归问题闭包

function  factorial(num) {
    if(num<= 1) {
        return 1;
    } else {
       return num * factorial(num-1)
    }
 }
 var anotherFactorial = factorial
 factorial = null
 anotherFactorial(4)   // 报错 。
 //最好是return num* arguments.callee(num-1),arguments.callee指向当前执行函数,可是在严格模式下不能使用该属性也会报错,因此借助闭包来实现

 // 使用闭包实现递归
 function newFactorial = (function f(num){
     if(num<1) {return 1}
     else {
        return num* f(num-1)
     }
 }) 
 //这样就没有问题了,实际上起做用的是闭包函数f,而不是外面的函数newFactorial
复制代码

用闭包模仿块级做用域异步

  • 例1:函数

    for(var i=0; i<10; i++){
         console.log(i)
    }
    alert(i)  // 变量提高,弹出10
    
    //为了不i的提高能够这样作
    (function () {
        for(var i=0; i<10; i++){
             console.log(i)
        }
    )()
    alert(i)   // undefined   由于i随着闭包函数的退出,执行环境销毁,变量回收
    复制代码
  • 例2:性能

    for (var i = 0; i < 5; i++) {
        (function(i) {
            setTimeout(function() {
                console.log(i)
            }, 1000);
        })(i);
    }
    复制代码

封装私有变量学习

function create_counter(initial) {
        var x = initial || 0;
        return {
            inc: function () {
                x += 1;
                return x;
            }
        }
   }
   var c1 = create_counter();
   c1.inc(); // 1
   c1.inc(); // 2
   c1.inc(); // 3

   var c2 = create_counter(10);
   c2.inc(); // 11
   c2.inc(); // 12
   c2.inc(); // 13
复制代码

在返回的对象中,实现了一个闭包,该闭包携带了局部变量x,而且,从外部代码根本没法访问到变量x。换句话说,闭包就是携带状态的函数,而且它的状态能够彻底对外隐藏起来。this

3.闭包的做用

  • 读取函数内部变量spa

  • 让变量的值始终保持在内存中code

4.闭包的注意事项

一般,函数的做用域及其全部变量都会在函数执行结束后被销毁,被垃圾回收机制回收。可是,在建立了一个闭包之后,这个函数的做用域就会一直保存到闭包不存在为止。对象

function makeAdd(x) {
    return function(y) {
      return x + y;
    };
  }

  var add1 = makeAdder(5);
  var add2 = makeAdder(10);

  console.log(add1(4));  // 9
  console.log(add2(3)); // 13

  // 释放对闭包的引用
  add5 = null;
  add10 = null;
复制代码

闭包只能取得包含函数中任何变量的最后一个值,这是由于闭包所保存的是整个变量对象,而不是某个特殊的变量。

function test(){
    var arr = [];
    for(var i = 0;i < 10;i++){
      arr[i] = function(){
        return i;
       };
    }
    for(var a = 0;a < 10;a++){
       console.log(arr[a]());
    }
  }
  test(); // 连续打印 10 个 10
复制代码

闭包中的this

var name = "The Window";
 var obj = {
   name: "My Object",
   getName: function(){
       var that = this;
       return function(){
          return that.name;
      };
    }
 };
 console.log(obj.getName()());  // The Window
 //将这一部分解:console.log( function(){return this.name;};() ); 
复制代码

改变做用域

var name = "The Window";
 var obj = {
   name: "My Object",
   getName: function(){
       var that = this;
       return function(){
          return that.name;
      };
    }
 };
 console.log(obj.getName()());  // The Window
 //将这一部分解:console.log( function(){return this.name;};() ); 
复制代码

5.闭包的缺点

  • 闭包的缺点就是常驻内存会增大内存使用量,而且使用不当很容易形成内存泄露。

  • 若是不是由于某些特殊任务而须要闭包,在没有必要的状况下,在其它函数中建立函数是不明智的,由于闭包对脚本性能具备负面影响,包括处理速度和内存消耗。

6.关于闭包的面试题

这些面试题很是有意思,坚持一下,来我们继续往下看>_<

第一个JS闭包问题

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?

//答案:
//a: undefined,0,0,0
//b: undefined,0,1,2
//c: undefined,0,1,1
复制代码

(1)先肯定这三个函数的关系

这段代码中出现了三个fun函数,因此第一步先搞清楚,这三个fun函数的关系,哪一个函数与哪一个函数是相同的。

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      //...
    }
  };
}
复制代码

先看第一个fun函数,属于标准具名函数声明,是新建立的函数,他的返回值是一个对象字面量表达式,属于一个新的object。这个新的对象内部包含一个也叫fun的属性,经过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新建立匿名函数表达式。

注意:全部声明的匿名函数都是一个新函数。

因此第一个fun函数与第二个fun函数不相同,均为新建立的函数。最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。因此,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。

(2)函数是怎样调用的

为了方便看把代码从新写一下

function fun(n,o) {
  console.log(o)
  return {
    fun:function(m){
      return fun(m,n);
    }
  };
}
var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);//undefined,?,?,?
var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
//问:三行a,b,c的输出分别是什么?
复制代码

第一行 a

var a = fun(0);  a.fun(1);  a.fun(2);  a.fun(3);
复制代码

第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,因此:第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。

遂:

  • 在第一次调用fun(0)时,o为undefined;

  • 第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);因此o为0;

  • 第三次调用fun(2)时m为2,但依然是调用a.fun,因此仍是闭包了第一次调用时的n,因此内部调用第一层的fun(2,0);因此o为0。

  • 第四次同理;

即:最终答案为 undefined,0,0,0

第二行 b

var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
复制代码

先从fun(0)开始看,确定是调用的第一层fun函数;而他的返回值是一个对象,因此第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。

遂:

  • 在第一次调用第一层fun(0)时,o为undefined;

  • 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);因此o为0;

  • 第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)因此n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),因此o为1;

  • 第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);因此o为2;

即最终答案:undefined,0,1,2

第三行 c

var c = fun(0).fun(1);  c.fun(2);  c.fun(3);//undefined,?,?,?
复制代码

根据前面两个例子,能够得知:

fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,因此c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。

遂:

  • 在第一次调用第一层fun(0)时,o为undefined;

  • 第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);因此o为0;

  • 第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);因此o为1;

  • 第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),因此o还为1

即最终答案:undefined,0,1,1

第二个JS闭包问题

循环中使用闭包解决 var 定义函数的问题

for ( var i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
复制代码

首先由于 setTimeout 是个异步函数,全部会先把循环所有执行完毕,这时候 i就是 6 了,因此会输出一堆 6。

解决办法两种,第一种使用闭包

for (var i = 1; i <= 5; i++) {
  (function(j) {
    setTimeout(function timer() {
      console.log(j);
    }, j * 1000);
  })(i);
}
复制代码

第二种就是使用 setTimeout 的第三个参数

for ( var i=1; i<=5; i++) {
	setTimeout( function timer(j) {
		console.log( j );
	}, i*1000, i);
}
复制代码

第三种就是使用 let 定义 i 了

for ( let i=1; i<=5; i++) {
	setTimeout( function timer() {
		console.log( i );
	}, i*1000 );
}
复制代码

由于对于 let 来讲,他会建立一个块级做用域,至关于

{ // 造成块级做用域
  let i = 0
  {
    let ii = i
    setTimeout( function timer() {
        console.log( ii );
    }, i*1000 );
  }
  i++
  {
    let ii = i
  }
  i++
  {
    let ii = i
  }
  ...
}
复制代码

若是您有更好的建议,或者对该篇文章的知识补充,请留言,实践后会及时补充的哦,谢谢>-<!

该篇文章多方参考,学习笔记。

相关文章
相关标签/搜索