JS基础课5闭包

闭包

在理解闭包之前,有个重要的概念需要先了解一下,就是 js 执行上下文
1.函数里面返回一个函数就叫做闭包
2.把局部变量交给全局使用
3.把局部变量交给内部函数使用

JS代码在执行前,JS引擎总要做一番准备工作,这份工作其实就是创建对应的执行上下文;
function f1() {console.log('anlan');};f1();function f1() {console.log('noob');};f1();
这说明代码在执行前一定发生了某些微妙的变化,我们都知道它叫变量提升。那JS引擎究竟做了什么呢

执行上下文有且只有三类,全局执行上下文,函数上下文,与eval上下文
全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,我们能通过this直接访问到它。
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。

函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
eval函数执行上下文:执行在eval函数内部的函数也有自己的函数上下文

执行上下文栈也叫调用栈,当js解释器运行的时候,执行栈就会用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。UJS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。


function f1() {f2();console.log(1);};function f2() {f3();console.log(2);};function f3() {console.log(3);};f1()
f1入栈 ---> f2入栈 ----> f3入栈  -----> f3 出栈 -----> f2 出栈 -----> f1 出栈
#先出栈先打印所以321
那么我们现在已经在一定程度上了解了上下文,那当一个函数创建的时候究竟发生了什么呢?
1. JavaScript创建一个新的函数执行上下文
2. 这个执行上下文将有它自己的一组变量,这些变量将是这个执行上下文的本地变量。
3. 新的执行上下文被推到到执行堆栈中
4. 直到遇到return语句或一个结束括号},函数执行结束
当函数结束时:
1. 弹出执行栈,形参都会被销毁
2.函数将返回值返回调用上下文
3.这个本地函数执行上下文被销毁


案例

1let a = 3;let b = addTwo(a);console.log(b);function addTwo(x) {let ret = x + 2;return ret;};
1.解析真个js代码找到可提升的变量function addTwo(x),将addTwo(x)变量提升到最上面,之后找变量,将a和b变量放到栈存死区里面script
2.执行代码,将3赋值给了变量a,其次找到被提升变量的函数addTwo(a),通过作用域链,将3传入addTwo(3),进入函数体function addTwo(x)内,形参x也会被3赋值
然后let ret = x + 2,计算成5,赋值给了ret,return返回,出函数5被赋值给了b,同时x和ret被销毁。然后调用console.log(b),b的值是5所以打印了5

我们在函数执行上下文中有变量,在全局执行上下文中有变量。JavaScript的一个复杂之处在于它如何查找变量,沿着作用域链查找而不是严格按照上下文的方式查找。

2let val = 7
function createAdder() {
function addNumbers(a, b) {
let ret = a + b
return ret
}
return addNumbers
}
let adder = createAdder()
let sum = adder(val, 8)
console.log('example of function returning a function: ', sum)
1.adder,val,sum三个进入栈存死区,script,function createAdder()函数变量提升,那么为何function addNumbers(a, b)不提升呢?因为不在当前的作用域内
2.let adder = createAdder()这里函数调用,像上线文寻找这个函数,然后进入,当进入的一瞬间function addNumbers(a, b)函数变量提升到当前作用域顶部。并且返回一个函数addNumbers,将返回函数的定义赋值给了adder。同时addNumbers被销毁
3.调用adder传入两个值(val, 8),当前作用域val是7,将7传入function addNumbers(a, b) ,进行 ret = a + b赋值7+8 ,返回ret。函数执行完毕,a,b,ret销毁。最后结果函数的定义赋值给了sum,打印sum

真正的闭包

真正的闭包
function createCounter() {
let counter = 0#2.定义了一个变量,这里为何用let不用var,因为let具有穿透函数的功能,var不具备这个功能,如果不用let,那么这个就变成全局变量了。
const myFunction = function() {#3.定义函数
counter = counter + 1#4.运算
return counter#5.返回函数,
}
return myFunction#6.返回函数,并销毁
}
const increment = createCounter()#1.执行进入
const c1 = increment();#1
const c2 = increment();#2#为什么这里是2,因为闭包机制,counter被存储在increment(),没有销毁,所以每次运算数字会网上叠加
const c3 = increment();#3
console.log('result', c1, c2, c3)
1.c1,increment进入栈存死区,将function createCounter() 提升到当前作用域最顶端,

真正的闭包

无论何时声明新函数并将其赋值给变量,都要存储函数定义和闭包。闭包包含在函数创建时作用域中的所有变量,它类似于背包。函数定义附带一个小背包,它的包中存储了函数定义创建时作用域中的所有变量。

在全局作用域中创建的函数创建闭包,但是由于这些函数是在全局作用域中创建的,所以它们可以访问全局作用域中的所有变量,闭包的概念并不重要。
	function a(){} 在全局作用域函数,在所有地方包含了所有的作用域,所以闭包的概念就不重要了
当函数返回函数时,闭包的概念就变得更加重要了。返回的函数可以访问不属于全局作用域的变量,但它们仅存在于其闭包中的内容,放了什么才能返回什么。

闭包的方法是通过背包的类比。当一个函数被创建并传递或从另一个函数返回时,它会携带一个背包。背包中是函数声明时作用域内的所有变量。

注意事项:闭包容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。

MDN对闭包的官方解释:
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
闭包是绑定在一起(封闭)的函数及其周围状态(词汇环境)的引用的组合。换句话说,闭包允许您从内部函数访问外部函数的scope。在JavaScript中,每次创建函数时,都会在函数创建时创建闭包。


闭包绝不可以理解为在函数里返回函数的函数。这是引发闭包问题的原因,而不是闭包概念的本身

思考题

1. let c = 4;const addX = x => n => n + x;const addThree = addX(3);let d = addThree(c);console.log('result', d)
#箭头函数addX = x => n => n + x函数的嵌套, 当执行addx(3)的时候,const addThree是箭头函数n => n + x,x成为了3,然后c给了n,结果就是7
2. let c = 4;const addX = x=>n=>(x += n,n + x);const addThree = addX(3);let d = addThree(c);console.log('result', d)
#11
3. var data = [];for (var i = 0; i < 3; i++) {data[i] = function () {console.log(i);};};data[0]();data[1]();data[2]()#3,3,3
4. var data = [];for (let i = 0; i < 3; i++) {data[i] = function () {console.log(i);};};data[0]();data[1]();data[2]()#0,1,2
5. var result = [];let a = 3; function foo(a) {let total = 0; for (var i = 0; i < 3; i++) {result[i] = function () {total += i * a;console.log(total);}}}foo(1);result[0]();result[1]();result[2]();#3,6,9
var result = [];let a = 3; function foo(a) {let total = 0; for (let i = 0; i < 3; i++) {result[i] = function () {total += i * a;console.log(total);}}}foo(1);result[0]();result[1]();result[2]();#0,1,3
6.4的环境基础下执行  result[0]();result[1]();result[2]();


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值