闭包
1. 概念
声明一个函数中的函数
闭包:
内部函数总是可以访问到外部函数中生命的参数和变量,即使在其外部函数被返回(寿命终结)
外部函数a和内部函数b,
function a() {
var val_a = "我是a函数里的变量";
function b() {
console.log(val_a);
}
//a函数将b返回
return b;
}
//全局变量fn,a执行返回了b给fn
var fn = a();
//调用fn,能够在全局作用域访问a函数里的变量
fn();
这里fn能够访问到a的变量,因为b中引用着a的活动对象,所以即使a函数执行完了,a的活动对象还是不会被销毁。这就说明过度使用闭包会导致内存泄漏。
2. 特点
(1) 让外部访问函数内部变量;
(2) 局部变量会常驻在内存中;
(3)可以避免使用全局变量,避免全局污染;
(4)会造成内存泄漏。(有一块内存空间被长期占用,而不释放)
每次外部函数执行的时候,外部函数的引用地址不同,都会创建一个新地址,但是当前活动对象中又被内部自己引用的数据,那么这个时候,这个数据不删除,保留一根指针给内存活动对象。大概意思就是当外部函数运行结束甚至销毁时,局部变量key=value,尽管key被垃圾回收机制给回收了,但是value仍不会被回收,会变成一个自由变量下引用的指针
内存溢出(out of memory) | 内存泄漏(memory leak) |
---|---|
要求分配的内存超出了系统给你的,系统不能满足需求 | 向系统申请分配内存进行使用(new),可是使用完之后却不归还,结果你申请到的那块地址自己也不不能访问(/地址弄丢了),系统不能再次将它分配给需要的程序。 |
结论:闭包找到的是同一地址中父级函数中对应变量最终的值
function addCount(){
var count = 0;
return function(){
count += 1;
console.log(count);
}
}
var fun1 = addCount();
var fun2 = addCount();
fun1();//1
fun1();//2
fun1();//3
fun2();//1
fun2();//2
function fn(){
var a = 3;
return function(){
return ++a;
}
}
console.log(fn()());//4
console.log(fn()());//4
console.log(fn()());//4
var newFn = fn();
console.log(newFn());//4
console.log(newFn());//5
console.log(newFn());//6
//闭包找到的是同一地址中父级函数中对应变量最终的值。
(function() {
var m = 0;
function getM(){
return m;
}
function seta(val){
m = val;
}
window.g = getM;
window.f = seta;
})();
//(function(){ /* code */ })()是立即执行函数,
#立即执行做了两件事:
//1、m的初始值为0,
//2、全局变量g和f分别指向两个函数
f(100);
//因为已经调用过全局变量f,所以,m的值已经成为了100,存储在内存空间中
//内存 存储m=100;但是此时的变量变成了一个 自由变量下引用的 指针
console.log(g());//100
var lis = document.getElementsByTagName("li");
for(var i=0;i<lis.length;i++){
//立即执行
(function(i){
lis[i].onclick = function(){
console.log(i);
};
})(i);
}
标题隐式声明的全局变量
//
var lis = document.getElementsByTagName("li");
for (var i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
console.log(i);
};
}
//
var a = 0;
function fn() {
var a = 1;
}
fn();
console.log(a); //0
//
var a = 0;
function fn() {
var a = b = 1;
}
fn();
console.log(a, b); //0 1
// a 是全局变量。
// b 是隐式声明的全局变量。
var a = 0;
function fn() {
var b = 1;
}
fn();
console.log(a, b); //0 b is not defined
function fnnn() {
var arr = [];
for (var i = 0; i < 5; i++) {
arr[i] = function() {
return i;
}
}
console.log(i); //5
return arr;
}
var list = fnnn();
// console.log(list); //[ƒ, ƒ, ƒ, ƒ, ƒ]
//函数的返回值 :外部变量list可以访问内部变量,arr中的每一项都指向了函数,第一次调用之后,局部变 量i已经加到了5
for (var i = 0, len = list.length; i < len; i++) {
console.log(list[i]()); //5 5 5 5 5
}
//再继续循环调用list的每一项函数,得到的i的值都是5
//闭包知识点:即使外部函数返回,但是内部函数的i=5保存在了内存中没有销毁(自由指针)
//没有销毁的原因是 内部函数还在使用外部函数的活动对象
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
memory leak会最终会导致out of memory!
内存溢出的原因以及解决方法
1.引起内存溢出的原因有很多种
1.1 内存加载数据量庞大,如一次从数据库取出过多数据;
1.2 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;(Java Virtual Machine)JAVA虚拟机
1.3代码中存在死循环或循环产生过多重复的对象实体;
1.4使用的第三方软件中的BUG;
1.5启动参数内存值设定的过小
2.内存溢出的解决方案:
第一步:
修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)
第二步
:检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步:
对代码进行走查和分析,找出可能发生内存溢出的位置。
3.重点排查以下几点:
- 检查代码中是否有死循环或递归调用。
- 检查是否有大循环重复产生新对象实体。
- 检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
- 检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
使用内存查看工具动态查看内存使用情况
回调函数
//定义主函数,回调函数作为参数
function A(callback) {
callback();
console.log('我是主函数');
}
//定义回调函数
function B(){
setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作
}
//调用主函数,将函数B传进去
A(B);
//输出结果
我是主函数
我是回调函数
// 上面的代码中,我们先定义了主函数和回调函数,然后再去调用主函数,将回调函数传进去。
//定义主函数的时候,我们让代码先去执行callback()回调函数,但输出结果却是后输出回调函数的内容。
//这就说明了主函数不用等待回调函数执行完,可以接着执行自己的代码。所以一般回调函数都用在耗时操作上面。
//比如ajax请求、处理文件等。