Table of Contents generated with DocToc (opens new window)

# 词法作用域

全局变量会自动成为全局对象(比如浏览器中的window对象)的属性,因此可以间接地通过全局对象属性的引用来对其进行访问

var a = 10;
a  // 10
window.a //10
1
2
3

无论函数在哪里被调用,也无论它如何呗调用,它的词法作用域都只由函数被声明时所处的位置决定.

# 关于 const 和 let 声明的变量不在 window 上

相关链接 (opens new window)

在ES5中,顶层对象的属性和全局变量是等价的,var 命令和 function 命令声明的全局变量,自然也是顶层对象。

var a = 12;
function f(){};

console.log(window.a); // 12
console.log(window.f); // f(){}
1
2
3
4
5

但ES6规定,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性,但 let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。 class、let定义的即便是全局对象,但不是顶层对象的属性,在window自然获取不到,只能去定义变量的块级作用域里获取

# 闭包

阮一峰-学习JavaScript闭包 (opens new window)

什么是闭包?

函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure)。也就是说,闭包可以让你从内部函数访问外部函数作用域。在 JavaScript 中,每当函数被创建,就会在函数生成时生成闭包。

简单来说,闭包是由函数和其他与其相关的引用环境组成的包裹体,使得函数能够在其定义的词法作用域外访问变量。

闭包的特性:

  • 函数嵌套:在一个函数内部定义了另一个函数
  • 内部函数引用外部函数的变量:内部函数可以访问外部函数的变量,即使外部函数已经执行完毕。
  • 外部函数返回内部函数:通常外部函数会返回内部函数,使得外部作用域的变量仍然可以被内部函数访问。
function getDate() {
  let date = 129;
  function getToday() {
    console.log("today is" + date);
  }
  return getToday();
}

getDate();
// getToday就是闭包
1
2
3
4
5
6
7
8
9
10

Chrome debug 工具中可以查看闭包

image-20230208163729388

使用闭包的注意点

image-20200609092422638

# 闭包为什么会造成内存泄漏

先看一段代码

function printNumber() {
  let num = 0;
  debugger;
  return function () {
    for (let i = 0; i < 10; i++) {
      num++;
    }
    debugger;
    console.log(num);
  };
}

var printer = printNumber();
debugger;
printer();
debugger;
printer();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

执行结果是10、20

这里产生闭包的原因是printNumber返回了一个函数printer,printer内引用了num,而在两次printer之间闭包(即printer)还存在,num被保存了;

最后当整个程序执行完毕后,gc回收机制执行,闭包被回收,内存被释放。

# 关于JavaScript的GC回收机制

JavaScript 引擎基础 GC 方案是(simple GC):mark and sweep(标记清除),即:

  • 遍历所有可访问的对象。
  • 回收已不可访问的对象。

浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

详见这篇文章 (opens new window)

# 闭包的用途

1.数据缓存

看以下一个题目

var data = [];

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

data[0]();
data[1]();
data[2]();
1
2
3
4
5
6
7
8
9
10
11

答案是都是3

很明显,闭包缓存了状态

# Vue的闭包

当你在浏览器中调试 Vue 组件时,可能会看到类似下面的闭包特征:

// Vue 组件的例子
function VueComponent() {
  let message = 'Hello, Vue!';
  
  function updateMessage(newMessage) {
    message = newMessage;
  }

  return {
    message,
    updateMessage
  };
}

const instance = VueComponent();
console.log(instance.message); // 'Hello, Vue!'
instance.updateMessage('New message');
console.log(instance.message); // 'New message'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18