关于javascript中的闭包的理解

168 阅读4分钟

javascript是基于词法作用域的语言

名词释义

作用域:程序源代码中定义变量的区域。

作用域分为 静态作用域 和 动态作用域。

**静态作用域:**函数的作用域在函数“定义”的时候就决定了,又叫 词法作用域

动态作用域:函数的作用域是在函数“调用“的时候才决定的

举个例子🌰

function foo() {
    var a = 2;
    return a;
}

function bar() {
    var a =3;
    retun foo(); // 此处的foo()就是闭包
}

// var a = 2;
bar();

上面的代码在Chrome控制台返回的是什么? 是 _2,根据_词法作用域_的定义,函数的作用域在定义的时候就决定了,调用顺序是 bar() => foo() => a => 2, 但是在_动态作用域_中返回的会是_3。
_相同的代码,解释器对代码的解释不一样,返回的结果就会有差异,_此处只是为了展示差异,不用纠结。

闭包

函数对象可以通过作用域链互相关联起来,函数体内部的变量都可以保存在函数的作用域内,这种特性在计算机科学中称为闭包(closure)

作用域链:一个函数内部的作用域对于函数外部作用域的引用的集合,调用关系采用链表的形式进行记录,称为****作用域链。

下面说说闭包的使用场景

1. 代码逻辑的封装/模块化

看两个例子
🌰 简单的累加

var i = (function(){
    var counter = 0;
    return function() { return counter ++;} // 这个匿名function就是闭包
})();

i(); // 0
i(); // 1
i(); // 2

🌰🌰 实现累加和重置

function counter() {
    var n = 0;
    return {
        count: function() { return n ++; },
        reset: function() { n = 0; }
    };
}

var c = counter(), d = counter();
c.count(); // 0
d.count(); // 0
c.reset(); // 思考🤔这步会影响 d 吗?
c.count(); // 0
d.count(); // 1 ,并不会

通过上面两个例子分别将逻辑封装在方法中,然后赋值给另外一个对象,运行时,不同的对象运行相同的属性方法,各自的运行都是独立的,累加的数值都单独存储在各自对象方法的内部参数中,不会互相影响。这在我们开发的过程中,都是常用的方式。

2.共享私有状态/数据缓存

看两个例子
🌰 不允许值变小

function counter(n) {
    return {
        get count() { return n ++; },
        set count(m) {
            if(m > n) n = m;
            else throw Error("出错");
        }
    };
}

var c = counter(1000);
c.count; // 1000
c.count; // 1001
c.count = 2000;
c.count; // 2000
c.count = 2000; // 思考一下这一步会发生什么?为什么?

🌰🌰实现私有属性存储器get set方法

function addPrivateProperty(o, name, predicate){
    var value;
    o["get"+name] = function(){ return value}; // get<Name>, 直接返回值
    o["set"+name] = function(v){ // set<Name> , 根据 predicate,判断是否符合条件
        if(predicate && !predicate(v))
            throw Error("set"+ name +": invalid value " + v)
        else
           value = v;
    }
}

var o = {};
addPrivateProperty(o,"Name",function(x){ return typeof x == "string"});
o.setName("Leon");
o.getName();  // Leon
o.setName(0); // Error: setName: invalid value 0

上面的两个例子中,都是闭包共享了私有变量或者变量,进行数据缓存,然后进行逻辑的实现。
仔细思考一下,这是一种很重要的技术,是JS的基础方法。这里需要注意,有时候不希望被共享的变量,也可能被错误的传递
看下面的例子

function constfuncs() {
    var funcs = [];
    for(var i= 0; i< 10; i ++) {
        funcs[i] = function(){return i;};
    }   
    return funcs;
}

var funcs = constfuncs();
funcs[5](); // 返回什么 ? 10

//--------------//

function constfuncs(v) {
    return function() { return v };
}

var funcs = [];
for(var i= 0; i< 10; i ++) {
    funcs[i] = constfuncs(i);
}   
funcs[5](); // 返回什么 ? 5

很明显,第二个方法,才是我们想要的结果,返回值与对应的下标相等。第一个方法所有个返回值都是10,这些闭包都在同一个函数中定义,因此它们共享了变量 i,当constfuncs()返回时,变量i是10,它们共享这一个值。

再思考一下,如果滥用闭包的数据缓存,导致缓存的数据过大,就可能会影响web界面的加载,查阅资料发现,在IE中还可能导致内存泄露。

总结一下

闭包:简单理解就是嵌套在函数中的方法。
优点:1. 有利于逻辑的封装,可以进行模块化  2. 私有变量的共享。
缺点:滥用的话,会导致缓存过大,影响网页加载性能,甚至可能导致内存泄漏。

以上就是最近学习闭包的理解,有更好的理解可以在评论区交流,觉得有用就点个赞吧!!