闭包-执行上下文-作用域

149 阅读3分钟

闭包的概念

总的来讲可以概括为:内层函数实现了一个对外部变量的引用,将外部变量长久的保存了下来 通俗的讲闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。

红宝书闭包的定义:闭包是指有权访问另外一个函数作用域中的变量的函数。 MDN:一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

相关概念:执行上下文,作用域链 执行上下文:(这里主要讲函数执行上下文)每个函数自从创建之初都拥有自己内部的一个执行环境,和执行的语句,在每次调用的时候都会形成自己的一个执行上下文;

在相关内部执行的时候内部引用的变量若是不存在,它会向它的上一级查找,如此往复形成一条作用域链

看下面一个例子:

function fun1() {
	var a = 1;
	return function(){
		console.log(a);
	};
}
fun1();
var result = fun1();
result();  // 1

从上面我们可以看到,内部函数对a引用了,形成了作用域链, 它的本质就是:当前环境中存在指向父级作用域的引用

有的人可能存在疑惑:是不是一定要返回函数才算是闭包呢?结果当然不是 回到闭包的概念,只要让内部函数存在一个对父级作用域的引用就可以了,如下

var logNum;
function fun1() {
  var num = 2
  logNum = function() {
    console.log(num);
  }
}
fun1();
logNum();

这里我们的代码中,将函数用外部变量接收,在外部执行,从而也达到了内部函数对父级作用域的引用

闭包的应用及表现

很多时候我们可能遇到过这样的一个面试题

for(var i=0;i<5;i++){
	setTimeout(()=>{
		console.log(i)
	})
}

看到这个题我们知道结果是5个5,那要怎么回答才能让对方满意呢?其实这个问题可以围绕两点来:作用域和js事件even loop来看

  1. setTimeout 为宏任务,在 JS 单线程 eventLoop 机制中,主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。

  2. 因为 setTimeout 函数也是一种闭包,往上找它的父级作用域链就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经就是 5 了,因此最后输出的连续就都是 5。

如何让他输出我们想要的0,1,2,3,4呢? 我们可以利用匿名自执行函数改造一下,如下:

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

也可以使用es6推出的块级作用域的形式,针对作用域的改变去做处理:

for(let i=0;i<5;i++){
	setTimeout(()=>{
		console.log(i)
	})
}

或者利用定时器的第三个参数将i传递进去作为回调函数的参数打印

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