闭包的原理
官方解释:闭包就是能够读取其他函数内部变量的函数。
要解释这段话,就要引入变量作用域的概念。
变量作用域
首先看下面这段代码的输出值
var num = 0
function add(){
num = num + 1
console.log(num)
}
add() // 1
add() // 2
add() // 3
console.log(num) //3
此时,num是全局的作用域,在add()函数内外都可以访问到的;且在内存中是只有一个的,所有的运算都指向这唯一的num。
然后修改一下num声明的位置,查看下面这段代码的输出
function add(){
var num = 0
num = num + 1
console.log(num)
}
add() // 1
add() // 1
add() // 1
console.log(num) // num is not defined
此时,num的作用域只在函数add()中,在add()函数外是访问不到的;且每次执行add()在内存中都会创建一个num,这里相当于创建了3个num,所以加减运算并不会互相影响。
引入闭包
那么针对上面的情况,想在add()函数体外控制num该怎么办呢?
想想开篇的官方解释:“闭包就是能够读取其他函数内部变量的函数”。
这不就是js提供给我们的解决方法么,那么我们修改一下上面的代码:
const add=(function(){
var num = 0
return function(){
num = num + 1
console.log(num)
}
})()
add() // 1
add() // 2
add() // 3
console.log(num) // num is not defined
此时,num的作用域在函数内部,在外部也是访问不到的,但是可以通过函数内部return的方法访问;且现在函数里面的num只在内存里保留了一份,每次加减运算都是针对这唯一的num操作的。
这样即达到了函数外部无法直接访问函数内部变量的目的,又提供了方法去操作内部变量。
有没有觉得这种设计似曾相识,学过java的同学应该能想到,这个跟java里类的private和public属性很相似。
不错,摊牌了,闭包的用法之一,就是模拟私有方法。
用途实例
用途一,模拟私有方法
创建一个闭包 Conter,内部变量num和方法changeByNum(),保证在外部无法访问;
对外提供add()、subtract()、value()方法,使得外部可以调用内部方法或变量
const Conter = (function(){
var num = 0
// 私有方法
function changeByNum(val) {
num += val
}
// 公有方法
return {
add:function(){
changeByNum(1)
},
subtract:function(){
changeByNum(-1)
},
value:function(){
return num
}
}
})();
Conter.add()
console.log(Conter.value()) // 1
Conter.add()
console.log(Conter.value()) // 2
Conter.subtract()
console.log(Conter.value()) // 1
console.log(num) // num is not defined
用途二,单例
假设通过上面的方法,创造两个闭包Conter1和Conter2,我们对Conter1做加减处理,在打印Conter2的值
const Conter = function(){
var num = 0
// 私有方法
function changeByNum(val) {
num += val
}
// 公有方法
return {
add:function(){
changeByNum(1)
},
subtract:function(){
changeByNum(-1)
},
value:function(){
return num
}
}
}
var Conter1 = Conter();
var Conter2 = Conter();
Conter1.add()
console.log(Conter1.value()) // 1
Conter1.add()
console.log(Conter1.value()) // 2
Conter1.subtract()
console.log(Conter1.value()) // 1
console.log(Conter2.value()) // 0
可以看到无论Conter1如何改变,都不会影响Conter2内部的值,因为在创建闭包时已经分别存在内存中,相当于有两个num了。
那如果想要Conter1和Conter2共享一个num,也就是无论创建多少个闭包,指向的都是一块内存的代码呢?这个就是单例的概念。
我们分析下面这段代码:
function Conter() {
this.num = 0
}
Conter.prototype = {
add: function () {
this.num++
},
subtract: function () {
this.num--
}
}
const singleton = (function () {
var instance = null
return function () {
if (!instance) {
instance = new Conter();
}
return instance
}
})()
var Conter1 = new singleton();
var Conter2 = new singleton();
Conter1.add()
console.log(Conter1.num) // 1
console.log(Conter2.num) // 1
Conter1.add()
console.log(Conter1.num) // 2
console.log(Conter2.num) // 2
Conter1.subtract()
console.log(Conter1.num) // 1
console.log(Conter2.num) // 1
首先创建构造函数 Conter,并原型中添加add()和subtract()两个方法。
然后创建闭包singleton,构造了一个Conter单例。
最后new了两个变量Conter1 ,Conter2,通过打印结果可以看到,无论Conter1 怎么变化,Conter2里面的num都是一起变化的,可以说明两个变量指向了同一个单例Conter。