MyException - 我的异常网
当前位置:我的异常网» JavaScript » 小弟我理解的闭包

小弟我理解的闭包

www.MyException.Cn  网友分享于:2013-10-08  浏览:0次
我理解的闭包

网上关于闭包的文章一搜一大堆,但是我还是要来说一下我的理解。

我理解的闭包,其实就是访问了外部变量的函数

let a = 0
function b() {
  console.log(a)
}

可能和同学们平常看到的理解不太一样,但维基百科的确是这样描述的:

a closure is a record storing a function together with an environment: a mapping associating each free variable of the function (variables that are used locally, but defined in an enclosing scope)
闭包就是一个引用了外部变量的函数以及其运行环境的统称

平常我们提到的“闭包”,都是通过返回一个函数来让外部能访问函数内部的变量,但我认为这只是闭包的一种应用罢了。前面的例子是闭包,但是它已经是全局环境了,不能再被指向给上一层环境,因此函数执行完成后该闭包便被销毁了。

下面让我们以一个简单的计数器函数为例,更加深入的去理解闭包:

/*不使用闭包*/
function add () {
  let count = 0;
  return counter += 1;
}
add() //1
add() //1
add() //1

/*使用闭包*/
let add = function plus ()  {
  let count = 0
  return function closure () {
      return count += 1
  }
}()
add() // 1
add() // 2
add() // 3

不使用闭包的情况下,每次执行add()函数时,局部变量count值都会被初始化为0,并不能起到计数器的作用。

使用闭包的情况,本身是两个匿名函数,为了方便描述我给它们分别命名为plus()和closure()。plus内定义了一个变量count,然后返回了closure函数,这个函数引用了count变量。函数最后一个()让其执行后,将其结果赋值给了一个全局变量add。

下面我们看下调用add函数会发生什么:调用add函数即调用closure,首先会执行count+=1,而closure函数内部是没有定义‘count’这个变量的,于是它会循着作用域链往上查找,到plus 函数找到了count变量,然后取得count的值,计算并返回。再次执行,可以看到count值是继续递增的,说明count被保存在了内存中,但却不能直接访问。

让我们看一下闭包是怎样工作的:closure函数引用了其外部变量count,此时closure函数(以及其运行环境和外部变量)形成一个闭包,并将整个闭包返回,赋值给了一个全局变量。于是整个闭包随着add留在内存中,直到add与该闭包的连接被清除(将add指向别处或者 add = null),该闭包占用的内存才会被回收。

闭包的应用1

闭包最常见的应用,是各种js库用来封装源码。以jQuery为例,源码核心结构如下:

(function (global, factory) {
  ...
})(window, function (window) {
  var arr = []
  var document = window.document
  ...
  var jQuery = function (selector, context) {
    ...
  }
  ...
  window.jQuery = window.$ = jQuery
})

我们来解读一下这里的闭包:首先,匿名函数内定义了各种变量,然后jQuery对象(同时也是一个函数)及其属性和方法引用到了这些变量。最后用window.$ = jQuery将jQuery对象和全局对象连接起来---因此这个闭包只会在其全局对象被销毁(页面或iframe被关闭),或者连接被切断(window.$ = null)时才会销毁。这就是闭包最常见的应用---利用匿名自执行函数的作用域,将内部变量封装起来,防止被外部修改,也避免了污染全局变量环境。

看到这里也明了,并不是“return一个函数才叫闭包”,前面计数器的例子也可以改成这样:

(function closure(global) {
  let count = 0
  function plus() {
    return count += 1
  }
  global.add = plus
})(window)

闭包的应用2

平常我们可能需要在window.onresize中改变页面样式,用户输入字符时ajax远程搜索等。由于这类事件会在短时间内多次触发,不加以控制则会频繁调用处理程序,影响性能。我们可以利用闭包,来实现函数节流(throttle)和函数去抖(debounce),提高页面性能。

函数节流:预先设定一个执行周期,当调用方法的间隔大于执行周期则执行该方法,然后记录当前执行的时间并进入下一个新周期。

const throttle = function (fn, ms) {
  let timestamp = 0
  return function () {
    let current = Date.now()
    if (current - timestamp > ms) {
      fn.apply(this, arguments)
      timestamp = current
    }
  }
}

setInterval(throttle(function (arg) {
  console.log(arg)
}, 2000).bind(this, 'hello'), 50)

利用闭包将timestamp 的值保存起来,以记录函数上次的调用时间。如果小于时间间隔则不处理,如果大于间隔则执行函数并记录这一次执行的时间。我们用setInterval模拟一下连续触发的情况,可以看到虽然setInterval的间隔设置为50,但是函数的执行间隔仍然是由throttle设定的间隔控制的---2s触发一次。这个方法也适用于防止用户连续点击按钮发起重复请求的情况。

函数防抖:有一个形象的比喻是,如果用手指一直按住一个弹簧,它将不会弹起,直到你松手为止。也就是说当调用函数n毫秒后,才会执行该函数,若在这n毫秒内又调用此函数,则将重新计算时间。

var debounce = function(fn, ms){
  var timeoutID
  return function(){
    clearTimeout(timeoutID)
    timeoutID = setTimeout(() => {
      fn.apply(this, arguments)
    }, ms)
  }
}

setInterval(debounce(function (arg) {
  console.log(arg)
},300).bind(this,'world'),50)

这次我们用闭包保存了setTimeout返回的ID。每当函数执行的时候,就重新设置timeout,因此50ms间隔的interval遇上300ms间隔的debounce,函数将永不会执行,直到取消interval。这种方法适用于页面resize,文字输入远程搜索等情况,但是由于是延迟触发,不适合用于按钮点击等交互体验明显的地方。

总结

作用域的限制,让外部环境不能访问内部变量,而内部环境可以访问其作用域链上的外部变量;当一个函数访问了其外部变量,这个函数就同这些被访问的变量形成了闭包;如果将这个闭包同外层环境连接起来,这个闭包将会一直存在内存中,直到外层环境销毁;而外层环境可以利用闭包函数,访问闭包中保存的变量。

由于闭包会让函数及变量一直留在内存中,不能被GC机制回收,因此当不再使用该闭包时,应当及时将该闭包与当前环境的连接清除,以便内存被系统回收。

4楼南栀夏沫
写的很详细啊,赞一个
3楼AdamAG
说的很好
2楼秋风扫落叶2
学习了
1楼cdx71666344
感谢分享

文章评论

漫画:程序员的工作
漫画:程序员的工作
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
我的丈夫是个程序员
我的丈夫是个程序员
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
那些争议最大的编程观点
那些争议最大的编程观点
代码女神横空出世
代码女神横空出世
中美印日四国程序员比较
中美印日四国程序员比较
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
程序员应该关注的一些事儿
程序员应该关注的一些事儿
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
老程序员的下场
老程序员的下场
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
编程语言是女人
编程语言是女人
每天工作4小时的程序员
每天工作4小时的程序员
程序员和编码员之间的区别
程序员和编码员之间的区别
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
旅行,写作,编程
旅行,写作,编程
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
如何成为一名黑客
如何成为一名黑客
程序员都该阅读的书
程序员都该阅读的书
为什么程序员都是夜猫子
为什么程序员都是夜猫子
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
 程序员的样子
程序员的样子
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Java程序员必看电影
Java程序员必看电影
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
程序员必看的十大电影
程序员必看的十大电影
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
一个程序员的时间管理
一个程序员的时间管理
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
程序员的鄙视链
程序员的鄙视链
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有