都是本人理解,笔记大致概念,不详细也并非完全正确,所以仅供参考。
作用域
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:不是很全面,要深度研究请网上冲浪,这里仅做一些参考。