JavaScript 闭包详解

js闭包详解

定义

  • MDN文档:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包closure
  • js高程:闭包是指有权访问另一个函数作用域中的变量的函数(意思是闭包是个函数)

简单介绍

先举两个例子:

function f1 () {
	var a = 1
	function f2 () {
		console.log(a)
	}
	f2()
}
f1() // 1
  • 显然,f1调用了f2,f2引用了父级作用域的a变量,这都是常规操作,并不是闭包。那如果我想在f1外边直接调用f2呢?而不是通过f1去调用:
function f1 () {
	var a = 1
	function f2 () {
		console.log(a)
	}
	return f2
}

const closure = f1()
closure() // 1
  • 现在呢?我们把f1中的f2这个函数返回给了closure(此时f1已经执行完毕,按正常进度a已经被销毁了),相当于closure就是函数f2,调用时(在外部调用f2),发现f2依旧调用到了f1中的变量a,说明a没有销毁,而是在内存中持续存在。化用MDN的解释:closure是执行f1时创建的f2函数实例的引用,f2的实例维持了一个对它词法环境(变量a存在于其中)的引用,导致a暂时未被销毁。

  • 事实也的确是这样,被闭包包装的变量并不会在函数销毁后进入js的垃圾回收机制,而是继续存在于内存中,而这些变量需要执行额外的操作进行销毁。所以,可以看出闭包对性能的影响是负面的,在使用闭包时一定要考虑清除是否有必要。

  • 可以看出闭包有这种功能:能在外部调用函数(f1)内部的函数(f2)时调用其父级作用域(f1)里的变量,也就是使得本该被销毁的变量没有被销毁,同时还能被调用

闭包与变量

  • 闭包所保存的是整个变量对象,而不是某个特殊的变量,所以它只能取得变量的最后一个值
	function f () {
		var a = new Array()
		for (var i = 0; i < 10; i++) {
			a[i] = function () {
				return i
			}
		}
		return a
	}
	let b = f() // 返回的是一个函数数组
	b[0]() // 10
	b[1]() // 10
	...
  • 这个例子说明,b初始化后,f执行完毕,i经历循环取得值10,意味着f函数的变量对象(变量对象储存了执行上下文的所有变量和函数,这是作用域相关知识里的)此时保存的是 “ i = 10 ”, 而当你调用b[i] ()时会重新取得i的值,且i的值已经固定

闭包与this对象

var name = "Window"
var obj = {
	name: "Object",
	getName: function () {
		return function () {
			return this.name
		}
	}
}
// 为何会是双括号:因为第一个括号只是执行getName函数,而该函数又返回一个函数,所以得再加一个“()”来执行返回的函数
alert(obj.getName()()) // "Window"
  • 为什么 输出“ Window ” ?最后一个执行语句可以被拆分理解,首先是执行obj.getName(),返回了一个函数,相当于下一步执行返回函数func()(相当于在全局调用func函数),此时该函数中的this指向全局对象window,因为this是运行时绑定机制,所以此时返回函数的执行追溯它的上一级就是全局。
var name = "Window"
var obj = {
	name: "Object",
	getName: function () {
		var that = this
		return function () {
			return that.name
		}
	}
}
alert(obj.getName()())
  • 这里与上题不同的是,this已经赋值给that,执行obj.getName()时,this指向obj对象,所以that也是,所以执行返回函数func()时由于是that而非this(this会随环境改变),所以返回的是obj的name(“Object”)

面试题

  • 理解闭包不是一蹴而就,多看资料多看题是很有必要的
for ( var i = 0 ; i < 5; i++ ) {
	// 此处涉及到 “事件循环” 知识
    setTimeout(function(){ // 该定时器被加入任务队列,循环结束后实行
        console.log(i);
    }, 0);
}
// 5 5 5 5 5
for ( var i = 0 ; i < 5; i++ ) {
    // 每一次循环,函数都会开辟一个新的作用域,五个作用域分别存了0,1,2,3,4
    // 每次定时器的函数调用的i都是不同作用域里的
    (function(j){
        setTimeout(function(){
            console.log(j);
        }, 0);
    })(i);
}
// 0 1 2 3 4
for ( let i = 0 ; i < 5; i++ ) {
	// let定义了块级作用域,类似上题函数的效果
    setTimeout(function(){
        console.log(i);
    },0);
}
// 0 1 2 3 4

function f1(a,b) {
  console.log(b)
  return {
      fun: function (c) {
          return fun(c,a);
      }
  };
}
var d = f1(0); // undefined,d:{fun: func...}
d.fun(1); // 0
d.fun(2); // 0
d.fun(3); // 0
  • 为什么末尾三个输出都是0?按照普通思路,一步步追溯你可能会以为返回undefined,这说明你忽略了闭包。首先,d是什么?d是个对象,里面有个fun属性,该属性是一个函数(暂且称作f2,闭包)。而d初始化时f1中a为0,所以当执行d.fun(1)时,f2中的a会引用f1中的a=0,所以无论传入什么参数,a都是恒定的,f2外部的f1已经规定好了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值