初识闭包

2019-02-25  本文已影响0人  学的会的前端

闭包

闭包就是指有权访问另一个函数作用域中的变量的函数。
在后台执行环境中,闭包的作用域链包含着它自己的作用域,包含函数的作用域和全局作用域。
作用:暴露局部变量。
JS 中的闭包是什么?

//形式一
    function(){
        local++
        console.log(local)
    }
    //形式二
    function foo(){
        var local = 1
        function bar(){
            local++
            returnlocal
        }
        return bar
    }
    var fnc = foo()
    fnc()

示例1引入闭包

    var fnArr = [];
    for(var i = 0; i < 2; i++){
        fnArr[i] = function(){
            return i;
        }
        //以上代码的函数其实并没有执行,一直在赋值而已。for循环遍历之后
        //才调用函数,此时,i已经等于2,所以调用函数输出的是全局变量2.
        //数组的每一项是一个函数
        //fnArr[1]是一个函数
    }
    console.log(fnArr[1]()); //输出结果为2
    console.log(fnArr[0]()); //输出结果为2
    console.log(fnArr[2]()); //报错,fnArr[2] is not a function
捕获.PNG
以上代码进行改装,使其输出的就是对应的i值。
    //方法1:
    var fnArr = [];
    for(var i = 0; i < 2; i++){
        (function(i){
            fnArr[i] = function(){
                return i;
            }
        })(i) // 立即执行函数
    }
// 代码改写
    var fnArr = [];
    function fn1(i){
        fnArr[i] = function fn11(){
            return i
        }
    }
    function fn2(){
        fnArr[i] = function fn22(){
            return i
        }
    }
    fn1(0)
    fn2(1)

利用作用域链理解一下代码:

    globalContext = {
        AO: {
            fnArr: [fn11,fn22];
            fn1: function;
            fn2: function;
        }
    }
    fn1.[[scope]] = globalContext.AO
    fn2.[[scope]] = globalContext.AO
    fn1Context = {
        AO: {
            i: 0
            fn11: function;
        }
        scope: fn1.[[scope]]
    }
    fn11.[[scope]] = fn1Context.AO
    fn2Context = {
        AO: {
            i: 1
            fn22: function;
        }
        scope: fn2.[[scope]]
    }
    fn22.[[scope]] = fn1Context.AO
    fn11Context = {
        AO: {

        }
        scope: fn11.[[scope]]
    }
    fn22Context = {
        AO: {

        }
        scope: fn22.[[scope]]
    }

示例2引入闭包

    function fn(){
        var s = 1
        function sum(){
            ++s
            console.log(s)
        }
        return sum
    }
// 以上代码可以改写成
    function(){
        var s = 1
        return function(){
            ++s
            console.log(s)
        }
    }
    var mySum = fn()
    mySum() // 2
    mySum() //重新调用的话,就会初始化一个新的sumContext 3
    mySum() // 4
    var mySum2 = fn()
    mySum2() // 2
    mySum2() // 3

利用作用域链解释:

globalContext = {
        AO: {
            fn: function
            mySum: sum
            mySum2: undefined
        }
    }
    fn.[[scope]] = globalContext.AO
    fnContext = {
        AO: {
            s: 2
            sum: function
        }
        scope: fn.[[scope]]
    }
    sum[[scope]] = fnContext.AO
    sumContext = {
        AO: {}
        scope:sum.[[scope]]
    }
    //调用函数会初始化一个新的执行上下文,与前一个没有任何关系
    fn-Context = {
        AO: {
            s: 1
            sum: function
        }
        scope: fn.[[scope]]
    }
    sum.[[scope]] = fn-Context.AO

变量(内存)的生命周期

  1. 默认作用域消失时,内存就被回收。
  2. 全局变量的生命周期:
    var a = 1;
    // 浏览器一行行执行代码,当执行到这一行a的值 1 就出现在内存中了
    // window窗口关闭(关闭页面),a就消失
    // 刷新页面的时候,之前的a也消失不见了,执行代码会出现新的a
    生命周期不会超过页面的生命周期
  1. 局部变量的生命周期:
function f1(){
        var a = 1;
        return undefined;//所有的函数不写return,默认return undefined。
    }
    // 函数f1被调用的时候,a才存在(出生)。此时,a不存在
    f1();
    //浏览器调用f1,当执行到a所在的一行,a才会出现在内存中。a的取值是1.
    //当a所在的环境作用域不在的时候,a就不存在了。当函数执行return之后,跳出函数的执行环境,a就不存在了。
    f1();
    //再一次调用f1,则产生了新的a,与原来的a没有任何关系
  1. 如果变量被引用着,则不能回收。
    function f1(){
        var a = {name: 'a'};
        var b = 2;
        window.xxx = a; //这不是覆盖
    }
    f1();
    // 函数执行完b就死了
    // 函数执行完,a还存在。
    console.log(a)
    //此时,a可以被引用,a的值为1,window死了,a才会死。
    //也可以认为,变量名死了,但真正的内存还存在
    window.xxx = {name:'b'}
    //此时a已经没有被引用了,就消失了

var作用域

        var a
        function f1(){
            var a  //a的值为1,只看父级作用域,而且就近不是指的代码近。
            function f2(){
                
                a = 1
            }
            function f3(){
                var a
            }
        }

函数的作用域的就近原则:

    function f2(){}
    function f1(){
        functon f2(){

        }
        f2() //指的是f1当中的f2
    }
var a //2
    function f1(){
        var a
        function f2(){
            var a
            f3()
            a = 1
        }
    }
    function f3(){
        a = 2 //指的是第一个a,和f3执行与否没有任何关系
    }

    function f1(){
        var a
        function f2(){
            var a
            a = 1
        }
    }
    //f2中的a与f1中的a只是名字相同,但是没有任何的关系

立即执行函数

function f1(){
        var a
        a = 1
        console.log(a)
    }
function f1(){
        var a
        a = 1
        console.log(a)
    }
f1()//f1()此时还是一个全局变量
    function(){
        var a
        a = 1
        console.log(a)

    }()
//声明了一个匿名函数,没有全局变量。
//此时的函数浏览器运行时会报错,语法错误。
!function(){
  var a
  a = 1
  console.log(a)
}()
第一种情况
var a = 2
    !function(a){
        
        a = 1 //形参声明的a
        //形参是给第一个参数赋值
        //这里面的a是一个新的作用域,与外面的a没有任何关系
        console.log(a) //1

    }(/*没有传参*/)
    console.log(a) //2
第二种情况
        var a = 100
    !function(a){ //此处的a与调用传入的参数a没有任何关系,只是名字相同
        console.log(a) //这个a是上面离他最近的a
    }(a) //此处的a为全局作用域下的a,取值为100  

变量提升

    function a(){}
    var a = 100
    console.log(a) // 100
    //变量提升
    var a 
    function(){}
    a = 100
    console.log(a) //100

    var a = 100
    function a(){}
    console.log(a) // 100
    //变量提升
    var a
    function(){}
    a = 100
    console.log(a) //100

        var a = 100
        var a = function(){}
        function a(){}
        console.log(a)
        //变量提升
        var a 
        var a
        function(){}
        a = 100
        a = function(){}
        console.log(a) // function(){}

        function f1(){
      a = 1
      var a
    }
    f1()
    //相当于
        function f1(){
        var a
        a = 1
    }
    f1()


    var a = 100
    f1()
    function f1(){
        var b = 2
        if(b === 1){
            var a //a=99指的是此处的a
        }
        a = 99
    }
    //声明提升
    var a
    function f1(){
        var b
        var a
        b = 2
        if(b === 1){

        }
        a = 99
    }
    a = 100
    f1()

时机(异步)

    button.onclick = function f(){
        console.log(1)//发生点击事件的时候才会执行代码
    }
    console.log(2)//代码一定会执行
    //先console.log(2),之后在console.log(1).
    //当用户点击按钮时,浏览器会执行函数f()
    //button.onclick(event),浏览器手动的执行这句话
    //button.onclick.call(target,event)

内存泄露

内存泄露的例子

var fn = function(){
        var a = {
            name: 'a'
        }
        var b = {
            name: 'b'
        }
        return function(){
            return a
        }
    }()
    console.log(fn())
    //b如果没有被回收,就是内存泄露,谁也不能访问b了,IE会出现内存泄露

如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素无法被销毁。

    function assign(){
        var element = document.getElementById('some')
        element.onclick = function(){
            alert(element.id)
        }
    }

由于匿名函数保存了一个对于assign()的活动对象的引用,因此就会导致无法减少element对的引用数,只要匿名函数存在,element的引用数至少也是1,因此它所占用的内存就永远不会被回收。

    function assign(){
        var element = document.getElementById('some')
        var id = element.id
        element.onclick = function(){
            alert(id)
        }
          element = null
    }

在上面的代码中,通过把element.id的一个副本保存在一个变量中,并且在闭包中引用该变量消除了循环引用,但是此时还不能解决内存泄露的问题。闭包会引用包含函数的整个活动对象,而其中包含着element。即使闭包不直接引用element,包含函数的活动对象中仍会保存一个引用。因此,有必要把element变量设置成null,这样就可以消除对DOM对象的引用,顺利的减少其引用数,确保正常回收其占用的内存。

面试题

闭包是造成问题的原因,立即执行函数是解决问题的方法。

    var items = document.querySelectorAll('li')
    for(var i = 0; i < items.length; i++){
        items[i].onclick = function(){
            console.log(i)
        }
    }
    //运行结果:无论i为多少,console.log(i)的值都为6.
    //变量提升
    var items
    var i
    for(i = 0; i < items.length; i++){
        //i == 0,1,2,3,4,5
        items[i].onclick = function(){
            console.log(i) //C
        }
    } 
    // i == 6
    console.log(i) //D 6
    //D一定会执行,C在D后面执行
i打印出值都为6.PNG

解决办法:

方法一:
    var items
    var i
    for(i = 0; i < items.length; i++){
        !function(i){
            items[i].onclick = function(){
            console.log(i) 
            }
        }(i)        
    } 
方法二:
var items
    var i
    items = document.querySelectorAll('li')
    for(i = 0; i < items.length; i++){
        items[i].onclick = function(i){
            return function(){
                console.log(i)
            }
        }(i)
              
        //自执行函数是有返回值的
//循环的时候把i的值赋值给了新的i,新的i是不会i++.
//console.log(i)的执行是很后面的,当执行的时候,i已经为6了。
    }
上一篇下一篇

猜你喜欢

热点阅读