javascript中的一些核心知识点(一)闭包

作用域链


闭包是javascript中一个重要知识点,也是javascript中一块魔法,我们在不熟悉他的情况下可能便经常使用了,熟悉他了解他是从初级至中级的一个标志。要真正了解闭包,就得从作用域链说起: javascript中,作用域链的作用是控制变量的访问顺序,仅此而已

首先,javascript在运行时需要一个环境,这个环境便是我们所谓执行上下文(execution context)。执行上下文决定了变量或者函数有权利访问其它数据,每个执行环境都有一个与之关联的变量对象,用于存储执行上下文中定义的变量或者函数。一般情况下我们所处的全局执行上下文便是window对象,所以全局范围内创建的所有对象全部是window的属性或者方法函数的变量对象一般是其活动对象(activation Object)

其次,javascript没有块级作用域的概念,但是每个函数有自己的执行上下文,这个便是变相的块级作用域。

每执行一个函数时,函数的执行上下文会被推入一个上下文栈中,函数若执行结束,这个上下文栈便会被弹出,控制权变回之前的执行上下文。

当代码在执行上下文中执行时,变回创建一个作用域链, 这个作用域链控制着执行上下文数据访问顺序。

function test() {
  var a = 2;
  console.log(a);
}
var a = 1;
test();

在这里便具有两个执行上下文,一个是window,一个是test函数。

首先,在test执行之前,我们全局执行上下文已经存在,他便是window,这个时候我们会有a与test在作用域最前端。

执行test时候,形成test执行上下文,于是最前端的执行上下文变成了test,这个时候会先形成活动对象,包括arguments以及a

在console.log时,会访问作用域链最近的a变量,也就是2,这个是打印出2的根本原因,若是没有作用域链这个顺序就坏了

下面是test执行时候的图示:


所以作用域链相关的知识点是:

① 控制变量访问顺序
② 执行上下文包含一个作用域链的指针
③ 该层函数外部有几个函数,便会有几个活动对象待处理,作用域链指针会指向其外部活动对象
④ 作用域链为执行上下文时函数内部属性,不要妄想去操作

闭包的形成

闭包的形成便是一个函数执行上下文中有一个变量被其内部函数使用了,并且这个内部函数被返回了,便形成了一个闭包

由于函数调用后,外部临时变量保存着内部的引用,执行时会形成内部上下文环境,内部的函数会包含外部的作用域链指向的变量对象,

这个时候就算外部执行环境消耗,由于外部保存着外部函数的活动对象的引用,所以这个变量对象不会被消耗,这个是闭包产生的原因
function test() {
  var a = 2;
  return function () {
    console.log(a);
  };
}
var b = test();
b();

这里会形成三个执行环境,一个是全局的,一个是test的,一个是匿名函数(最终是b函数)的,我们依旧从test执行时说起

当test函数执行时:
var b = test();

会形成一个执行上下文,执行上下文包含一个作用域链指针,并且会形成一个活动对象

这里test的作用域链只是一个指针,他只是引用这个活动对象,执行结束后执行上下文会被释放,作用域链也会消失,但是其活动对象未必会GC

在b执行时,其匿名函数的作用域链便指向了外部函数的活动对象,不要问他怎么获得这个指针引用的,他就是知道,于是test的活动对象将一直被保存,直到b调用结束

这里b执行的关系是:

经典例子

关于闭包有一个经典的例子,他便是for循环的例子:
function createFn() {
  var ret = [], i;
  for (i = 0; i < 10; i++) {
    ret[i] = function () {
      return i;
    };
  }
  return ret;
}
var fns = createFn();

这段代码非常简单,根据一个数组形成10个函数,每个函数返回其索引值,这类应用在实际工作中会经常用到,只不过我们需要的是其索引对应的数据,而不是简单的索引了

这类会createFn执行时会有两个执行环境,一个是自己的,一个是windows的,内部执行环境作用域链会指向一个活动对象

当然fns数组中任意一个函数执行时,其会使用到createFn的活动对象中的数据i,而该活动对象是被10个函数共用的,都是10,所以与预期不合

该问题的处理便是各自形成自己的闭包:

function createFn() {
  var ret = [], i;
  for (i = 0; i < 10; i++) {
    ret[i] = (function (i) {
      return function () {
        return i;
      };
    })(i);
  }
  return ret;
}
var fns = createFn();

这里循环中会形成10个独立的执行上下文,其中的10个活动对象的arguments都保存了外部i的独立数据,而内部又形成一个闭包访问立即执行函数的数据,所以数据正确了......

其它闭包


requireJS中的闭包


标准的requireJS来说都是一个AMD的模块,比如:
define(function () {
  var add = function (x, y) {
    return x + y;
  };
  return {
    add: add
  };
});

我们知道,requireJS每一次加载其模块皆会被执行一次,并且只会执行一次,这个模块会被requireJS所保存,所以这个匿名函数活动对象是不会被释放的,且是唯一的

这个时候我们很多组件便可以统一使用其功能即可,比如生成uuid什么的......当然,这种不释放的问题,也会导致heap值的提升,这个是不是有问题便需要各位去验证了

webapp中的闭包


webapp一般会使用requireJS管理模块,而内部又会形成许多view的实例,这个实例并且会保存下来,这样也会导致很多函数的活动对象得不到释放

一来二往之间,heap值会比传统网站高,这个是webapp一块比较头疼的地方,需要慢慢优化。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值