闭包原理
作用域对象:
- 当JavaScript在运行的时候,它需要一些空间让它来存储本地变量(local variables)。我们将这些空间称为作用域对象(Scope object) 。
- 例如,当你调用函数时,函数定义了一些本地变量,这些变量就被存储在一个作用域对象中。
- 你可以将作用域函数想象成一个普通的JavaScript对象, 但是有一个很大的区别就是你不能够直接在JavaScript当中直接获取这个对象。
- 你只可以修改这个对象的属性,但是你不能够获取这个对象的引用。
- 在JavaScript中,作用域对象是在堆中被创建的,所以在函数返回后它们也还是能够被访问到而不被销毁。
作用域链:
- 当代码试图访问一个变量的时候,解释器将在当前的作用域对象中查找这个属性。
- 如果这个属性不存在,那么解释器就会在父作用域对象中查找 这个属性。
- 就这样,一直向父作用域对象查找,直到找到该属性或者再也没有父作用域对象。
- 我们将这个查找变量的过程中所经过的作用域对象称作作用域链 (Scope chain)
作用域链顶端:
- 在作用域链的最顶层的元素就是全局对象(Global Object)了。
- 运行在全局环境的JavaScript代码中,作用域链始终只含有一个元素,那就是全局对象。
- 所以,当你在全局环境中定义变量的时候, 它们就会被定义到全局对象中。
- 当函数被调用的时候,作用域链就会包含多个作用域对象。
函数的执行空间
function fn(){
console.log('我是 fn 函数');
}
fn();
- 函数执行的时候会在内存中开辟一个执行空间(我们暂且叫他
xxff00
) console.log('我是 fn 函数')
这个代码就是在xxff00
这个空间中执行- 代码执行完毕以后,这个内存中开辟的
xxff00
执行空间空间就销毁了 - 每一个函数会有一个存储空间
- 但是每一个调用都会生成一个完全不一样的执行空间
- 并且执行空间会在函数执行完毕后就销毁了,但是存储空间不会
- 那么这个函数空间执行完毕就销毁了,还有什么意义?
- 我们有一些方法可以让这个空间不销毁
- 闭包:就是要利用这个 不销毁的空间
执行空间不销毁
- 函数的执行空间会在函数执行完毕以后销毁
- 但是,一旦函数内部返回了一个引用数据类型,并且在函数外部有变量接收的情况下
- 那么这个函数的执行空间就不会销毁了
function fn(){
const obj= {
name:'Jack',
age:18,
gender:'男'
}
return obj;
}
const o = fn();
- 函数执行的时候,会生成一个函数执行空间(我们暂且叫他
xxff
) - 代码在
xxff00
空间中执行 - 在
xxff00
这个空间中声明了一个对象空间(xxff11
) - 在
xxff00
这个执行空间把xxff11
这个对象地址返回了 - 函数外部
o
接收的是一个对象的地址没错- 但是是一个在
xxff00
函数执行空间中的xxff11
对象地址 - 因为
o
变量一直在和这个对象地址关联着,所以xxff00
这个空间一直不会销毁
- 但是是一个在
- 等什么时候,执行一句代码
o=null
- 此时,
o
变量不再关联着xxff00
函数执行空间中的xxff11
对象地址 - 那么,这个时候函数执行空间
xxff00
就销毁了
- 此时,
闭包概述
闭包:有权限访问另一个函数作用域中的变量的函数,通俗的理解就是函数内部的函数
- 闭包产生的三个必要条件
function A(){
const num = 100;
return function B(){
console.log(num);
}
}
const fn = A();
1. 在函数 A 中直接或间接返回一个函数 B
2. B 函数中使用着 A 函数的私有变量
3. A 函数外面有一个变量接收着 B 函数
- 闭包空间
function A(){
const num = 100;
return function B(){
console.log(num);
}
}
const fn = A();
1. 这个不会销毁的 A 函数的执行空间,叫做 闭包空间
2. 把函数 A 里面返回的 函数 B, 叫做函数 A 的闭包函数
3. 即:闭包 = 函数内部的函数
- 闭包分析
function fn(){
const num = 100;
return function a(){
console.log(num);
}
}
const f = fn();
- `fn()`的时候会生成一个`xxff00`的执行空间
- 在`xxff00`这个执行空间内部,调用了一个`a`函数的存储空间`xxff11`
- 全局 f 变量接收的就是`xxff00`里面的`xxff11`
- 所以`xxff00`就是不会销毁的空间
- 因为`xxff00`不会销毁,所以定义在里面的变量num也不会销毁
- 将来用`f()`的时候,就能访问到num变量
闭包特点
用途:
- 1、闭包可以读取函数内部变量
- 2、将函数内部变量的值始终保存在内存中
function f1() {
var n = 999;
nAdd = function() {
n += 1
}
function f2() {
alert(n);
}
return f2;
}
var result = f1();
result(); // 999
nAdd();
result(); // 1000
- 代码分析
- 在这段代码中,result实际上就是闭包f2函数。
- 它一共运行了两次,第一次的值是999,第二次的值是1000。
- 这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
- 原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,
- 而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
- 这段代码中另一个值得注意的地方,就是“nAdd=function(){n+=1}”这一行,
- 首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。
- 其次,nAdd的值是一个匿名函数,而这个匿名函数本身也是一个闭包,
- 所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
优势:
1、保护函数内的变量安全。
以最开始的例子为例,函数`f1`中`n`只有函数`f2`才能访问,而无法通过其他途径访问到,因此保护了`n`的安全性。
2、在内存中维持一个变量。会导致内存一直被占用,使用不当会很容易造成内存泄露
依然如前例,由于闭包,函数`f1`中`n`的一直存在于内存中,因此每次执行`nAdd()`,都会给`n`自加1。
3、通过保护变量的安全实现JS私有属性和私有方法(不能被外部访问)。
闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。