关于做用域闭包的一些理解

闭包

红宝书上对闭包的定义:有权访问另一个函数做用域中变量的函数。html

MDN对闭包的定义是:是可以访问自由变量的函数。算法

自由变量:是指在当前函数中可使用的(可是既不是arguments也不是本函数定义的局部变量)。浏览器

两个点:闭包

  1. 是个函数
  2. 能访问另外一个函数做用域中的变量,即便外层函数的上下文已经被销毁

就是说咱们常见的好比内部函数从外部函数返回这种状态,该内部函数就是闭包。能够看以下特性中的示例!函数

说明闭包的几个特性:优化

  • 能够访问当前函数之外的变量
  • function outer() {
                var date = '11月1日'; function inner(str) { console.log(str + date) } return inner('today is ') } outer() function outer() { var date = '11月1日'; return function () { console.log('today is ' + date) }() } outer() // 上下两例均返回“today is 11月1日”
  • 即便外部函数已经返回,闭包仍然可以访问外部定义的变量
  • function outer() {
                var date = '11月1日'; function inner() { console.log('today is ' + date) } return inner } // 如下是拆成分步执行,实际等同于outer()();
    // 先执行outer()获得一个返回值inner,此时outer函数执行完毕,跳出outer这个外层函数
    // 而后执行inner(),可是此时依然可用outer定义的变量date var getDate = outer() getDate()
  • 闭包能够修改外部函数的变量的值
  • function outer() {
                var date = '11月1日'; function inner(newDate) { date = newDate // 将传入的值替换掉外层的date console.log('today is ' + date) } return inner } var getDate = outer() getDate('191101') // “today is 191101”

闭包的做用域链:

如下例分析:this

        var scope = 'global';
        function checkscope() { var scope = 'local'; function fun() { return scope; } return fun; } var check = checkscope(); console.log(check()); // 'local'

执行过程:spa

  1. 进入全局代码,建立全局执行上下文并压入执行上下文栈
  2. 全局上下文初始化
  3. 执行checkscope函数,建立checkscope执行上下文并将其压入执行栈
  4. checkscope函数上下文初始化,建立变量对象、做用域链、this
  5. checkscope函数执行,执行完后checkscope执行上下文从执行栈中弹出
  6. 因为checkscope函数返回了一个f函数,所以建立f()执行上下文,将fun()的执行上下文压入执行栈,而后执行fun(),一样的,建立变量对象、做用域链、this
  7. fun()执行完毕后,从执行栈中弹出。

这个流程能够看到,checkscope执行完毕后是带着返回值弹出了执行栈的,在fun执行的时候checkscope函数的上下文已经被销毁了,可是,函数fun执行上下文维护了一个做用域链,结构以下:code

funContext = {
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

指向关系是:当前做用域 -> checkscope -> 全局,不论checkscope 是否被销毁,fun函数均可以经过fun的做用域链找到它,这是闭包实现的关键htm

有关全局环境下函数嵌套与非嵌套时做用域链的指向分析,参考:深刻浅出图解做用域链和闭包

 

一些常见的闭包考题

题1:

        var data = [];

        for (var i = 0; i < 3; i++) {
            data[i] = function () {
                console.log(i);
            };
        }

        data[0]();  
        data[1]();
        data[2]();

答案很显然:三、三、3

上面这个题的分析参照个人另外一篇博客分析:let和const,里面详细的分析了这个题的过程~

如何才能让这个题输出咱们想要的0、一、2呢?

解法一:博客当中给出了使用let的写法,很简单只须要将for中的var替换成let便可

解法二:

还有别的方法吗?本片将采用闭包的方式解决这个问题~~~回想一下闭包的状态是什么?内层函数可使用外层函数定义的变量呀!

因此第一步:咱们在function中return一个新的函数,在内层函数中访问变量 i。

而后考虑咱们如何才能把当前的i传到外层function中呢?马上咱们联想到利用参数!

第二步:外层函数自执行,将 i 做为参数传入外层的function中

所以获得以下优化后的代码:

        var data = [];

        for (var i = 0; i < 3; i++) {
            data[i] = (function (i) {
                return function () {
                    console.log(i);
                }
            })(i)
        }

        data[0]();
        data[1]();
        data[2]();

结果为:0、一、2

正是咱们要的结果啦~

解法三:

这里还能够改为咱们常见的定时器写法

        for (var i = 0; i < 3; i++) {
            (function (i) {
                setTimeout(function () {
                    console.log(i)
                }, 100 * i)
            })(i)
        }

解法三其实和解法二的本质相同,都是将变量 i 的值复制给外层function的参数 i ,在函数内部又建立一个用于访问 i 的匿名函数,这样每一个函数都有一个 i 的副本,就不会相互影响!

题2:这两段代码在checkscope执行完后,f所引用的自由变量scope会被垃圾回收吗?why?

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

checkscope()();  

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope(); 
foo();   

结论是:第一个代码段中的scope特定时间后会被回收,第二段代码的自由变量不会回收

分析:

如今主流浏览器的垃圾回收算法是:标记清除。当垃圾回收开始时,从root开始寻找这个对象的引用是否可达,也就是找是否存在相互引用,若是引用链断裂,那么这个对象就能够被回收!

对于第一段代码,checkscope()执行完毕后被弹出执行栈,而且也没有其余引用,Root开始查找时不可达,所以闭包引用的自由变量scope过段时间能够被回收

对于第二段代码,因为var foo = checkscope(),checkscope()执行完成后,将foo()执行上下文压入执行栈,foo()指向堆中的自由变量 f ,对于Root来讲可达,所以不会被回收!!

若是想要scope必定能够被回收,只要加:foo = null;便可!

相关文章
相关标签/搜索