《你不知道的javascript》打卡学习笔记📒(5)

·  阅读 68

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第 7 天,点击查看活动详情

作用域闭包

什么是闭包?

当函数可以记住并访问所在的词法作用域时,就产生了闭包

  • 函数;
  • 记住并访问;
  • 词法作用域
function foo() {
    var a = 2;

    function bar() {
        console.log(a);
    }

    bar();
}

foo();
复制代码
function foo() {
    var a = 2;

    function bar() {
        console.log(a);
    }

    return bar();
}

var baz = foo();

baz(); // 2;
复制代码
  1. 函数bar()的词法作用域能够访问foo()函数的词法作用域,这个是没问题的。
  2. 然后我们将bar()函数本身当作一个值类型进行传递,比如这个例子中的函数对象本身
  3. bar()可以被正常执行,但是在这个例子中,它在自己定义的词法作用域以外的地方执行
  4. 在foo()执行完后,通常是要被销毁的,因为js引擎里面有垃圾回收机制用来释放不再使用的内存空间
  5. 但是闭包,就是可以阻止foo()的作用域被销毁,事实上foo()的内部作用域依然存在,没有被回收
  6. 因为bar()函数还在使用这个内部作用域
  7. 因此foo()的作用域会一直存活,以供bar()在之后的任何时间使用。

bar()依然保持对该作用域的引用,这个引用就叫作闭包

这个函数在定义的词法作用域以外的地方被调用,闭包使得函数可以继续访问定义时的词法作用域。

使用传递参数的方式产生的闭包举例:

function foo() {
    var a = 2;

    function baz() {
        console.log(a);
    }
    bar(baz);
}

function bar(fn) {
    fn();
}
复制代码

把内部函数baz传递给bar,当调用这个内部函数的时候,它涵盖的foo()的作用域的闭包就可以看到了,因为它可以访问a

间接传递函数产生的闭包:

var fn;
function foo() {
    var a = 2;

    function baz() {
        console.log(a);
    }
    fn = baz;
}

function bar(fn) {
    fn();
}

foo();
bar(); // 2;
复制代码

无论通过哪种手段将内部函数传递到所在的词法作用域以外,它都会持有对原始定义作用的引用,无论在何处执行这个函数都会使用闭包。

再次理解闭包

常见的闭包:

function wait(message) {
    setTimeout( function timer() {
        console.log(message);
    },1000);
}

wait('hello,jingda');
复制代码
  1. 将一个内部函数timer传递给setTimeout,timer具有涵盖wait作用域的闭包,因此还保留对变量的引用。
  2. wait执行1000毫秒之后,它的内部作用域并不会消失,timer函数依然会保有wait作用域的闭包
  3. 在引擎的内部,内置函数setTimeout有一个对参数的音乐,这个参数加上为fn,引擎会调用这个函数,在这个例子就是内部的timer函数,而词法作用域在这个过程一直都是保持完整的。这个就是闭包。

本质上,如果将函数当作第一级的值类型到处传递,会看到闭包在这些函数中的应用。

在定时器,事件监听器,ajax请求,跨窗口通信,web workers,或者其他异步同步的任务中,只要使用了回调函数,实际上就是在使用闭包。

循环和闭包

for(var i = 1;i <= 5;i++) {
    setTimeout( function timer() {
        console.log(i);
    },i * 1000);
}
复制代码

结果是:每秒一次输出五次6;

  1. 首先是为什么输出6,这个循环的终止条件是i>5,条件成立的第一个值就是6
  2. 输出显示的是循环结束时的最终值。
  3. 延迟函数的回调会在循环介绍时才执行。
  4. 循环的五个函数在各个迭代中是分别定义的,但是他们都封闭在了一个共享的全局作用域中,因此,只有一个i
  5. 如果将延迟函数的回调重新定义5次,完全不使用循环,和这段代码是一样的效果。
  6. 我们需要更多的作用域。
  • 立即执行函数

下面这样直接加是不起作用的哦,还是一样的效果: 此时的立即执行函数作用域里面是空的,没有实质的变量。

for(var i = 1;i <= 5;i++) {
    (function () {
      setTimeout( function timer() {
        console.log(i);
      },i * 1000);
    })();
}
复制代码

立即执行函数,需要有自己的变量去存储i的值:

for(var i = 1;i <= 5;i++) {
    (function (j) {
      setTimeout( function timer() {
        console.log(j);
      },j * 1000);
    })(i);
}
复制代码

闭包和块作用域

for(var i = 1;i <= 5;i++) {
    let j = i;
    setTimeout( function timer() {
        console.log(j);
    },j * 1000);
}
复制代码
for(let i = 1;i <= 5;i++) {
    setTimeout( function timer() {
        console.log(i);
    },i * 1000);
}
复制代码

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包

分类:
前端
收藏成功!
已添加到「」, 点击更改