闭包

138 阅读4分钟

说闭包之前我们有必要先说一下 执行环境和作用域,这有利于我们理解闭包

全局作用域、函数作用域、块级作用域

在代码中任何地方都能访问到的对象拥有全局作用域

函数作用域,是指声明在函数内部的变量

块级作用域可通过新增命令let和const声明,所声明的变量在指定块的作用域外无法 被访问

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行

作用域链

要了解作用域链首先要说一下 自由变量

自由变量

当前作用域没有定义的变量,就成为了自由变量。

所以要想获取这个自由变量的值,就需要向它的父级作用域查找,如果父级作用域没有的话,那就一层一层的向上查找,直到全局作用域,这种层级关系,就是作用域链。

之前我一直以为作用域和执行上下文是一个概念,后来发现这两个概念是不一样的。

在 函数定义时,作用域就已经确定了,而且不会改变。而执行上下文是在函数运行时确定的,随时都有可能改变。

闭包的概念

有权访问另一个函数作用域中变量的函数。

一个简单的闭包:

var utils = (function(){
    return {
        
    }
})()

闭包的作用

  • 保护(保护私有作用域不受外界干扰)
  • 保存(形成一个不销毁的私有作用域)

什么时候用到闭包

  1. 在团队开发过程中,我们通常会把自己的代码存放在一个私有作用域中,如果别人需要使用某些方法的话,可以通过return或者window.xxx暴露在全局下。

  2. jQuery源码也是利用这种保护机制的

     ~function(){
         var jQuery = function(){
             
         }
         window.$=window.jQuery=jQuery
     }()
    
  3. 闭包解决选项卡问题

         <div class="tabBox" id="tabBox">
             <ul>
                 <li>标题1</li>
                 <li>标题2</li>
                 <li>标题3</li>
             </ul>
             <div class="selected">content1</div>
             <div>content2</div>
             <div>content3</div>
         </div>
     
         var tabBox = document.getElementById('tabBox'),
             oList = tabBox.getElementsByTagName('li'),
             oDivList = tabBox.getElementsByTagName('div');
         function changeTab(index){
             //index 存储当前点击li的索引
             for(var i=0;i<oList.length;i++){
                 oList[i].className = oDivList[i].className = null //清除样式
             }
             oList[i].className = oDivList[i].className = 'selected'
         }
         for(var i=0;i<oList.length;i++){
             oList[i].onclick = function(){
                 //三次循环给三个li都绑定了一个方法,但是都没有执行,当循环结束了,i此时为3,
                 //等到点击的时候,就无法实现想要的效果了
                 changeTab(index)
             }
         }
    

解决办法是:

  1. 自定义属性方式(可以给当前元素加一个属性)

     for(var i=0;i<oList.length;i++){
         oList[i].myIndex = i;
         oList[i].onclick = function(){
             changeTab(this.myIndex)
         }
     }
    
  2. 闭包机制

     for(var i=0;i<oList.length;i++){
         oList[i].onclick = (function(i){
             //执行自执行函数首先会形成一个私有作用域
             //接下来 形参赋值 i=0,i=1,i=2
             return function(){
                 changeTab(i)
             }
         })(i)
     }
     //形成不销毁的私有作用域,不一定要有return,我们也可以写成这样
     for(var i=0;i<oList.length;i++){
         (function(i){
             oList[i].onclick = function(){
                 //oList[i].onclick占用了返回的function,所以这个作用域也不会被销毁
                 changeTab(i)
             }
         })(i)
     }
    

现在有这样一道题

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

首先我们考虑变量提升

function fn(){ //fn内部会形成一个私有作用域,在私有作用域内,会发生形参赋值和变量提升,在这里并没有形参传递,我们只考虑变量提升
    var i=1;
    return function(n){ //return 引用类型的值才会让私有作用域不销毁
        console.log(n + i++) // 先把i=1赋值给结果,然后i自己再加1
    }
}
var f;
f= fn() //把fn执行的返回结果赋值给f 实际上f是function(n){console.log(n + i++)}
f(10) //把fn执行的返回结果再执行,并且给它传递一个参数10
fn()(10)
f(20)
fn()(20)

函数执行返回了一个引用数据类型堆内存的地址(并且堆内存隶属于这个作用域),在外面有一个变量接收了这个返回值,此时当前作用域就不能销毁(想要销毁,只需要让外面的变量赋值为null)

闭包与this问题

var num = 1,
    obj = {
        num:2,
        fn:(function(num){
            this.num*=2;
            num+=2;
            return function(){
                this.num*=3;
                num++;
                console.log(num)
            }
        })(num)
    }
var fn = obj.fn;
fn();
obj.fn();
console.log(num,obj.num)