12-闭包
晴空闲云 614 1

本节目标

  1. 掌握闭包(closure)的概念。
  2. 掌握闭包(closure)的应用。

内容摘要

本篇介绍了闭包的概念,闭包的应用场景:缓存数据和模拟私有变量。

阅读时间30~40分钟。

闭包(closure)介绍

闭包(closure),这个概念现在js很常提及,也基本是面试极易考的一道题。

官方的介绍来看:

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

官方的这段介绍看着是挺绕的,其实简单来讲,我的理解就是:

闭包就是函数和函数内可以访问到的变量的集合。

我们在讲解作用域章节有碰到闭包的情况了,只是没有专门提,现在再来个简单示例看看:

function f1() {
    let j = 2;
    function f2() {
        console.log(j);
    }
    f2();
}
f1();

是不是很眼熟了?这边 f2函数 定义在 f1函数内,f2函数 可以访问 函数f1 内定义的变量,那么在这种场景下:

f2函数和可以访问到的f1函数内的变量构成了一个闭包。

实际上,如果我们打印出f2函数,我们也可以发现闭包的定义:

function f1() {
    let j = 2;
    function f2() {
        console.log(j);
    }
    console.log([f2]); // 打印函数f2
}
f1();

查看其原型链:

12-闭包

讲完了闭包的概念,如果一个东西只是停留在概念,没有啥用,估计也不会流行起来,下面我们来看下闭包的应用。

闭包应用

闭包的作用网上分析文章很多,我的理解来看,就两个作用:

  1. 缓存数据:使得外部函数的变量不会被清除,一直存在内存中。
  2. 模拟私有变量:既读取及操作外部函数的私有变量。

下面对两种作用做一下分析。

缓存数据

假如我们有一个视频APP,需要对视频、评论的点赞数进行计数。我们先尝试定义一个定义一个函数来计算:

function videoCounter(){
    let count = 0;
    count++;
    return count;
}

尝试调用三次:

console.log(videoCounter()); // 1
console.log(videoCounter()); // 1
console.log(videoCounter()); // 1

查看输出结果,每次都是1,照理调用几次应该就输出几才对?

原因是当我们调用函数的时候,函数执行完成后,count变量就被释放了,js中es6之前没有类似static的修饰符,可以让变量常驻内存,这种方法就行不通了。

解决办法,我们这边就可以考虑用闭包来实现了。

1)声明计数函数,返回计数函数:

function counter() {
    let count = 0;
    return function() {
        count++;
        return count;
    }
}

这边 counter 函数返回值是一个函数。

2)使用计数函数构造视频点赞函数:

let videoCounter = counter(); // videoCounter 是一个函数
console.log(typeof videoCounter); // function

构造完成后,videoCounter 函数和 counter 函数的内的变量 count 形成了一个闭包。

当counter函数运行完成后,count变量并不会被释放,这样就可以用来计数。

3)调用视频计数:

console.log(videoCounter()); // 1
console.log(videoCounter()); // 2
console.log(videoCounter()); // 3

结果符合预期,每次调用都在之前的基础上+1了。

模拟私有变量

js中不像java可以将属性和方法声明有私有的,所以可以通过闭包进行模拟。

上面例子中,counter函数 内的 count变量 就是一个私有变量,只能在 counter函数 内访问,只是上例不太明显,我们稍微做下改造。

1)把返回值修改为对象,并且包含incr、decr、value三个方法:

function counter() {
    let count = 0;
    return {
        increment: function() {
            count++;
        },
        decrement: function() {
            count--;
        },
        value: function() {
            return count;
        }
    }
}

这会 counter函数 就返回了一个对象了,其中有三个方法,都是操作 count 这个变量。

2)使用计数函数:

let videoCounter = counter();
console.log(typeof videoCounter); // object
videoCounter.increment();
videoCounter.increment();
console.log(videoCounter.value()); // 2
videoCounter.decrement();
console.log(videoCounter.value()); // 1

我们不能直接操作counter函数内的count变量,但是可以通过 videoCounter 这个变量就间接操作,这个就是模拟私有属性的概念了。

本节总结

  1. 闭包的概念:函数和函数内可以访问到的变量的集合。
  2. 闭包作用:缓存数据、模拟私有变量。

练习题

  1. 阅读如下代码,分析输出,并说明为什么。
let foo = function() {
    let i = 0;
    return function() {
        console.log(i++);
    }
}
let f1 = foo();
let f2 = foo();
f1(); // ?
f2(); // ?
f1(); // ?
  1. 阅读如下代码,分析输出,并说明为什么。
let x = 100;
let y = 200;
let funA = function(x) {
    x += 1;
    let y = 201;
    let funB = function(){
        console.log(x); // ?
        console.log(y); // ?
    }
    return funB;
}
let f = funA(101);
f();
  1. 阅读如下代码,分析输出,并说明为什么。
function makeAdder(x) {
    return function(y) {
        return x + y;
    };
}

let add5 = makeAdder(5);
let add10 = makeAdder(10);

console.log(add5(2));  // ?
console.log(add10(2)); // ?
评论区

索引目录