作用域闭包

介绍

参考链接
闭包是由函数以及声明该函数的词法环境组合而成的
闭包是指有权访问另外一个函数作用域的变量的函数

  • 存在函数嵌套;
  • 嵌套的内部函数必须引用在外部函数中定义的变量;
  • 嵌套的内部函数必须被执行。

闭包详细解读

对整个执行过程有了一定认识后,我们似乎也很难解释为什么闭包中的变量a不会被GC回收。只有一个事实是很清楚的,那就是每次执行increase和getValue方法时,都依赖函数test中定义的变量a,但仅凭这个事实作为理由显然也是不具有说服力。

这里不妨抛出一个问题,代码是如何解析a这个标识符的呢?

通过阅读规范,我们可以知道,解析标识符是通过GetIdentifierReference(lex, name, strict),其中lex是词法环境,name是标识符名称,strict是严格模式的布尔型标志。

那么在执行函数increase时,是怎么解析标识符a的呢?我们来分析下!

首先,让lex的值为函数increase的localEnv(函数的本地环境),通过GetIdentifierReference(lex, name, strict)在localEnv中解析标识符a。
根据GetIdentifierReference的执行逻辑,在localEnv并不能解析到标识符a(因为a不是在函数increase中声明的,这很明显),所以会转到localEnv的外部词法环境继续查找,而这个外部词法环境其实就是increase函数的内部属性[[Scope]](这一点我是从仔细看了多遍规范定义得出的),也就是test函数的localEnv的“阉割版”。
回到执行函数test那一步,执行完函数test后,函数test中localEnv中的其他变量的binding都能在后续GC的过程中被释放,唯独a的binding不能被释放,因为还有其他词法环境(increase函数的内部属性[[Scope]])会引用a。
闭包的词法环境和函数test执行时的localEnv是不一样的。函数test执行时,其localEnv会完完整整地重新初始化一遍,而退出函数test的执行上下文后,闭包词法环境只保留了其环境记录中的一部分bindings,这部分bindings会被其他词法环境引用,所以我称之为“阉割版”。
这里可能会有朋友提出一个疑问(我也这样问过我自己),为什么adder.increase()是在全局执行上下文中被调用,它执行时的外部词法环境仍然是test函数的localEnv的“阉割版”?

这就要回到外部词法环境引用的定义了,外部词法环境引用指向的是逻辑上包围内部词法环境的词法环境!

闭包的优缺点

优点:
架起了一座桥梁,让函数外部访问函数内部变量成为可能。
私有化,一定程序上解决命名冲突问题,可以实现私有变量。

缺点:
存在这样的可能,变量常驻在内存中,其占用内存无法被GC回收,导致内存溢出。

闭包的三个特点

//* 1、闭包可以访问当前函数以外的变量
function getOuter(){
  var date = '815';
  function getDate(str){
    console.log(str + date);  //访问外部的date
  }
  return getDate('今天是:'); //"今天是:815"
}
getOuter();
// getDate是一个闭包,该函数执行时,会形成一个作用域A,A中并没有定义变量date,但它能在父一级作用域中找到该变量的定义。

//* 2、即使外部函数已经返回,闭包仍能访问外部函数定义的变量
function getOuter(){
  var date = '815';
  function getDate(str){
    console.log(str + date);  //访问外部的date
  }
  return getDate;     //外部函数返回
}
var today = getOuter();
today('今天是:');   //"今天是:815"
today('明天不是:');   //"明天不是:815"
//* 3、闭包可以更新外部变量的值
function updateCount(){
  var count = 0;
  function getCount(val){
    count = val;
    console.log(count);
  }
  return getCount;     //外部函数返回
}
var count = updateCount();
count(815); //815
count(816); //816

作用域链

为毛闭包就能访问外部函数的变量呢?这就要说说Javascript中的作用域链了。
Javascript中有一个执行环境(execution context)的概念,它定义了变量或函数有权访问的其它数据,决定了他们各自的行为。
当前作用域没有就去向上查找,找不到抛出异常. 在词法环境中,先找全局环境,全局环境找不到,就去外部环境去找

问题一

<script>
    console.log(fun) // 报错  未定义

    console.log(person) // 报错 未定义
</script>

<script>
    console.log(person) // undefined

    console.log(fun) // function

    var person = "Eric";

    console.log(person) // Eric

    function fun() {
        console.log(person) // undefined
        var person = "Tom";
        console.log(person) // Tom
    }

    fun()

    console.log(person) // Eric
</script>
// 无限循环调用
function add(a) {
  function sum(b) {
    // 使用闭包
    a = a + b // 累加
    return sum
  }
  sum.toString = function() {
    // 重写toString()方法
    return a
  }
  return sum // 返回一个函数
}

console.log(add(1)(2)(4).toString())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值