0
点赞
收藏
分享

微信扫一扫

JS高级--闭包

Gascognya 2022-04-13 阅读 67

一、理解闭包

1、如何产生闭包

  • 当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包

2、闭包到底是什么(使用chrome调试查看)

  • 理解一:闭包是嵌套的内部函数(绝大多数人理解)
  • 理解二:包含被引用变量(函数)的对象(少数人理解)
  • 注意:闭包存在于嵌套的内部函数中

3、产生闭包的条件

  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)
  • 执行外部函数
function fn1 () {
    var a = 2
    var b = 'abc'
    function fn2 () { //执行函数定义就会产生闭包(不用调用内部函数)
        console.log(a)
    }
    // fn2()
}
fn1()

二、常见的闭包

1、将函数作为另一个函数的返回值

// 1. 将函数作为另一个函数的返回值
function fn1() {
    var a = 2
    function fn2() {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1() // 实际上,这时调用外部函数时已经产生了闭包,此时外边将得到一个包含闭包的函数,当你调用这个函数的时候,就能访问这个闭包里所有的数据【变量、函数】
f() // 3
f() // 4 正常情况下,函数执行完局部变量就会被释放,但由于闭包的存在,a存活了下来,并且之后还能被访问到
// 到这里只产生了一个闭包(因为只创建了一次内部函数对象),和内部函数调用多少次无关

2、将函数作为实参传递给另一个函数调用

// 2. 将函数作为实参传递给另一个函数调用
// 个人理解:定时器只是调用函数,不是定义函数对象。所以time不能够产生闭包。
function showDelay(msg, time) {
    setTimeout(function () {
        alert(msg)
    }, time)
}
showDelay('atguigu', 2000)

三、闭包的作用

1、使用函数内部的变量在函数执行完后, 仍然存活在内存中(延长了局部变量的生命周期)

2、让函数外部可以操作(读写)到函数内部的数据(变量/函数)

function fn1() {
    var a = 2
    function fn2() {
        a++
        console.log(a)
        // return a
    }
    function fn3() {
        a--
        console.log(a)
    }
    return fn3
}
var f = fn1() // fn3存的是函数地址 在栈里面 fn3这个变量没了 但是在堆里面的函数对象还在 返回给f了
fn1() // 这种情况闭包也不存在了  总结:闭包也是会被垃圾回收的,需要定义一个变量来一直指向闭包函数体的地址值,让闭包一直存在
f() // 1
f() // 0
// fn1()执行完由于是函数释放成为垃圾对象,当f=fn1时由于是赋值操作把地址赋值给了所以现在是f和fn1都指向一个内存空间,
// 所以fn1执行完不能释放因为f还指向这个内存空间

问题:
  1. 函数执行完后, 函数内部声明的局部变量是否还存在?  一般是不存在, 存在于闭中的变量才可能存在
  2. 在函数外部能直接访问函数内部的局部变量吗? 不能, 但我们可以通过闭包让外部操作它

四、闭包的生命周期

1、产生: 在嵌套内部函数定义执行完时就产生了(不是在调用)

2、死亡: 在嵌套的内部函数成为垃圾对象时

function fn1() {
//调用函数后,此时闭包就已经产生了(函数提升, 内部函数对象已经创建了)
    var a = 2
    function fn2 () {
        a++
        console.log(a)
    }
    return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //闭包死亡(包含闭包的函数对象成为垃圾对象)

五、闭包的应用:定义JS模块

  • 具有特定功能的js文件
  • 将所有的数据和功能都封装在一个函数内部(私有的)
  • 只向外暴露一个包含n个方法的对象或函数
  • 模块的使用者, 只需要通过模块暴露的对象调用方法来实现对应的功能
// 方法一
function myModule() {
    //私有数据
    var msg = 'My atguigu'
    //操作数据的函数
    function doSomething() {
        console.log('doSomething() '+msg.toUpperCase())
    }
    function doOtherthing () {
        console.log('doOtherthing() '+msg.toLowerCase())
    }

//向外暴露对象(给外部使用的方法)
return {
    doSomething: doSomething,
    doOtherthing: doOtherthing
}
}
// 方法二
(function () {
  //私有数据
  var msg = 'My atguigu'
  //操作数据的函数
  function doSomething() {
    console.log('doSomething() '+msg.toUpperCase())
  }
  function doOtherthing () {
    console.log('doOtherthing() '+msg.toLowerCase())
  }

  //向外暴露对象(给外部使用的方法)
  window.myModule2 = {
    doSomething: doSomething,
    doOtherthing: doOtherthing
  }
})()

六、闭包的缺点及解决

1、缺点

  • 函数执行完后, 函数内的局部变量没有释放, 占用内存时间会变长
  • 容易造成内存泄露

2、解决

  • 能不用闭包就不用
  • 及时释放
function fn1() {
    var arr = new Array[100000]
    function fn2() {
        console.log(arr.length)
    }
    return fn2
}
var f = fn1()
f()

f = null //让内部函数成为垃圾对象-->回收闭包

七、补充(内存溢出和内存泄漏)

1、内存溢出

  • 一种程序运行时出现的错误
  • 当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误

2、内存泄漏

  • 占用的内存没有及时释放
  •  内存泄漏积累多了就容易导致内存溢出
  • 常见的内存泄露:
  1. 意外的全局变量
  2. 没有及时清理的计时器或回调函数
  3. 闭包

八、面试题

//代码片段一

var name = "The Window";

var object = {

    name : "My Object",

    getNameFunc : function(){

        return function(){

            return this.name;

        };

    }

};

alert(object.getNameFunc()());  //The Window




//代码片段二

var name2 = "The Window";

var object2 = {

    name2 : "My Object",

    getNameFunc : function(){

        var that = this;

        return function(){

            return that.name2;

        };

    }

};

alert(object2.getNameFunc()()); //My Object
举报

相关推荐

0 条评论