1、垃圾回收
(1)全局变量不会被回收
(2)局部变量会被回收,函数执行结束,函数内部东西会被销毁
(3)某作用域中的某个变量还在被另一个作用域引用就不会被回收
var a=[];
for(var i = 0;i<10;i++){
var q = i;
a[i]=function(){console.log(q)}
}
a[0]();//9
首先,for循环不是一个函数作用域,因此变量a,i,q
都是同级的。其次,只是将函数赋值给a[i]
,函数并没有立即执行。for循环执行完毕后,i=10,q=9
,因此,执行a[0]()
,输出q
,q=9
。
解决方法:
(1)使用let声明变量
(2)使用闭包
(1)使用let声明变量
var a=[];
for(let i = 0;i<10;i++){
let q = i;
a[i]=function(){console.log(q)}
}
a[6]();//6
可以将for循环理解为两个块级作用域:
var a=[];
let i=0;
if(i<10){
let q = 0;
a[0]=function(){console.log(q)}
};
let i=1;
if(i<10){
let q = 1;
a[1]=function(){console.log(q)}
};
...
let i=9;
if(i<10){
let q = 9;
a[9]=function(){console.log(q)}
};
let i=10;
a[6]();
体会以下代码:
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
输出三次abc
可以看出for循环的变量i和循环体中的i在不同作用域中。
(2)使用闭包
闭包特性:
①函数嵌套函数
②内部函数可以访问外部函数的变量
③被访问的参数和变量不会被js垃圾回收机制回收
var a=[];
for (var i = 0; i < 10; i++) {
(function (n) {
let q=n;
a[n]=function(){console.log(q)}
})(i);
}
a[6]();
注:例题1:
function fc(){
let n=1;
function sum(){//函数声明时没有开辟内存空间
console.log(++n);
}
sum();//函数执行时,开辟内存空间压栈
}
fc();//2,开辟内存空间,压栈;运行完,出栈,内存被回收
fc();//2,开辟新的内存空间,压栈,与上面开辟的内存空间是独立的,互不干涉,因此打印的都是2.
例题2:
function fc(){
let n=1;
return function sum(){//返回函数的内存地址
console.log(++n);
}
}
let a=fc();//引用函数的内存地址,因此函数fc执行完不会被销毁,会继续使用。
a();//2
a();//3
let b=fc();//重新创建新的内存空间
b();//2
b();//3
区别:
function fc(){
let n=1;
return n;//此处只是返回n的值,
}
let a=fc();//变量a只是得到n的值,因此函数fc执行后其内容会被销毁。
例题3:
function fc(){
let n=1;
return function sum(){
let m=1;
function show(){
console.log(++m);
}
show();
}
}
let a=fc();
a();//2
a();//2
这个例题不是很理解啊!!!!
函数sum被引用,在外部多次执行sum时,
show函数会被多次创建新的内存空间,而不是使用原内存空间进行累加,
为啥呀???
区别以下代码:
function fc(){
let n=1;
return function sum(){//函数声明时没有开辟内存空间
let m=1;
return function show(){
console.log(++m);
}
}
//sum();//函数执行时,开辟内存空间压栈
}
let a=fc()();//此处是将show函数返回到外部
a();//2
a();//3
例题4:
var name='The window';
var obj={
name:'MY OBJ',
getNameFunc:function(){
return this.name;
}
};
alert(obj.getNameFunc());//MY OBJ
区别以下代码:
var name='The window';
var obj={
name:'MY OBJ',
getNameFunc:function(){
return function(){
return this.name;
}
}
};
alert(obj.getNameFunc()());//The window
//obj.getNameFunc()()运行匿名函数function(){ return this.name;},this指向window。
匿名子函数没有引用父函数getNameFunc的变量,所以没有形成闭包。
区别以下代码:
var name2 = "the window";
var obj2 = {
name2:"my name is a obj",
getNameFunc2 :function fn1(){
var that = this;
return function(){
return that.name2;
};
}
}
alert(obj2.getNameFunc2()()); //输出 my name is a obj
有函数嵌套,匿名子函数引用了父函数getNameFunc的变量,产生了闭包环境。
父函数的this指向obj2,that指向obj2。
面试题:
理解闭包:
一个父函数嵌套着另一个子函数。
正常来说,对于嵌套函数,内部的子函数不能被外部作用域引用,但是如果把这个子函数作为一个返回值传给父函数,那么作用域就能执行这个子函数内的结果了。
闭包的作用
相同函数可以用多个相互独立的对象引用,避免代码冗余、相互污染。
产生闭包:
产生闭包必须要有嵌套函数,以及子函数引用父函数的变量或属性。
普通嵌套函数:
function fun(){
var a = 100;
function fn(){
console.log(++a);
}
fn();
}
fun(); // 101
fun(); // 101
fun(); // 101
产生闭包:
function fun(){
var a = 20;
return function fn(){ // 直接将执行结果返回给 fun,这个 fn就是闭包了。
console.log(++a);
}
}
var fns = fun(); // 接收函数fn
fns() // 输出21;
fns() // 输出22;
fns() // 输出23;
闭包的缺点:
函数执行完后,函数内的局部变量没有释放,占用内存时间变长,容易造成内存泄露。(当程序运行需要的内存超出了剩余内存时,就会产生内存泄露。由于设计错误,导致在释放该内存之前就失去了对该内存的控制,从而造成了内存的浪费。内存泄露过多,会影响程序性能,甚至导致程序崩溃。)
解决:内部函数赋值为null,让浏览器回收闭包。
闭包的应用:
1、具有特定功能的JS模块
2、将所有的数据和方法封装在一个函数内部
1、
function fun(){ // JS 模块,并且有特定的功能,转换大小写
var msg = "my name is a juzheng"; // 私有属性
// 操作数据的函数
function f1(){
console.log("输出小写" + msg.toLowercase()); // 调用了上层的局部变量属性
}
function f2(){
console.log("输出大写"+ msg.toUpperCase());
}
// 向全局暴露两个对象,需要一个对象容器来保存
return {
one:f1,
two:f2
}
}
// 接收返回值
var abc = fun(); // 通过暴露对象,接收数据
abc.one(); // 接收容器的对象
2、
(function fun(window){ // JS模块,匿名函数自调用, 并且具有转换大小写功能
var msg = "my name is a juzheng";
function f1(){
console.log("输出小写" + msg.toLowerCase()); //调用上层变量属性
}
function f2(){
console.log("输出大写" + msg.toUpperCase());
}
window.myModel = { //将容器对象,转为 window全局对象,将容器对象暴露出来,
one:f1,
two:f2,
}
})(window) // 推荐函数自调用时,形参和实参写上 window, 这样可以实现压缩代码
// 调用暴露对象
myModel.one();
myModel.two();
// 不需要用一个变量来接收,因为不是用return方法,而是在闭包中用window属性将暴露对象给定义好了,
所以在全局作用域下,只需要调用即可。