javascript(JS)高级进阶(五)闭包、内存溢出、内存泄漏

闭包

1、 如何产生闭包

  • 当一个嵌套的内部函数引用了嵌套的外部函数的变量(函数)时,就产生了闭包

2、闭包时什么

  • 使用chorme 调试查看
  • 理解一: 闭包时嵌套的内部函数(大部分人)
  • 理解二:包含被引用变量的对象(少部分人)

3、产生闭包的条件

  • 函数嵌套
  • 内部函数引用了外部函数的数据(变量/函数)

闭包的作用

1、 使用函数内部的变量在执行完后,仍然存活在内存中(延长了局部变量的生命周期)

2、让函数外部可以操作(读写)到函数内部的(变量/函数)

问题:

1、函数执行完后,函数内部变量是否还存在?

  • 一般不存在,存在于闭包中的变量才可能存在(要var f = fun())指向内部函数

2、在函数外部能直接访问到内部的局部变量吗

  • 不能,但是可以通过闭包让外部操作它,要return内部函数

闭包的生命周期

产生:在嵌套的内部函数定义执行时就产生了(不是在调用)

死亡:在嵌套的内部函数称为垃圾对象时

闭包

开发者通常应该都知道“闭包”这个通用的编程术语。

闭包 是指内部函数总是可以访问其所在的外部函数中声明的变量和参数,即使在其外部函数被返回(寿命终结)了之后。在某些编程语言中,这是不可能的,或者应该以特殊的方式编写函数来实现。但是如上所述,在 JavaScript 中,所有函数都是天生闭包的(只有一个例外,将在 “new Function” 语法 中讲到)。

也就是说:JavaScript 中的函数会自动通过隐藏的 [[Environment]] 属性记住创建它们的位置,所以它们都可以访问外部变量。

在面试时,前端开发者通常会被问到“什么是闭包?”,正确的回答应该是闭包的定义,并解释清楚为什么 JavaScript 中的所有函数都是闭包的,以及可能的关于 [[Environment]] 属性和词法环境原理的技术细节。

闭包示例:

Counter 是独立的吗?

在这儿我们用相同的 makeCounter 函数创建了两个计数器(counters):countercounter2

它们是独立的吗?第二个 counter 会显示什么?0,12,3 还是其他?

function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ?

答案是:0,1。

函数 countercounter2 是通过 makeCounter 的不同调用创建的。

因此,它们具有独立的外部词法环境,每一个都有自己的 count

if 内的函数

看看下面这个代码。最后一行代码的执行结果是什么?

let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();

答案:error

函数 sayHi 是在 if 内声明的,所以它只存在于 if 中。外部是没有 sayHi 的。

变量可见吗?

下面这段代码的结果会是什么?

let x = 1;

function func() {
  console.log(x); // ?

  let x = 2;
}

func();

答案:error

你运行一下试试:

let x = 1;

function func() {
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  let x = 2;
}

func();

在这个例子中,我们可以观察到“不存在”的变量和“未初始化”的变量之间的特殊差异。

你可能已经在 变量作用域,闭包 中学过了,从程序执行进入代码块(或函数)的那一刻起,变量就开始进入“未初始化”状态。它一直保持未初始化状态,直至程序执行到相应的 let 语句。

换句话说,一个变量从技术的角度来讲是存在的,但是在 let 之前还不能使用。

下面的这段代码证实了这一点。

function func() {
  // 引擎从函数开始就知道局部变量 x,
  // 但是变量 x 一直处于“未初始化”(无法使用)的状态,直到结束 let(“死区”)
  // 因此答案是 error

  console.log(x); // ReferenceError: Cannot access 'x' before initialization

  let x = 2;
}

变量暂时无法使用的区域(从代码块的开始到 let)有时被称为“死区”。


垃圾收集

通常,函数调用完成后,会将词法环境和其中的所有变量从内存中删除。因为现在没有任何对它们的引用了。与 JavaScript 中的任何其他对象一样,词法环境仅在可达时才会被保留在内存中。

但是,如果有一个嵌套的函数在函数结束后仍可达,则它将具有引用词法环境的 [[Environment]] 属性。

在下面这个例子中,即使在(外部)函数执行完成后,它的词法环境仍然可达。因此,此词法环境仍然有效。

例如:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // g.[[Environment]] 存储了对相应 f() 调用的词法环境的引用

请注意,如果多次调用 f(),并且返回的函数被保存,那么所有相应的词法环境对象也会保留在内存中。下面代码中有三个这样的函数:

function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// 数组中的 3 个函数,每个都与来自对应的 f() 的词法环境相关联
let arr = [f(), f(), f()];

当词法环境对象变得不可达时,它就会死去(就像其他任何对象一样)。换句话说,它仅在至少有一个嵌套函数引用它时才存在。

在下面的代码中,嵌套函数被删除后,其封闭的词法环境(以及其中的 value)也会被从内存中删除:

function f() {
  let value = 123;

  return function() {
    alert(value);
  }
}

let g = f(); // 当 g 函数存在时,该值会被保留在内存中

g = null; // ……现在内存被清理了

实际开发中的优化

正如我们所看到的,理论上当函数可达时,它外部的所有变量也都将存在。

但在实际中,JavaScript 引擎会试图优化它。它们会分析变量的使用情况,如果从代码中可以明显看出有未使用的外部变量,那么就会将其删除。

在 V8(Chrome,Edge,Opera)中的一个重要的副作用是,此类变量在调试中将不可用。

打开 Chrome 浏览器的开发者工具,并尝试运行下面的代码。

当代码执行暂停时,在控制台中输入 alert(value)

function f() {
  let value = Math.random();

  function g() {
    debugger; // 在 Console 中:输入 alert(value); No such variable!
  }

  return g;
}

let g = f();
g();

正如你所见的 —— No such variable! 理论上,它应该是可以访问的,但引擎把它优化掉了。

这可能会导致有趣的(如果不是那么耗时的)调试问题。其中之一 —— 我们可以看到的是一个同名的外部变量,而不是预期的变量:

let value = "Surprise!";

function f() {
  let value = "the closest value";

  function g() {
    debugger; // 在 console 中:输入 alert(value); Surprise!
  }

  return g;
}

let g = f();
g();

V8 引擎的这个特性你真的应该知道。如果你要使用 Chrome/Edge/Opera 进行代码调试,迟早会遇到这样的问题。

这不是调试器的 bug,而是 V8 的一个特别的特性。也许以后会被修改。你始终可以通过运行本文中的示例来进行检查。


内存溢出

  • 一种程序出现的错误
  • 当程序运行需要的内存超过剩余的内存,就会抛出内存溢出的错误

内存泄漏

  • 占用的内存没有及时释放
  • 内存泄漏积累多了就容易导致内存溢出
  • 常见的内存泄漏:
    • 意外的全局变量
    • 没有及时清理的计时器和回调函数
    • 闭包
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值