💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## previously 函数执行形成一个私有作用域,保护里面的私有变量不受外界干扰就叫闭包 ### 作用域 [栈内存] > 全局作用域:window > 私有作用域:函数执行 > 块级作用域:使用let创建变量,存在块级作用域中 ### 作用域链 当前作用域代码执行的时候遇到一个变量,我们首先看一下它是否属于私有变量,如果是当前作用域私有变量,那么以后在私有作用域中再遇到这个变量都是操作私有的(“闭包:私有作用域保护私有变量不受外界干扰”);如果不是私有的变量,向其上级作用域查找,如果也不是上级作用域私有的,就会继续向上查找,直到找到window全局作用域,我们把这种向上一级级查找的机制叫做`作用域链` 全局下有,操作的就是全局变量,全局下没有(设置:给全局对象window增加了属性名&&获取:报错) ## 查找私有变量 >JS中的私有变量有且只有两种 >- 在私有作用域变量提升阶段声明过的变量或则函数 >- **形参**也是私有变量 ``` function fn(num1,num2){ var total = num1+num2; return total; } var result = fn(100,200); ``` >函数执行形成一个全新独立的私有的作用域 >1、 形参赋值 >2、 变量提升 >3、 代码自上而下执行 >4、 当前栈内存(私有作用域)销毁或则不销毁 **注意:** 形参赋值在变量提升之前 ``` var x=10 ,y = 20 ,z = 30; function fn(x,y){ //=>[私有作用域] //=>形参赋值:x=10 y=20 (x/y都是私有变量) //=>变量提升:var x(忽略的,已经存在这个名字了) console.log(x,y,z); //=>z不是私有变量,是全局变量 var x = 100; //=>私有的x=100 y = 200; //=>私有的y=200 z = 300; //=>全局的z=300 console.log(x,y,z); //=>100 200 300 } fn(x,y,z); //=>FN执行传的的是实参(实参都是值,不是指要转换成值,这里传递的不是变量,而是变量的值传过去了) console.log(x,y,z); //=>10 20 300 ``` FN执行传的的是实参(实参都是值,不是指要转换成值(**这个值可能是地址**),这里传递的不是变量,而是变量的值传过去了) ``` //=>[私有作用域] //=>形参赋值:b=1 (私有变量) //=>变量提升:b=aaafff111(此处赋值操作替换了形参赋值的内容) function fn(b){ console.log(b); //=>函数b本身 function b(){ //=>[私有作用域] //=>形参赋值和变量提升都没有 console.log(b); } b(); //=>函数b本身 } fn(1); ``` ## 如何查找上级作用域 ``` var n = 10; function sum(){ console.log(n); } // sum(); //=>10 ~function(){ var n = 100; sum(); //=>10 //sum的宿主环境(和箭头函数的宿主环境定义貌似不一样)是当前自执行函数形成的私有作用域,宿主环境和上级作用域没有关系 }(); ``` >函数执行形成一个私有作用域(A),A的上级作用域是谁,和它在哪执行没有关系,主要是看他在哪定义的,在哪个作用域下定义的,当前A的上级作用域就是谁。 ``` var n = 10; var obj = { n:20 ,fn:(function(){ var n = 30; //->上级作用域:全局作用域 return function(){ //->上级作用域:自执行函数 console.log(n); } })() }; obj.fn(); //30//return function(){console.log(n);} ``` 以上栗子,在堆内存中存obj的时候,遇到fn,发现是一个函数自执行,于是开始函数自执行,开辟一个栈内存,形参赋值,变量提升,逐行执行,然后return function,因为是function,此时会另外再开辟一个堆内存,返回堆内存的地址,作为return 返回的值。 ![](https://box.kancloud.cn/6365e868e9eaf1dd13377a63ce6aa60b_1723x367.png) ``` var n = 10; var obj = { n:20 ,fn:(function(n){ //->上级作用域:全局作用域 return function(){ //->上级作用域:自执行函数 console.log(n); } })(obj.n) }; obj.fn(); //会报错 ``` ## 闭包作用之保护 >函数执行形成的私有作用域会保护里面的私有变量不受外界干扰。 ``` (function(window,undefined){ var jQuery = function(){ ... }; ... window.jQuery = window.$ = jquery; })(window); ``` ``` var Zepto = (function(){ var Zepto = function(){ ... }; ... return Zepto; })(); Zepto(); ``` 在真实项目中,我们利用这种保护机制实现团队协作开发。(避免了多人同一个命名,导致代码冲突的问题) ## 闭包作用之保存 > 函数执行,形成一个私有作用域,函数执行完成,形成的这个栈内存一般情况下都会自动释放。 > >但是还有二般情况,函数执行完成,当前私有作用域中的某一部分内容被栈内存以外的其它东西(变量/元素的事件)占用了。当前的栈内存就不能释放掉,也就形成了不销毁的私有作用域(里面的私有变量也不会销毁)。 ``` function fn(){ var i = 1; return function(n){ console.log(n+i++); } } var f = fn(); f(10);//11 fn()(10);//11 f(20);//22 fn()(20);//21 ``` ![](https://box.kancloud.cn/771f39d5b8dfead624a78c60d3e78f98_1359x602.png) >函数执行形成一个私有作用域,如果私有作用域中的的部分内容被外部的变量占用了,私有作用域不会销毁。 >[形式] >函数执行返回了一个引用数据类型堆内存的地址(并且这个堆内存是隶属于这个作用域的),在外面有一个变量接收了这个返回值,此时当前作用域就不能销毁(想要销毁,只需要让外面的变量赋值为null,也就是不占用当前作用域中的内容了) ### 有return不一定销毁 私有作用域里没有东西被占用,就会销毁。 ### 不销毁不一定要有return ``` //这种形式私有作用域也不会销毁 for(var i=0;i<oList.length;++i){ ~function(i){ oList[i].onclick = function(){ changeTab(i); } //没有return 但被事件绑定占用 }(i) } ``` 另外要注意,不是你return一个堆内存就不会销毁(上面的例子并没有return),而是要看return的这个是否被占用,如果没有变量来接收,是直接会销毁的 ### 闭包保存在循环中的应用 ``` //这样达不到预期的效果 for (var i=0;i<oList.length;++i){ oList[i].onclick = function(){ changeTab(i); //=>不行的原因,给当前LI点击事件绑定方法的时候,绑定的方法并没有执行(点击的时候才执行),循环3此,分别给3个LI的点击事件绑定了方法,循环完成后i=3(全局的)。当点击的时候,形成一个私有作用域,用到了变量i,i不是私有的变量,向全局查找,此时全局的i已经是最后循环的3了 } } //--- --- --- for(var i=0;i<oList.length;++i){ oList[i].onclick = (function(i){ return function(){ changeTab(i); } })(i) } //--- --- --- //这种形式私有作用域也不会销毁 for(var i=0;i<oList.length;++i){ ~function(i){ oList[i].onclick = function(){ changeTab(i); } }(i) } //--- --- --- //块级作用域 每次循环都会形成一个单独的{}块级作用域,每个块级作用域之间没有冲突 for (let i=0;i<oList.length;++i){ oList[i].onclick = function(){ changeTab(i); } } ```