JavaScript 笔记三:作用域、闭包、内存的管理与泄漏

都是本人理解,笔记大致概念,不详细也并非完全正确,所以仅供参考。


作用域

ES5 中,除全局作用域外,每一个函数块存在属于自己的作用域,称之为函数作用域,在其内部声明的变量无法被外部使用到。

但可以使用上级作用域的变量,其概念类似原型链,一层一层寻找,找到了就使用。

原型链参考笔记二:JavaScript 原型链的深度剖析

而全局下的变量就属于全局作用域。

var a = 123;
window.a === a // true

ES6 中新增了一个块级作用域,主要是给 let 和 const 使用,简单来说就是,任何被函数 {} 包裹的区域,如:

if(1){
  let a = 1;
  const b = 2;
}
console.log(a); // a is not defined
console.log(b); // b is not defined

闭包

简单来说,就是一个作用域里可以调用外部作用域的变量和函数,则称之为闭包。

PS:如果一个函数访问了它的外部变量,那么它就是一个闭包。

function a(x) {
var age = x;
return function (name) {
    console.log(name, age);
    age++;
  }
}
var age = 10;
var b = a(age);
b('jack'); // jack 10
b('jack'); // jack 11
b('jack'); // jack 12

所以可以说,a 实现了闭包。


内存管理(垃圾收集机制)

内存管理的机制就是周期性执行这一操作:找出不再使用的变量,释放其空间。

主要有以下两种方式。

  • 标记清除
  • 引用计数

标记清除

标记清除是JavaScript最常用的内存管理机制,即当变量进入环境时,则标记这个变量为进入,而当变量离开环境时,则将其标记为离开。

function add(n){
  var sum += n;    // 进入
  return sum;      // 离开
}
add(1); // 1

变量的定义和赋空值,也会出发标记清除:

var user = {
  name: 'jack',
  age: 20
};  // 在作用域定义变量,标记变量为进入

// some code ...

user = null;    //最后定义为null,标记变量为离开,释放内存

引用计数

引用计数是不太常见的内存管理机制,顾名思义,引用计数会跟踪记录每个值被引用的次数,若是为0则清除。

var user = {
  name: 'jack',
  age: 20
};  // 引用次数 + 1

// some code ...

user = null;    // 最后定义为null,引用计数 -1 ,为0清除

但引用计数会因为变量相互引用,造成内存计数永不为0,故而虽然已经执行完了代码,但是还是不会清除变量。

function a(){
  var b = {};
  var c = {};
  b.test = c;
  c.test = b;
}
a(); // 虽然 a 已经执行完,但是还是不会清楚 b 和 c 。

总结

虽然大多浏览器上JavaScript的内存管理机制是标记清除,但是在 IE 浏览器(又是它)中,某些对象的内存管理机制是引用计数,如IE的 DOM和BOM,(统称为COM对象),所以在写代码时要避免相互引用。


内存泄漏

首先,内存泄漏并不是不合理的,如闭包就会造成内存泄漏,但是大家还是在使用闭包的方式写函数,所以存在即合理,需要做的,是在合理使用的情况下把不合理的去掉。

常见的造成内存泄漏的情况:

  • 定义全局对象
  • 闭包
  • dom清空或删除时,事件未清除
  • 相互引用

由上可知,基本解决方案就是:

  • 不要轻易定义全局变量
  • 如果有必要,尽量减少闭包函数。
  • 删除dom时,清除dom以及其子节点绑定的函数
  • 不要相互引用,即使要相互引用,最后请加一个 a = null 切断联系

PS:不是很全面,要深度研究请网上冲浪,这里仅做一些参考。

你可能感兴趣的:(JavaScript 笔记三:作用域、闭包、内存的管理与泄漏)