一、在说闭包之前,首先需要了解两个东西
执行上下文(EC):简单来说,抽象点可以看做一个对象{}的格式(调试里的scope作用域);里面存储着函数里面定义的所有变量。
js中只有函数可以创建上下文(执行环境)。
垃圾回收机制(GC):js具有垃圾回收机制;就是说执行环境会负责管理代码执行过程中使用的内存。原理就是找出不再使用的变量,然后释放其占用的内存。垃圾回收器会按照固定时间间隔周期性的执行这一操作。或者内存占用过大的时候,去遍历所有的变量;如果没有其他变量引用它,就释放内存。
js垃圾回收机制主要有两种策略:1、标记清除;2、引用计数
标记清除:就是当变量进入环境的时候,就把其标记为’进入环境’,永远不会清除,只有当变量离开环境时,才会把其内存释放。
引用计数:就是跟踪每个值引用的次数,当声明一个变量并赋值一个引用类型赋值引用次数加1;如果同一个值又被赋值给另一个变量,该值引用次数再加1;相反如果包含这个值的引用比变量又取得另外的值,则该值引用次数减1。当引用次数为0。说明无法引用该值了;就可以释放内存了。
js最常用的是标记清除的策略;最不常用的是引用计数,因为引用 计数在Netscape Navigator 3.0最早使用时,出现了一个严重的问题;循环引用。循环引用指:对象A包含一个指向对象B的指针;而对象B也包含一个指向对象A的引用。此时它们的引用次数都是2;所有不会释放内存。
下面看看一个例子:
<script>
/**
* a、b为参数变量
* p、q为var定义的
* m:为函数声明
*
* 这些变量都会变量上浮。
* 除了函数声明,其他类型变量只是定义上浮了而已;所以函数声明在声明的前面,就可以调用;
* 而其他类型变量在声明的前面不可以调用,会出现undefined
*
* @param a
* @param b
*/
function fn(a,b){
m();
console.log(q);
var p=1;
var q = function () {
console.log('q')
};
function m(){
var x = 1;
function y(){
console.log('y')
}
console.log("m");
}
m();
}
fn();
</script>
/**
* a、b为参数变量
* p、q为var定义的
* m:为函数声明
*
* 这些变量都会变量上浮。
* 除了函数声明,其他类型变量只是定义上浮了而已;所以函数声明在声明的前面,就可以调用;
* 而其他类型变量在声明的前面不可以调用,会出现undefined
*
* @param a
* @param b
*/
function fn(a,b){
m();
console.log(q);
var p=1;
var q = function () {
console.log('q')
};
function m(){
var x = 1;
function y(){
console.log('y')
}
console.log("m");
}
m();
}
fn();
</script>
二、闭包
函数创建上下文的时候会调用堆栈,栈的特点是:先进后出;每创建一个上下文就会往栈压入一个上下文;函数执行完就会弹出此上下文。
闭包比较特殊;虽然也已经弹出了栈;但是它还被其它的引用着;所以垃圾回收集没有对它进行回收,还存在着。由此就形成了闭包。
自由变量:函数里面引用外面定义的变量,叫自由变量。
闭包的表层理解:当一个函数执行时,返回一个函数,这个函数还可以引用这个执行函数内部的变量。这种情形叫闭包。
闭包的深层理解:当函数执行完时,它的上下文(EC)被弹出了堆栈,但是它还有被别的变量引用着,所以垃圾回收集(GC)没有对它进行回收,还存在着。由此就形成了闭包。
例子:
function fn(a,b){
var m= 1;
var n = function(){
console.log('n');
};
var p = function(){
console.log(m);
n();
};
return p;
}
var ret = fn();
ret();
var m= 1;
var n = function(){
console.log('n');
};
var p = function(){
console.log(m);
n();
};
return p;
}
var ret = fn();
ret();