美文网首页
初识闭包

初识闭包

作者: 学的会的前端 | 来源:发表于2019-02-25 10: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)

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

  • 当执行一个函数,就会初始化一个活动对象。
  • 当在执行上下文A创建函数fn,fn的作用域链就指向A的活动对象。
    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
    }
  • 词法作用域
    只要看层级关系,分析语句的词法,就可以确定a到底是谁,不需要执行代码
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了。
    }

相关文章

  • Javascript 闭包

    闭包 (注:所以案例以 javascript 实现) 初识闭包 什么是闭包 MDNClosures are fun...

  • 初识闭包

    闭包 闭包就是指有权访问另一个函数作用域中的变量的函数。在后台执行环境中,闭包的作用域链包含着它自己的作用域,包含...

  • 初识闭包

    闭包,JavaScript语言一个很重要的点,可以说js库和各个框架百分百会用到闭包。那到底什么是闭包?闭包用来做...

  • 初识JavaScript闭包

    前言: 学习JavaScript的时候老是听说闭包,感觉他很厉害的样子,所以就来会会他吧! 闭包是什么? 闭包是指...

  • 从??(空合运算符)看autoclosure

    ## 1:autoclosure初识 先看一下书上的解释:自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数...

  • swift-闭包

    闭包 闭包定义 闭包简化 - 尾随闭包 闭包参数 闭包返回值 闭包的循环引用

  • 闭包,闭包,闭包

    1、这家伙到底是什么? 网上关于这个的讨论的太多了太多了,有各种的举例子,但是大部分还在寻找这个答案的小伙伴对于变...

  • 闭包-Closures [swift 5.1]

    闭包的语法 尾随闭包 闭包逃离 自动闭包

  • Day7 闭包(Closures)

    本页包含内容:• 闭包表达式• 尾随闭包• 值捕获• 闭包是引用类型• 逃逸闭包• 自动闭包 1、闭包表达式 闭包...

  • Python闭包

    闭包 = 环境变量 + 函数 调用闭包内部的环境变量 闭包的经典误区 闭包与非闭包实现人类走路 非闭包 闭包

网友评论

      本文标题:初识闭包

      本文链接:https://www.haomeiwen.com/subject/mqhwyqtx.html