首页 > 请问JS闭包的使用场景是什么?

请问JS闭包的使用场景是什么?

新人请问一下,闭包应该在什么场景使用?


这篇博客不错:http://www.ruanyifeng.com/blog/2009/08/l...
闭包的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。


场景:优雅的实现javascript面向对象

<script>
    function user(username){
        this.hello = function(){
            alert("hello "+username);
        }
    }
    var user = new user("charles");
    user.hello();
</script>

随便写写,为自己的博客做准备,大家多提意见。

闭包这个东西起源与函数式编程。
lisp,haskell 等有一个特点,就是把所有的语法作尽量少的规定。就是把看似不太相关的东西解释成同一个东西。一个不太恰当的例子是,数组和字符串本质上都是数组,字符串是字符组成的数组。所以在js里面数组和字符串处理的函数十分的类似。近年发明的语言都是把字符串看作数组的,当年分开是为了把字符串当作特殊的数组便于编译器优化提高效率,但是现在不需要了。

这里面有一个重要的思想就是,函数其实和一般的数据类型,比如 boolean,int等没有本质上的不同。这样一个函数就可以返回一个函数,也可以用函数作为参数。到此,这个和C语言里面函数的参数/返回值是一个函数指针还看不出区别。但是为了进一步统一(或者抽象)函数,又加上一条,函数只接受一个参数。多个参数的函数,比如两个参数的函数,其实也是接受一个参数的函数,只是它的返回值也是一个函数。比如加法(js代码) add(2)(5) ,其实 add之接收了一个参数,然后返回了一个函数2+,再以参数 5 调用这个2+ 函数,那个这个2+函数就返回一个7

那么,定义一个两个参数的函数就是:(不合法的js代码)

function add(a)(b)
{
 return a+b;
}

把上面那段代码修改为合法的js代码就是

function add(a) {
   return function (b) {
      return a+b;
   }
}

这样当我们写 add2 = add(2),形参a(局部变量)赋值为2。根据上面的语法就必须在 add2(5)的时候能够获取这个局部变量a的值,这个现象叫做闭包。

假设我们再定义一个两个参数的函数 2*a+b

function doubleAdd(a) {
   var c = 2*a;
   return function (b) {
      return c+b;
   }
}

同样 add22 = doubleAdd(2) 然后调用add22(5)的时候必须能够读取局部变量c的值。
现在我们看看闭包到底是什么?简单来说闭包里面保存函数执行了一半时候的状态。比如 doubleAdd 其实是计算 2a+b 的,但是如果我们使用doubledAdd(2) 这样函数只会先计算 2a 保存到c里面,然后下次给出参数 b 的时候再计算最终的结果。 这样做的其中一个优势就是,比如要计算 doubleAdd(2)(5),doubleAdd(2)(6) ,如果我们使用add22 = doubleAdd(2); add22(5);add22(6) 这样就不用重复计算 2*a 了。
因此从外部看来(这句仅仅是针对js等语言的),所谓的闭包就是执行了一半的函数,闭包里面保存了这个执行到的状态,然后可以根据后面调用时候的给出的参数从这个状态执行不同的路径。所以无论返回了一个内部的函数,还是将内部函数绑定为某个DOM的事件处理函数,都是一个执行了一半的函数,等函数调用或是触发事件之后再执行另一半。在js里面的使用场景么,就是这个执行到的状态与其他的代码是隔离的,也就是说实现了一种类似私有变量的东西。
改写一下上面的函数:

function doubleAdd(a) {
   var c = 2*a;
   function inner(b)
   {
      return c+b
   }
   return inner;
}

这样写就可以看出所谓的 “内部的函数可以引用外部的函数的变量”, inner函数可以使用外部函数的局部变量c

因此我们从函数的内部以及变量作用域的来看一下如何读懂有闭包的内部代码,如下代码。

function func1()
{ 
    var a;
    function func2(){
        var b;
        console.log(a);
    }
    return func2;
}
function func3(){ var c;}

func3();
func3();
var f21 = func1();
f21();
var f22 = func1();
f22();

函数在调用的时候都会生成一级新的作用域。函数声明和变量创建都是在这个作用域中新定义的。这个新的作用域的上一级作用域是就是看这个函数是在那个作用域里定义的。
比如上面的代码,初始情况下,代码只定义了函数func1 func3,他们是在全局定义的。当执行函数func3 的时候,会生成一个新的作用域,由于func3是在全局定义的,所以这个新的作用域里面定义的的上一级作用域就是全局作用域,在这个作用域中定义了一个变量c。当再次执行函数func3 的时候,又会生成一个新的作用域,他的上一级还是全局作用域,并且又会在新的作用域创建变量c(和上一个c只是重名而已)。同样,第一次调用func1执行的时候会生成一个作用域(假设叫做func1-1),在这个作用域上定义了一个函数和变量a。当执行 f21的时候也会生成一个新的作用域,由于f21 是在func1-1中定义的,所以其上一级作用域就是func1-1。当第二次执行func1的时候,又会生成一个新的作用域(func1-2),其上有定义了一个func2a(与第一次生成的无关,只是重名,所以说闭包浪费内存)。然后执行,f22的之后形成一个新的作用域,其上一级是func1-2

上面的逻辑仅仅在代码上来看就是:作用域的包含关系,主要看函数定义所在的位置,而不是函数调用的位置,所以如果查找一个函数中的变量查找顺序主要是看{}(特指函数定义中的{},ES5.1里不存在块作用域)。首先看,本{} 中有没有这个变量定义,没有的话就去找上一级的{}中有没有。。。。一直找到全部代码(全局)中有没有定义。仅仅以这个逻辑就可以解释几乎所有实际的闭包代码。复杂一点儿的可能就需要上面那一段的逻辑了。

【热门文章】
【热门文章】