一块儿来了解一下闭包吧

做用域

做用域是静态的,在你写代码的时候就肯定了。javascript

  • 做用域分为全局做用域和局部做用域。
  • 全局做用域:任何地方都能访问到的对象拥有全局做用域。
  • 局部做用域:只在固定的代码片断内可访问到,最多见的例如函数内部。
  • 做用域链:当你查找一个变量的时候,先在自身做用域中查找,没找到就像上级函数做用域中依次查找,这个过程就会造成一个做用域链。

执行上下文

执行上下文能够理解为当前代码的执行环境,执行上下文是动态的,在执行调用的时候才会产生。java

  • 每一个执行上下文包括变量对象(VO)、做用域链(ScopeeChain)、this指针。
  • 执行上下文的生命周期能够分为两个阶段:建立阶段(执行上下文会分别建立VO,ScopeChain以及肯定this指向)、执行阶段(完成变量赋值,函数引用以及执行其余代码)。

什么是闭包

  • 在javascript高级程序设计中的定义:闭包是指有权访问另外一个函数做用域中变量的函数。
  • 函数做用域和函数执行的做用域不在同一做用域就是闭包。
  • 闭包是链接函数内部和函数外部的桥梁,函数外部能够访问到函数内部的变量(好比:函数做为另外一个函数的返回值)
  • 闭包的实现主要思路是返回一个函数,因此闭包是典型的高阶函数。

闭包产生的条件

  • 函数嵌套
  • 内部函数引用了外部函数的变量

闭包的生命周期

  • 产生:在嵌套内部函数定义执行完的时候闭包就产生了,过程以下:
function fn1() {
    //函数提高,内部函数已经建立。请看图片1
    var a = 1
    function fn2() {
        a++ ////代码执行到这的时候,请看图片3
        console.log(a)
    }
    return fn2 //代码执行到这的时候,请看图片2
}
let fn3 = fn1()
fn3() //代码执行到这的时候,请看图片2
fn3 = null //闭包死亡
复制代码

图片1面试

图片2
图片3

  • 死亡:嵌套在内部函数的对象成为垃圾对象时就死亡了。

闭包的应用

  • 让函数外部访问私有变量 特权方法:有权访问私有变量(局部的变量、函数的参数或者函数内部定义的其余函数)和私有方法的方法
function Pay(value) { //Pay是一个闭包
        let money = value
        this.getMoney = function() {
            return money
        }
        this.setMoney = function(value) {
            money = value
        }
}
let pay = new Pay(8000)
console.log(pay.money) //undefined 在外面没法访问到私有变量money
console.log(pay.getMoney()) //8000 //getMoney这个特权方法能够经过对象访问,getMoney能够访问money
pay.setMoney(10000)
console.log(pay.getMoney()) //1000
复制代码
  • (和上面的相似)定义JS模块,将全部的数据和功能都封装在一个函数内部(私有的),只向外暴露一个包含n个方法的对象或者函数。
function myModule() {
     let msg = 'hello world' //私有数据
     function doSomething() { //操做数据的函数
         console.log('dosomething'+msg.toUpperCase())
     }
     function doOtherthing() {
         console.log('doOtherthing'+msg.toLocaleLowerCase())
     }
     return{
        doSomething:doSomething,
        doOtherthing:doOtherthing
     }
}
//模块的使用者
let module = myModule()
module.doSomething()
module.doOtherthing()
复制代码
  • 每一个1秒输出对应li的索引号(利用for循环建立当即执行函数)
for(var i=0;i<lis.length;i++) {
        (function(i) {
            setTimeout(() => {
                alert(i)
            }, 1000);
        })(i)
}
复制代码
  • 局部做用计数器
function count() {
    let num = 0
    return function() { //函数做为返回值
        num++
        return num
    }
}
let fn = count()
fn() //1
复制代码

闭包优势

  • 延伸了变量的做用范围和生命周期
  • 避免全局变量的污染

闭包缺点

  • 通常函数的做用域和函数中的变量都会在函数执行结束被垃圾回收机制回收。因为闭包中的变量在外层函数被调用以后不会被自动清除,因此闭包使用不当容易形成内存泄漏。为了防止内存泄漏,咱们须要将再也不使用的变量经过手动释放。能不用闭包就不用。
内存溢出:是一种程序运行出现的错误,当出现运行须要的内存超过了剩余的内存时,就抛出内存溢出的错误。
内存泄漏:占用的内存没有及时释放,内存泄漏积累多了就容易致使内存溢出。常见的内存泄漏:闭包、意外的全局变量(函数中没有用var|let|const声明的变量)、没有及时清理的计时器或回调函数。
function demo() {
    let name = 'Eileen'
    console.log('name')
}
demo() //demo函数执行完,demo函数的做用域没有被外部引用,则该函数的做用域及其变量都会在函数执行完后被销毁
复制代码
function foo() {
    let name = 'Eileen'
    return function() {
        name = 'John'
        return name
    }
}
let fn = foo() 
console.log(fn()
fn = null //在全局做用域中执行fn函数,fn函数的环境依赖于foo函数环境,因此foo函数内的做用域及其变量不会被销毁。fn = null释放对闭包的引用,foo函数做用域被销毁
复制代码
  • 使用闭包以后,能够访问到函数内部的私有变量,经过特权方法能够随意更改私有变量。

最后再来一道面试题吧!

function fun(n,o) {
    console.log(o)
    return {
        fun: function(m) { 
            return fun(m,n)
        }
    }
}
var a = fun(0) //undefined
    a.fun(1)   //0
    a.fun(2)   //0
    a.fun(3)   //0
var b = fun(0).fun(1).fun(2).fun(3)   //undefined,0,1,2
var c = fun(0).fun(1)   //undefined,0
    c.fun(2)   //1
    c.fun(3)   //1
复制代码

结语

好啦,以上就是我对闭包的理解和解释啦,若是有不对的地方,欢迎各位大牛指导一下😁bash

相关文章
相关标签/搜索