1. 永不销毁的执行空间
1. 正常书写一个函数
2. 在这函数内 向外返回一个 引用(复杂)数据类型,并且函数外部,有东西一直引用着这个函数的返回值。
则这个函数的执行空间不会销毁
案例1
function fn() {
const num = 100;
const obj = {
name:'jack'
}
return obj;// 返回一个引用数据类型
}
let res = fn();
/*
fn 函数调用,根据fn中的地址,找到函数的存储空间 `0x001`
在运行内存中 开辟了一个 fn函数的执行空间 `0x00f`
在函数的执行空间 `0x00f` 中 定义了变量num并赋值为100
定义了一个对象: 在这个执行空间内存中会开辟一个对象存储空间 地址为 `0x002`
并且将对象的数据存储在 存储空间`0x002`中
将这个对象的存储空间地址 `0x002` 赋值给变量obj
执行到return,则将对象的存储空间地址 `0x002`作为函数的返回值 返回
在fn函数外部,有一个变量引用着函数fn的返回值 `0x002`
res变量中的值为 地址 `0x002`,通过这个地址一直指向这个对象的存储空间
因为函数外部,有变量一直引用着函数内的对象,所以函数的执行空间不会销毁
*/
案例2
<script>
function fn() {
// var a = 100
const obj = {
name: 'fn函数的name',
age: '不知道'
}
return obj
}
// 变量 newObj 内部保存着 fn 函数中声明的一个对象 obj 的地址, 所以 fn 函数就不会被销毁掉, 如果销毁了, 那么对象也就无法访问了
const newObj = fn()
console.log(newObj)
// 将 newObj 的值修改后就与函数内部的对象切断了联系, 那么这个函数的执行空间就会被销毁
// newObj = null
</script>
2. 闭包函数
- 作用域嵌套形成的一种js高级应用场景
1) 闭包形成条件
1. 大函数执行空间不销毁
2. 大函数内返回的引用数据类型是一个小函数(函数外部有东西引用着函数内的小函数就行)
3. 小函数引用了大函数中的数据(变量,形参)
+ 小函数叫做大函数的闭包函数
2) 闭包作用:
1. 保护变量私有化-----定义在函数内的变量就是私有变量
2. 可以让函数外部访问到函数内部私有变量的值----通过闭包函数访问
3. 延长了变量的生命周期----作用域
缺点: 不会销毁的执行空间,多了容易造成内存泄露
闭包案例1
function fn() { // 大函数
var num = 100;
return function ff() { // 大函数中返回一个小函数
console.log( num )
return num; // 小函数引用了大函数中的数据
}
}
// 函数外部有东西一直引用着fn函数的返回值
let f = fn();
f() // 100
分析
1)fn函数调动,根据fn中的地址,找到fn函数存储空间 `0x001`
在运行内存中 开辟了一个 fn函数的执行空间 `0x00f`
在函数的执行空间 `0x00f` 中 定义了变量num并赋值为100
定义了一个小函数ff: 在这个执行空间内存中会开辟一个小函数存储空间 地址为 `0x002`
并且将小函数中的代码存储在 存储空间`0x002`中
将这个小函数的存储空间地址 `0x002` 作为函数的返回值 返回
在fn函数外部,有一个变量引用着函数fn的返回值 大函数内小函数的存储空间地址 `0x002`
所以fn函数的执行空间 `0x00f` 不会销毁
2)f()调用执行:
f中存储的是地址 `0x002`,
f() 会在内存中 开辟一个小函数的执行空间 `0x01f`
在执行空间 `0x01f`中 输出num的值,但是在小函数中没有变量num,
则会去上一级空间中查找,在fn的执行空间中找到了变量num,所以输出100
并且小函数 返回100
小函数中的代码执行结束,小函数执行空间 `0x01f` 销毁
闭包案例2
function ff(i) {
return function (n) {
console.log( n+ --i )
}
}
let f1 = ff(2);
f1(3) // 4
ff(4)(5) // 8
ff(6)(7) // 12
f1(8) // 8
闭包案例3
<script>
function fun(n, o) {
console.log(o);
const obj = {
fun: function (m) {
return fun(m, n);
},
};
return obj;
}
var a = fun(0); // undefined
a.fun(1); // 0
a.fun(2); // 0
a.fun(3); // 0
</script>
/**
* var a = fun(0)
* 调用全局函数 fun (QF001), 并传递了一个实参 0, 所以对应的两个形参 n === 0; o === undefined
*
* 函数内部代码开始执行
* 1. console.log(o) undefined
* 2. 创建一个 对象 obj, 内部有一个属性名为 fun 值为一个函数
* 3. 将这个 对象 obj 返回到函数外边 (变量 a 接受了 这个 对象, 所以 将来 变量a 可以调用内部的 fun)
*
* a.fun(1)
* 调用的是 对象 obj 内部的 fun (QF999) 函数, 并传递了一个 实参 1, 所以对应的一个形参 m === 1
* 开始执行函数内部代码
* return fun(m, n) 注意!!! 这个函数是全局函数 fun(QF001). 如果想要调用对象内部的 应该是 对象.属性名()
* 并传递了两个实参
* m === 1
* n === 0 (函数QF999 的作用域中没有, 所以需要去上一层作用域查找, 也就是 函数QF001,
在这个函数中找到了一个形参n, 它的值为0)
* 函数开始执行时就会执行一句 console.log(o) 我们传递的是数字 0
*
* a.fun(2)
* 调用的是 对象 obj 内部的 fun(QF999), 并传递一个 实参 2, 所以对应的一个形参 m === 2
* 开始执行函数内部代码
* return fun(m, n) 传递的两个实参 m === 2; n === 0 (n 怎么来的参考 45行注释)
* 函数开始执行: console.log(o) 我们传递的是数字 0
*
* a.fun(3)
* 调用的是 对象 obj 内部的 fun(QF999), 并传递一个 实参 3, 所以对应的一个形参 m === 3
* 开始执行函数内部代码
* return fun(m, n) 传递的两个实参 m === 3; n === 0 (n 怎么来的参考 45行注释)
* 函数开始执行: console.log(o) 我们传递的是数字 0
*/