JavaScript--深入浅出之闭包

本文很多理论基础以及概念来自于《你不知道的JavaScript》,感谢KYLE SIMPSON先生。首先要说明,闭包是和作用域息息相关的。

function foo(){
  var a = 2;
  function bar(){console.log(a)}
  return bar;
}
var baz = foo();
baz();  //2

这段代码,函数bar()被当做一个值类型进行传递,这个时候就产生了闭包。这里呢,bar()声明的位置来看,他拥有访问foo()内部作用域的闭包。也就是bar()对该作用域的引用。

强调一下概念,(访问自身作用域【函数或块作用域】)的函数被传递,就会产生闭包在这些函数中的应用。

通俗点说,这个函数能够读取到其他函数的内部变量,并且在这个函数的词法作用域之外被执行了。

下面概念清晰了,让我们来接触一道经典的闭包面试题:

for (var i = 0; i < 5; ++i) {
     setTimeout(function () {
        console.log(i + ' ');
     }, 100);
}

屁话不多说,执行结果5个5。

背景知识:setTimeout注册的函数需要等待线程空闲才能执行,也就是for循环执行之后。
所以即使是setTimeout(..,0)输出结果也不会变。现在的问题就是,当函数执行的时候,访问的都是同一个i,所以我们要将函数自己圈起来一个闭包。

for (var i = 0; i < 5; ++i) {
      (function () {
            setTimeout(function () {
                  console.log(i + ' ');
            }, 100);
      }());
}
这样,通过这个IIFE函数确实包起来一个作用域,但是这个作用域却没有什么内容,那我们给他加点内容进去。
for (var i = 0; i < 5; ++i) {
      (function (j) {
            setTimeout(function () {
                  console.log(j + ' ');
            }, 100);
      }(i));
}

加了个i,这样内存中就会有五个i,分别存在于函数的作用域中。

这里简单说明一下:函数体外的 i 是调用函数时传入的参数,函数的参数 j 是形式参数,实际的值来源于函数执行时传入的 i 的值,这里的逻辑是,立即执行函数 锁定住 当前的循环索引值 i ,然后利用函数的作用域将当前的 索引值 保存,参照闭包的概念,函数执行时读取到定义时上下文的值。

 

es6语法推出了块作用域,还可以通过将var 改成let来操作。

 

for (let i = 0; i < 5; ++i) {
     setTimeout(function () {
         console.log(i + ' ');
     }, 100);
}

let在for循环中,每次循环都会声明一次,for循环了5次,在每个函数作用域中都存在一个自己的 i.


以下为更新内容:

闭包实战:

比如我要实现一个防抖函数,顾名思义,规定时间内连续操作则只运行一次逻辑函数,这里以浏览器滚动实时打印高度为例介绍防抖函数的实现:

function debounce(fn, delay) {
	let timer = null
	return function() {
		if(timer) {
			clearTimeout(timer)
		}
		timer = setTimeout(fn, delay)
	}
}
function showTop () {
	var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
	console.log('当前位置' + scrollTop)
}
window.onscroll = debounce(showTop, 1000)

debounce作为一个函数,执行之后每次返回一个函数来进行操作,但是在这两个函数之间存在一个timer变量,根据上述闭包的概念我们可以知道,他并不会随着debounce函数的执行而消亡。

 

以上就是我对闭包的个人理解,谢谢观看,欢迎交流。
 



 

你可能感兴趣的:(javascript,Some,meaningful,JavaScript,闭包,经典)