JavaScript 闭包详解

Posted YuLong~W

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript 闭包详解相关的知识,希望对你有一定的参考价值。

文章目录

什么是闭包?

要理解 闭包 就要去理解 变量的作用域,在JS中存在两种变量的作用域,一种是全局变量,一种是局部变量。两种变量的区别就是:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。

闭包就是能够读取其他函数作用域中局部变量的函数。

只有函数内部的子函数才能读取局部变量,所以闭包可以理解成 定义在一个函数内部的函数。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

声明在一个函数中的函数,叫做 闭包函数。而且内部函数总是可以访问其所在的外部函数中声明的参数和变量,即使在其外部函数执行完毕之后。


1、闭包产生条件:

【1】访问所在作用域

【2】函数嵌套

【3】在所在作用域外被调用

2、闭包的特点:

【1】让外部访问函数内部变量成为可能

【2】局部变量会常驻在内存中

【3】可以避免使用全局变量,防止全局变量污染

【4】会造成内存泄漏(有一块内存空间被长期占用,而不被释放)

3、闭包的坑点:

【1】引用的变量可能已经发生变化

【2】this指向问题

【3】内存泄漏问题


闭包就是可以创建一个独立的环境,每个闭包里面的环境都是独立的,并且互不干扰。闭包会发生内存泄漏,每次外部函数执行的时候,外部函数的引用地址不同,都会重新创建一个新的地址。

但凡是当前活动对象中有被内部子集引用的数据,那么这个数据不删除,保留一个指针给内部活动对象。大概意思就是说当外部函数运行结束甚至销毁时,局部的变量key=value,尽管key被垃圾回收机制给回收了,但是value仍不会被回收,会变成一个自由变量留下引用的指针。

例1:

//fun1、fun2相当于有两个外部函数,因此是两个独立环境,两个地址

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

例2:

function fn() {
    var a = 3;
    return function () {
         return ++a;
    }
}
console.log(fn()());//4
console.log(fn()());//4
var newFn = fn();
console.log(newFn());//4
console.log(newFn());//5

同理还是一样的,相当于fn()()的直接调用都是在开辟一个新的地址,但是把它赋给一个变量的时候,地址就不会再改变了,这个时候输出是在递增的。

例3:

function foo() {
    var arr = [];
    for (var i = 0; i < 4; i++) {
        arr[i] = function () {
            return i;
        }
    }
    return arr;
}
var bar = foo();
consolelog(bar);  //数组 存储四个函数
console.log(bar[0]); //打印 闭包函数结构
for(var i=0;i<bar.length;i++){
    console.log(bar[i]()) // 4 4 4 4
}

在循环的过程中,并没有把函数的返回值赋值给数组元素,而仅仅是把函数赋值给了数组元素、这就使得在调用匿名函数时,通过作用域找到的执行环境中存储的变量的值已经不是循环时的瞬时值索引值,而是循环执行完毕之后的索引值。

解决方案:

1、使用立即执行函数

function foo() {
    var arr = [];
    for (var i = 0; i < 4; i++) {
        arr[i] = (function(i){
            return function () {
                return i;
            }
        })(i);
    }
    return arr;
}
var bar = foo();
console.log(bar);
console.log(bar[0]);
for(var i=0;i<bar.length;i++){
    console.log(bar[i]()) // 0 1 2 3
}

立即执行函数内部形成了一个独立的作用域,可以封装一些外部无法读取的私有变量,这个作用域里面的变量,外面访问不到,这样就可以避免变量污染

2、使用块级作用域:将var改成let

function foo() {
    var arr = [];
    for (let i = 0; i < 4; i++) {
        arr[i] = function () {
            return i;
        }
    }
    return arr;
}
var bar = foo();
consolelog(bar);  //数组 存储四个函数
console.log(bar[0]); //打印 闭包函数结构
for(var i=0;i<bar.length;i++){
    console.log(bar[i]()) // 0 1 2 3
}


由于 块作用域 可以将索引值i重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值,相当于为每一次索引值都创建一个执行环境(执行环境中保存了当前循环时的值)

使用闭包注意点:

(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在浏览器中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

(2)闭包会在父函数外部改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

以上是关于JavaScript 闭包详解的主要内容,如果未能解决你的问题,请参考以下文章

Javascript闭包详解

Js_闭包详解

JavaScript闭包函数详解

JavaScript 闭包详解

JavaScript闭包和回调详解

javascript中的闭包closure详解