共计 1998 个字符,预计需要花费 5 分钟才能阅读完成。
概念:什么是闭包(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 |
正文完
发表至: javascript
2019-09-04