闭包的定义
简单讲:闭包就是指有权访问另一个函数作用域中的变量的函数。
MDN这样定义:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。
执行上下文和作用域
作用域就是一套规则,用于确定在何处以及如何查找变量(标识符)的规则
三种作用域类型:
- 全局作用域:全局作用域为程序的最外层作用域,一直存在。
- 函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。
- 块级作用域:块级作用域由最近的一对包含花括号{}界定,ES6新增,用let命令新增了块级作用域,
笔者所理解的执行上下文和作用域的区别:
- 作用域在函数定义时已经确定,因为js使用的作用域类型是词法作用域
- 执行上下文会在函数被调用时创建,它是一个内部对象,它定义了一个函数执行时的环境。函数执行完毕,上下文被销毁。
所以可以利用同一个作用域可以创建多个执行上下文
作用域链
作用域链当中储存了执行其上下文的集合,这个集合呈链式链接,称作作用域链。
我理解的闭包
如果一个函数执行时引用了外部的变量,则被引用的那个变量所在的执行上下文会被保存下来,不会被出栈释放。
- 第13行intter是一个闭包,包含函数以及一个私有的执行上下文,被它调用的a和b都被保存下来了
值得一提的是,全局下的变量也可以用作闭包,因为变量存在于全局下,所以全局上下文被保存下来,由于全局上下文始终存在且只有一个,所以这个操作忽略不计。
下面一个例子,判断它的输出:
let bar="hello"
function outter() {
return function intter() {
console.log(bar)
}
}
bar="world"
let intter=outter()
intter();
输出结果为“world”
- 此例我们可以理解到,闭包并不是拷贝了一份变量保存下来,而是将它所调用的变量当时的执行上下文给保存下来了。
- bar变量定义在全局下,全局上下文只有一个,所以bar能被改变再输出。
再来个例子:
function outter() {
let bar="hello"
function intter() {
return function intter2(){
console.log(bar);
}
}
bar="world"
let intter2=intter()
intter2();
}
outter()
很显然,intter2会把outter的一个执行上下文保存下来,所以如果你还在此执行上下文中改变变量,会产生效果。
闭包的应用场景
1.单例模式
- 单例模式保证了一个类只有一个实例,是一种常见的设计模式。
- 实现方法:先判断实例是否存在,如果存在就直接返回,否则就创建了再返回。
- 好处:避免了重复实例化带来的内存开销
//单例模式
function fun() {
this.data = 'hello,world'
}
//立即执行函数保证只会创建一次函数上下文
//所以instance只会存在一个
fun.getInstance = (function () {
let instance=null;
return function () {
if(instance){
return instance
}
else {
instance=new fun()
return instance
}
}
})()
let sa=fun.getInstance()
let sb=fun.getInstance()
console.log(sa===sb)
sa.data="111"
console.log(sb.data)
2.模拟私有属性
javascript 没有 java 中那种 public private 的访问权限控制,对象中的所用方法和属性均可以访问,这就造成了安全隐患,内部的属性任何开发者都可以随意修改。虽然语言层面不支持私有属性的创建,但是我们可以用闭包的手段来模拟出私有属性:
function fun() {
let _name="why"
let _age=20
return function () {
return {
getName:function() {return _name;},
getAge:function() {return _age;}
}
}
}
let obj = fun()()
console.log(obj.getName())//why
console.log(obj.getAge())//20