《JavaScipt 设计模式与开发实践》之闭包

107 阅读3分钟

闭包

《JavaScipt 设计模式与开发实践》中没有对闭包的概念给出明确定义。但指出了闭包的形成与变量的作用域以及变量的生存周期密切相关。

JavaScript 中常见的作用域有三种:

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6 新增) 作用域可以嵌套,Javascript 中变量查找是由内向外的。外层作用域不能找到内层作用域。
// 示例1
var fn = function() {
    var a = 1;
    console.log(a); // 输出: 1
};
fn();
console.log(a); // 输出:undefined

全局作用域中不能访问函数作用域中的变量。

// 示例2
var a = 1;
var fn1 = function() {
    var b = 2;
    var fn2 = function() {
        var c = 3;
        console.log(a); // 输出: 1
        console.log(b); // 输出:2
    };
    fn2();
    console.log(c); // 输出:undefined
};
fn1();

由示例代码可以看出搜索一个变量时是由内向外的,搜索过程会随着执行环境创建的作用域链一层层向上查找。直到全局对象,如果全局对象中变量不存在,则返回 undefined。

函数作用域中声明的变量,在函数执行完毕后,退出函数时会被销毁。就像示例 1 中的代码。

// 示例3
var fn = function() {
    var a = 1;
    return function() {
        a++;
        console.log(a);
    };
};
var f = fn();
f(); // 2
f(); // 3
f(); // 4
f(); // 5

在这个示例中,函数作用域中的变量没有被销毁,这是因为这里产生了一个闭包结构。fn 函数返回了一个匿名函数,这个函数对函数作用域中的变量 a 保持引用,导致变量不被销毁。

由以上特性,我们可以对什么是闭包做一个结论。

闭包是函数作用域中声明的变量可以被该作用域外的函数调用且在函数执行完成后不会被销毁的结构。

闭包的作用

封装变量

闭包可以帮助把一些不需要暴露在全局的变量封装成”私有变量“。

延续局部变量的寿命

一般声明的变量会在函数执行完后进行销毁,闭包可以让变量继续存在内存中。

很多人闭包会导致内存泄漏。但其实放在手动闭包中的变量和放在全局的变量对内存其实是一致的。如果需要回收这些变量可以手动把这些变量设为 null。

闭包和内存泄漏有关系的地方是循环引用。

高阶函数

所谓高阶函数是满足下列条件之一的函数:

  • 函数可以作为参数传递
  • 函数可以作为返回值输出

把函数作为参数传递

抽离出一部分容易变化的业务逻辑放入函数参数中。

回调函数

ajax 请求时回调函数应用的一个重要场景。

// ajax 请求
var getUserInfo = function(userId, callback) {
    $.ajax('http://xxx.com/getUserInfo?' + userId, function)
}

钩子函数也是回调函数的应用。

函数作为返回值

将函数作为返回值的场景

  • 函数柯里化
  • 工厂函数

PS

记录读书的笔记,有误希望大佬指出。