闭包、作用域链、执行上下文

闭包


一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

关键

  1. 是一个函数
  2. 可以引用外部函数的变量

特点

  1. 闭包可以访问当前函数以外的变量,闭包可以更新外部函数的值
  2. 即使外部函数已经返回,闭包仍能访问外部函数定义的变量
  3. 被闭包函数访问的父级及以上的函数的局部变量(如范例中的局部变量 i )会一直存在于内存中,不会被JS的垃圾回收机制回收。

作用

  1. 隐藏变量,避免全局污染
  2. 可以读取函数内部的变量

例:

闭包模拟私有方法

var Counter = (function () {
    var privateCounter = 0;
    function changeBy(val) {
        privateCounter += val;
    }
    return {
        increment: function () {
            changeBy(1);
        },
        decrement: function () {
            changeBy(-1);
        },
        value: function () {
            return privateCounter;
        },
    };
})();

console.log(Counter.value()); /* logs 0 */
Counter.increment();
Counter.increment();
console.log(Counter.value()); /* logs 2 */
Counter.decrement();
console.log(Counter.value()); /* logs 1 */

 闭包循环

使用闭包

for (var i = 0; i < 3; i++) {
    (function(num) {
        setTimeout(function() {
            console.log(num);
        }, 1000);
    })(i);
}
// 0
// 1
// 2

未使用闭包

var data = [];

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

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

  问题

使用闭包时变量不会被垃圾回收机制收销毁,需要手动销毁

 JS规定在一个函数作用域内,程序执行完以后变量就会被销毁,这样可节省内存;

使用闭包时,按照作用域链的特点,闭包(函数)外面的变量不会被销毁,因为函数会一直被调用,所以一直存在,如果闭包使用过多会造成内存销毁

例:

function getData() {
    var buf = new Array(1000).join('*');
    var index = 0;
    return function () {
        index++;
        if (index < buf.length) {
            return buf[index - 1];
        } else {
            buf = null; // 不再使用buf, 手动清除引用。
            return '';
        }
    };
}
var data = getData();
var next = data();
while (next !== '') {
    // process data()
    next = data();
    console.log(next);
}

getData()返回一个函数,就是说返回了一个闭包。

闭包引用了getData()函数中的局部变量buf。var data = getData()执行完后,getData()的局部变量buf不会被释放。这是因为data变量引用了getData()返回的闭包,而该闭包又引用了变量buf,所以javascript引擎不会回收buf内存。


作用域链理解闭包

var scope = '全局作用域';
function localScope() {
    var scope = '本地作用域';
    function f() {
        return scope;
    }
    return f;
}

var foo = localScope(); // foo指向函数f
foo(); // 调用函数f()

作用域链是一个数组,结构如下:

fContext = {
    Scope: [AO, localScopeContext.AO, globalContext.VO],
}

执行上下文

执行上下文是当前 JavaScript 代码被解析和执行时所在环境的抽象概念。

三个类型

  1. 全局执行上下文:只有一个,浏览器中的全局对象就是 window 对象this 指向这个全局对象
  2. 函数执行上下文:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
  3. eval函数执行上下文: 指的是运行在 eval 函数中的代码,很少用而且不建议使用。

 8步过程

  1. 进入全局代码,创建全局执行上下文, 全局执行上下文压入执行上下文栈
  2. 全局执行上下文初始化
  3. 执行 localScope函数,创建 localScope函数执行上下文,localScope 执行上下文被压入执行上下文栈
  4. localScope执行上下文初始化,创建变量对象、作用域链、this等
  5. localScope函数执行完毕,localScope执行上下文从执行上下文栈中弹出
  6. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
  7. f 执行上下文初始化,创建变量对象、作用域链、this等
  8. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

Q:函数f 执行的时候,localScope函数上下文已经被销毁了,那函数f是如何获取到scope变量的呢?

A:函数f 执行上下文维护了一个作用域链,会指向指向localScope作用域,作用域链是一个数组,结构如下

fContext = {
    Scope: [AO, localScopeContext.AO, globalContext.VO],
}

函数上下文中,两个对象

  1. 变量对象(VO):是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
  2. 活动对象(AO) :当进入到一个执行上下文后,这个变量对象才会被激活,这时候活动对象上的各种属性才能被访问。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值