乐趣区

JavaScript-闭包

概念:什么是闭包(Closure)

  • MDN:闭包是函数和声明该函数的 ++ 词法环境 ++ 的组合。
    A closure is the combination of a function and the lexical environment within which that function was declared.
  • 百度百科 1:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数)。
  • 百度百科 2:闭包就是能够读取其他函数内部变量的函数,是“定义在一个函数内部的函数”。
  • 其他说法 1:闭包是词法闭包的的简称, 是引用了 ++ 自由变量 ++ 的函数。
  • 其他说法 2:闭包就是通过返回一个函数来保留某段作用域的一种方法,通过返回函数把本该消失的作用域保留到这个函数中。

可见:闭包并没有一个明确的、易于理解的概念,泛泛来看,闭包本质上是函数及作用域的一类问题。而实际上,我们也不太需要关注闭包的概念是什么,只需知道其表现、原理及常见的应用场景即可。

自由变量与作用域

  • 作用域分:全局作用域、函数作用域和块作用域(ES6 后);
  • 作用域是在函数定义时确定的,而不是在函数执行时确定的;
  • 在 A 作用域中使用的变量 X,却没有在 A 中声明,那么对于 A 作用域来说,变量 X 即自由变量;
  • 按照作用域链向上查找自由变量时,要先到创建该函数的作用域中去查找,而不是调用该函数的作用域中去查找;
var a = 10;
function sum(x) {return x + a;}
sum(1);
// 11
// 在函数 sum 的作用域里,a 就是自由变量 
var a = 100;
function fn() {
    var a = 10;
    return function(x) {return a + x;}
}
var f = fn();
a = 200;
f(1);
// 11
// 自由变量 a 的值是沿着作用域链向上一级一级找到的 
var x = 10;
function fn() {console.log(x);
}
function show(f) {
    var x = 20;
    (function() {f();
    })()}
show(fn);
// 10
// 自由变量 x 在函数 fn 中执行,而 fn 在全局中定义
// 按上文总结:x 的值要到全局中(定义上下文)查找,而不是到函数 show 的作用域(执行上下文)中查找 
var x = 10;
function show(y) {
    var x = 20;
    (function(z) {console.log(x+z)
    })(y)
}
show(30);
// 30 + 20 = 50

闭包的表现、原因及其应用

  • 闭包的两种表现:
// 函数作为返回值
function fn() {
    var max = 10;
    return function bar(x) {if (x > max) {console.log(x);
        }
    };
}
var f = fn();
f(20); 
// 20
// 函数作为参数
var max = 10;
function bar(x) {if (x > max) {console.log(x);
    }
}
(function(f) {
    var max = 100;
    f(15);
})(bar)
// 15
  • 闭包产生的原因:

正常来讲,一个函数 fn 执行完成后,其上下文环境会销毁。但存在一种意外:
fn 执行完后返回了另一个函数 f,而正巧,返回的函数体中存在一个属于 fn 上下文环境的自由变量,那么 fn 执行完后,其上下文环境不能销毁。
很显然,闭包会增加内容开销。

  • 闭包的实际应用:

首先需要知道每次调用外部函数,都会返回一个新的函数;而且返回函数的执行互不影响。

function lazy_sum(arr) {return function() {return arr.reduce((x,y) => {return x+y;})
    }
}
var f1 = lazy_sum([1,2,3,4,5]);
var f2 = lazy_sum([1,2,3,4,5]);
f1 === f2; // false

以上便是闭包的一种应用:返回一个函数,并延迟执行。

闭包更强大的一个功能是:模拟面向对象的程序设计语言,封装一个 private 变量

// 使用 js 创建一个开放的计数器
function create_counter(initial) {
    var x = initial || 0;
    return {inc: function() {return ++x;}
    }
}
var c1 = create_counter();
var c2 = create_counter(10);
c1.inc(); // 1
c1.inc(); // 2
c2.inc(); // 11
c2.inc(); // 12
c1.inc(); // 3
c2.inc(); // 13

闭包还可以把一个多参数的函数转化成单参数的函数:

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)); // 5^2=25
console.log(pow3(7)); // 7^3=343
退出移动版