js系列十:闭包

版权声明:版权归作者所有 https://blog.csdn.net/qq_36772866/article/details/87920510

概念
闭包是一种特殊的对象。
它由两部分组成:执行上下文(代号A),已经在该执行上下文中创建的函数(代码B)。
当B执行时,如果访问了A中变量对象中值,那么闭包就会产生。
我们只需要知道,一个闭包对象,由A,B共同组成,在以后的文章中,都会将以chrome的标准来称呼。

function foo() {
	var a = 20;
	var b = 30;
	function bar() {
		return a + b;
	}
	return bar;
}
var bar = foo();
bar();

在上面的例子中,首先执行上下文 foo,在foo中定义了函数bar,而后通过对外返回bar的方式让bar得以执行。当时执行时,访问了 foo 内部变量a和b。因此这个时候闭包产生,
在chrome中通过断点调试的方式可以逐步分析该过程,从图中可以看出,此时闭包产生,用foo代替
在这里插入图片描述
在这里插入图片描述
在图中,箭头所指的正是闭包,其中call stack 为当前的函数调用栈,scope 为当前正在被执行函数的作用域链,Local 为当前活动对象。
现在我们来思考一个小小的问题,把上面的代码调整成这样后,是否形成闭包?

function foo() {
	var a = 20;
	var b = 30;
	function bar() {
		return a + b;
	}
	bar();
}
foo();

在这里插入图片描述
还是形成了闭包。
再来看一个例子,非常有意思的例子

function add(x) {
	return function _add(y){
		return x+y;
	}
}
add(2)(3); //5

这个例子当然是产生闭包的,内部函数_add(y)被调用执行的时候,访问了add函数变量对象的中x的值,这个时候,闭包就会产生,如图所示,一定要记住,函数参数中的变量传递给函数之后也会加到变量对象中
在这里插入图片描述
还有一个例子可以验证大家对于闭包的理解。
看看下面这个例子是否产生闭包

var name = "window";
var p = {
	name: 'Perter',
	getName: function() {
		return function () {
			return this.name;
		}
	}
}
var getName = p.getName();
var _name = getName();
console.log(_name);

getName在执行时,他的this其实是指向的是window对象,而这个时候并没有形成闭包的环境,因此并没有产生闭包。

那么这个例子改成如下这样呢?

var name = "window";
var p = {
	name: 'Perter',
	getName: function() {
		return function () {
			return this.name;
		}
	}
}
var getName = p.getName();
var _name = getName.call(p);
console.log(_name);

没有产生闭包,
如果再改成这样呢?

var name = "window";
var p = {
	name: 'Perter',
	getName: function() {
		var self = this;
		return function () {
			return self.name;
		}
	}
}
var getName = p.getName();
var _name = getName();
console.log(_name);

这样就会产生闭包了
在这里插入图片描述
闭包与垃圾回收机制
垃圾回收机制,知道当一个值失去引用之后就会被标记,然后被垃圾回收机制回收并释放空间。
我们知道,当一个函数的执行上下文运行完毕之后,内部的所有内容都会失去引用而被垃圾回收机制回收。
我们还知道,闭包的本质就是在函数外面保持了内部变量的引用,因此闭包会阻止垃圾回收机制进行回收。
我们用一个例子来证明这一点

function f1() {
	var n = 999;
	nAdd = function() {
		n += 1;
	}
	return function f2(){
		console.log(n);
	}
}
var result = f1();
result();
nAdd();
result();

从上面的例子可以看出,因为 nAdd ,f2 都可以访问f1中的n,因此他们都会与f1形成了闭包。这个时候变量n都被保留下来了,因为f2(result)与nAdd执行时间都访问了n,而nAdd每运行一次就会将n加1,所以上例的执行结果非常符合我们的认知。
在这里插入图片描述
闭包与作用域链
先花几秒钟时间来思考一个小问题,闭包会导致函数的作用域链的变化呢?

var fn=null
function foo() {
	var a = 2;
	function innerFoo() {
		console.log(a);
	}
	fn = innerFoo; // 将innerFoo 的引用赋值给全局变量中的fn;
	function bar() {
		fn(); //此处保留innerFoo的引用
	}
	foo();
	bar();
}

在这个例子中,foo内部的innerFoo访问了foo的变量a。因此当innerFoo执行时会有闭包产生,这是一个比较简单的例子,不一样的地方是全局变量fn。fn在foo内部获取了innerFoo的引用,并在bar中执行。
在这里插入图片描述
那么innerFoo的作用域链会怎么样呢?
在这里需要特别注意的地方是函数调用栈作用域链的区别。
因为函数调用栈其实是在代码执行时才确定的,而作用域规则在代码编译阶段就已经确定了,虽然作用域链是在代码执行时才生出的,但是他的规则不会在执行期间发生变化。所以闭包的存在不会影响作用域链的变化。

猜你喜欢

转载自blog.csdn.net/qq_36772866/article/details/87920510