js_闭包

定义

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式是在一个函数中创建另一个函数

函数是怎样创建的

js在执行代码前会对代码进行预处理。例如用var声明变量和用function声明函数,这一阶段同样包括声明提升的情况。当预处理发现有函数声明时,就会创建该函数。但预处理会跳过所有函数内部代码。只有调用函数时才,执行流进入函数内部才会进行处理。

函数创建时发生了什么

创建一个预先包含外部环境变量对象的作用域链,这个作用域链被保存在函数内部的[[scope]]属性中。

函数被调用时发生了什么

函数被调用,即执行流进入函数中

  • 为函数创建一个执行环境,复制函数内部的[[scope]]属性中的对象构建起函数执行环境的作用域链。
  • 使用arguments和其它命名参数的值来初始化函数的活动对象。
  • 将函数的活动对象(在此作为变量对象使用)推入执行环境作用域链的前端。

如上例所示,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。在函数中访问变量时,会从作用域链中从本地活动变量一直搜索到全局变量对象。函数执行完毕后,局部活动对象被销毁,内存中仅保存全局执行环境和它的变量对象。

在函数内部创建函数形成闭包后,作用域链是怎样的

        function outer(propertyAge) {
            return function (obj1, obj2) {
                var val1 = obj1[propertyAge];
                var val2 = obj2[propertyAge];
                if (val1 > val2) {
                    console.log(`${obj1.name}比较大`);
                } else if (val1 < val2) {
                    console.log(`${obj2.name}比较大`);
                } else {
                    console.log(`${obj1.name}与${obj2.name}一样大`);
                }
            }
        }
        var person1 = {
            name: 'Nick',
            age: 27
        };
        var person2 = {
            name: 'Jane',
            age: 25
        };
        var compare = outer('age');
        compare(person1, person2); //'Nick比较大'

从头开始捋:
1 创建outer函数时,将全局作用域的作用域链(即全局执行环境下的变量对象的指针)的副本加入到函数的内部的[[scope]]属性中。
2 调用outer函数,为outer函数创建一个执行环境,创建作用域链,复制函数内部的[[scope]]属性的变量对象的指针,把它塞进作用域链。
3 将outer函数的arguments对象和其它命名参数的值初始化为活动变量,作为当前局部作用域的变量对象,并把它塞进作用域链的前端。(此时的outer函数的作用域链拥有本地活动对象和全局变量对象的指针)
4 执行outer函数完毕,将内部匿名函数返回给全局变量compare。执行流进入到outer函数内时便会对局部作用域执行预处理(js预处理不会进入到函数内部,全局作用域预处理阶段outer函数连局部作用域都没有创建,当代码执行流进入到outer函数内部后才会创建匿名函数),内部函数因为函数声明提升会被直接创建(执行流进入到outer函数中时,匿名函数就被创建了)。执行创建函数的流程————将外部作用域的作用域链的副本存入到自己的[[scope]]属性中去。执行return语句后执行流会直接跳出当前作用域,outer函数已经完成使命,其作用域被直接销毁,作用域中保存的作用域链也会被直接被销毁。
5 outer函数的作用域和作用域链虽然被销毁了,但是对outer函数作用域链中保存的全局变量对象和outer函数本地活动对象的引用被复制保存到匿名函数中了(这些变量对象和活动对象被完好地保存在堆内存中,销毁的只是outer函数作用域链对它们的引用的指针),对于对象而言,只要引用存在就不会被销毁。
6 调用匿名函数,创建匿名函数的作用域,创建该作用域的作用域链,将[[scope]]属性中的作用域链副本塞进作用域链,使用arguments对象和其它命名参数的值初始化本地活动对象,并将本地活动对象视为变量对象,塞进匿名函数作用域链的最前端。此时的匿名函数作用域链按索引优先级包括了本地活动对象,outer函数的活动对象,全局变量对象。也因此匿名函数可以访问到在outer函数和全局作用域中定义的变量,函数以及参数。完成闭包操作。

需要注意的地方

  • js预处理不会进入函数。当执行流进入函数创建局部作用域后才会对局部作用域进行预处理,才会创建匿名函数。
  • 创建函数时只会做一件事,保存外部作用域的副本到函数的[[scope]]内部属性中。
  • 调用函数执行的操作:
    1.创建局部作用域。
    2.复制[[scope]]中保存的作用域链。
    3.初始化本地活动对象并塞到作用域链最前端(索引最优先)
posted @ 2020-07-03 23:13  Syinho  阅读(228)  评论(0编辑  收藏  举报