JavaScript之闭包

闭包

MDN
面试官问我什么是闭包该如何回答
廖雪峰博客-闭包
阮一峰博客-闭包

个人理解

内部函数可以访问外部函数的作用域, 如果在内部函数持有了外部函数的变量等, 并将内部函数return出去,导致这个变量不能被销毁

function outer() {
    var a= 1;  // 定义一个内部变量
    return function () {
        return a; // 返回a的变量值
    }
}

var b = outer()

console.log(b()) ===> 1

产生一个闭包

闭包的作用域链包含着它自己的作用域,以及包含它的函数的作用域和全局作用域。

function func() {
    let a = 1, b = 2
    function closure() { // 闭包
        return a + b // 返回a+b的值
    }
    return closure // 返回闭包函数
}

闭包的注意事项

通常,函数的作用域及其所有变量都会在函数执行结束后被销毁。但是,在创建了一个闭包以后,这个函数的作用域就会一直保存到闭包不存在为止。

function makeAddr(x) {
    return function(y) {
        return x+y
    }
}

var add5 = makeAddr(5)
var add10 = makeAddr(10)

console.log(add5(2))  ===> 7
console.log(add10(10)) ===> 12


add5 = null
add10 = null
function f1(){

    var n=999;

    nAdd=function(){n+=1}

    function f2(){
      alert(n);
    }

    return f2;

  }

  var result=f1();

  result(); // 999

  nAdd();

  result(); // 1000

在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。

注意点

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

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

  3. 返回的函数并没有立即执行,而是直到调用了才执行

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push(function () {
            return i * i;
        });
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];



f1() ===> 16
f2() ===> 16
f3() ===> 16

将var改成let即可正确输出

全部都是16!原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了4,因此最终结果为16。返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

function count() {
    var arr = [];
    for (var i=1; i<=3; i++) {
        arr.push((function (n) {
            return function () {
                return n * n;
            }
        })(i));
    }
    return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

注意这里用了一个“创建一个匿名函数并立刻执行”的语法:

(function(n){
    return n*n;
})(3); ===> 9

闭包中的this

  var name = "The Window";

  var object = {
    name : "My Object",

    getNameFunc : function(){
      return function(){
        return this.name;
      };

    }

  };

  alert(object.getNameFunc()());
===> The Window

在上面这段代码中,obj.getName()()实际上是在全局作用域中调用了匿名函数,this指向了window。这里要理解函数名与函数功能是分割开的,不要认为函数在哪里,其内部的this就指向哪里。window才是匿名函数功能执行的环境。

如果想使this指向外部函数的执行环境,可以这样改写:

  var name = "The Window";
  var object = {
    name : "My Object",
    getNameFunc : function(){
      var that = this;
      return function(){
        return that.name;
      };
    }
  };

  alert(object.getNameFunc()());
===> My Object

在闭包中,arguments与this也有相同的问题。下面的情况也要注意:

  var name = "The Window";
  var object = {
    name : "My Object",
    getName : function(){
      retuen this.name;
    }
  };

object.getName() ===> My Object

(Object.getName = Object.getName)()  ===> The Window

obj.getName();这时getName()是在对象obj的环境中执行的,所以this指向obj。

(obj.getName = obj.getName)赋值语句返回的是等号右边的值,在全局作用域中返回,所以(obj.getName = obj.getName)();的this指向全局。要把函数名和函数功能分割开来。

闭包的主要应用场景

  1. 私有变量 实现 private的功能
'use strict';

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。换句话说,闭包就是携带状态的函数,并且它的状态可以完全对外隐藏起来

  1. 把多参数的函数变成单参数的函数
'use strict';

function make_pow(n) {
    return function (x) {
        return Math.pow(x, n);
    }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

console.log(pow2(5)); // 25
console.log(pow3(7)); // 343

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