闭包的概念
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。这一段描述是MDN社区对闭包的定义,也就是说在子层作用域内存在访问父层作用域中的引用,闭包就会产生。闭包是函数式编程产生的特有的概念。
闭包的形成时机
由上面的概念,我们可以推导出闭包的形成与作用域有关。具体有啥关系呢,可从下图看出结论。
上图的代码逻辑是:先声明一个函数fa以及作用域内变量a,fa作用域下再声明函数fb以及变量b,变量b捆绑了对父作用域变量a的引用。函数fb并没有开始执行,我们打印了fb的对象,可以看到[Scopes](作用链)属性中存在Closure对象。
也就是说,函数未执行的声明阶段,闭包对象已经产生了,闭包对象是作用域链数组中的一个元素,更具体的表述是,在代码的预解释阶段,闭包已经形成。
闭包的应用场景以及应用演变过程
用循环生成任务队列时
for(var i = 0; i < 5; i++){
setTimeout(() => {
console.log(i)
},1000)
}
以上代码表示,循环发布5个宏任务,将这5个任务加入到任务队列中,并且执行。任务队列执行时,for循环已经完成循环。所以取到的变量i为5。具体的原因是,用var语法声明变量时,并不能产生一个隔离的作用域。或者说,for(){}语法并不能产生作用域。所以我们要给代码创建一个隔离的作用域,才能达到我们的目的。
for(var i = 0; i < 5; i++){
setTimeout(((i) => {
console.log(i)
})(i),1000)
}
创建一个自执行函数,便达到了生成隔离作用域的效果。
两种的代码运行效果如下
用闭包的方式实现单例模式
const Person = (() => {
let instance = null;
const name = "white";
const age = 20;
const init = () => ({
getName : () => name,
getAge : () => age
})
return {
getInstance: () => {
if(!instance){
instance = init();
}
return instance;
}
}
})();
const a = Person.getInstance();
const b = Person.getInstance();
console.log(a === b) //结果为true
单例模式在react的应用有很多,比如一些状态管理器、路由对象等等。其实现原理便是采用的单例模式来创建。
用闭包实现模块化,达到数据共享
// 模拟react的useState钩子
const useState = (inital) => {
let value = inital;
const setValue = val => value = val;
const getValue = () => value;
return [getValue, setValue]
}
用自执行函数生成闭包,达到类似JQuery模块化的效果
//自定义jquery原型
(function(module){
JQuery = JQuery.prototype = {
constructor: JQuery
//自定义jquery属性
//some attribute...
}
//给window对象添加全局属性$ 和 JQuery
module.JQuery = module.$ = JQuery
})(window)
闭包既是提供给我们JS开发者的一套编程思想。也是JS走向模块化的基石。
以上就是闭包的概念以及一些简单的应用场景的描述。实际应用上闭包是函数式编程最为广泛的应用。
未完待续...