但凡读书,或者学一门技术,都要问本身如下几个问题。javascript
我下面就试着从这几个方向来阐述闭包这个概念。html
在了解闭包以前,咱们须要了解几个概念。本文在这里只作简单介绍,如须要进一步了解,请参考文章末尾的连接。java
变量和函数的可做用范围,分为局部做用域和全局做用域。Javascript不具备块级做用域,而具备函数做用域。node
变量和函数有权访问的其余数据。git
每一个函数在执行的时候,会把它的执行环境推入一个栈中,在函数执行完毕后执行环境出栈并被销毁。保存在其中的全部函数和比变量定义随之销毁,控制权返回到以前的执行环境中。全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。github
做用域链用于保证对执行环境有权访问的变量和函数的有序访问。面试
闭包这个概念,在函数式编程里很常见,简单的说,就是使内部函数能够访问定义在外部函数中的变量。严格一点的定义是编程
在函数内声明另外一个函数,而且返回这个函数。这个返回的函数和它的执行环境总体叫作闭包。
让咱们来看一个例子:浏览器
function f1(){ var val = 10; } console.log(val); //Uncaught ReferenceError: val is not defined(…)
因为从函数外部没法访问函数内部的变量,因此报出了错误。那么如何可以访问到局部做用域的变量呢?闭包
function f1(){ var val = 10; function f2(){ console.log(val); } return f2; } var f2 = f1(); f2(); // 10
在这段代码中,f2 函数和其执行环境构成了一整个闭包。对于常规的 f1() 方法, 在其内部的变量 val 应该在 f1() 方法执行完毕之后就被垃圾回收。可是 f1() 返回了一个新的方法 f2()。因为 f2() 访问了其外部函数的变量 val,val就构成了f2函数的执行环境。val 存在于f2的做用域链中,只要f2()方法没有被销毁,其做用域链中的变量和函数就不会被销毁, val 也就会一直存在。
for (var i = 0; i < 5; i++) { setTimeout(function () { console.log(i); }, 5); }
上面这个代码块会打印五个 5
出来,而咱们预想的结果是打印 1 2 3 4 5。
之因此会这样,是由于 setTimeout 中的 i 是对外层 i 的引用。当 setTimeout 的代码被解析的时候,运行时只是记录了 i 的引用,而不是值。而当 setTimeout 被触发时,五个 setTimeout 中的 i 同时被取值,因为它们都指向了外层的同一个 i,而那个 i 的值在迭代完成时为 5,因此打印了五次 5
。
为了获得咱们预想的结果,咱们能够把 i 赋值成一个局部的变量,从而摆脱外层迭代的影响。
for (var i = 0; i < 5; i++) { (function (idx) { setTimeout(function () { console.log(idx); }, 5); })(i); }
假如咱们要实现一系列的函数:add10,add20。咱们为此构造了一个名为 adder 的构造器,以下:
var adder = function (x) { var base = x; return function (n) { return n + base; }; }; var add10 = adder(10); console.log(add10(5)); var add20 = adder(20); console.log(add20(5));
每次调用 adder 时,adder 都会返回一个函数给咱们。咱们传给 adder 的值,会保存在一个名为 base 的变量中。因为返回的函数在其中引用了 base 的值,因而 base 的引用计数被 +1。当返回函数不被垃圾回收时,则 base 也会一直存在。
因为闭包会使得函数中的变量都被保存在内存中,内存消耗很大,因此不能滥用闭包,不然会形成网页的性能问题,在IE中可能致使内存泄露。解决方法是,在退出函数以前,将不使用的局部变量所有删除。
请定义这样一个函数
function repeat (func, times, wait) { } // 这个函数能返回一个新函数,好比这样用 // var repeatedFun = repeat(alert, 10, 5000) // 调用这个 repeatedFun ("helloworld") // 会alert十次 helloworld, 每次间隔5秒
代码参见:JS bin 闭包面试题一
写一个函数stringconcat, 要求能
var result1 = stringconcat("a", "b") result1 = "a+b" var stringconcatWithPrefix = stringconcat.prefix("helloworld"); var result2 = stringconcatWithPrefix("a", "b") result2 = "helloworld+a+b"
代码参见:JS bin 闭包面试题二
参考:
学习Javascript闭包(Closure)
node-lessons/lesson11 at master · alsotang/node-lessons · GitHub
JavaScript做用域链