昨天的文章中主要记录了,函数表达式与函数声明的区别
以及在JS中如何安全地使用递归
那么既然要深入地理解JS中的函数,闭包就是一个绕不开的概念
闭包
JS高编一书中对闭包的概念定义如下:
闭包是指有权访问另一个函数作用域中变量的函数
我们来理解这句话,闭包指的是一类函数
这类函数的特点是可以访问另一个函数的作用域
我们知道JS中Es6以下是没有块级作用域的
只有全局作用域,以及函数作用域
一般来讲,函数作用域里面的变量在函数外部是无法访问的
而闭包却可以访问另一个函数作用域,那么说明了什么?
说明闭包说白了就是在函数内部定义或声明的函数
以下面的代码举例
function createComparisonFunction(propertyName){// 用于创建比较函数的函数 return function(object1,object2){// 根据propertyName来比较对象的对应属性的值 var value1 = object1[propertyName]; var value2 = object2[propertyName]; if(value1>value2){ return 1; }else if(value1<value2){ return -1; }else{ return 0; } } }
这就是闭包的使用场景之一
我们在内部的函数中访问了外部函数的变量
而当内部匿名函数作为值返回后我们在函数的外部也能访问到函数createComparisonFunction内部的值
这样的结果似乎跟我们之前对JS的认知产生了冲突
我们知道当一个函数执行完毕后,其执行上下文便会被销毁
为其分配的内存也会被垃圾收集器回收
那么为什么闭包依旧可以访问呢?
之前我们讲过JS中的垃圾回收机制
当一个对象不再被引用时才会被垃圾收集器释放内存
而JS中的执行上下文,在ES5被称为活动对象,ES6中似乎被称为变量环境
不管名字是什么,其实就是指的一个保存变量声明等相关信息的对象
虽然 createComparisonFunction 已经执行完毕,但是由于其内部的匿名函数仍旧保存着对这个对象的引用,所以该对象无法被回收
这也是闭包占用内存多的原因
那么闭包的引用的这个对象什么时候会被回收呢?
var compareName = createComparisonFunction('name'); // compareName 保存了对返回的匿名函数的引用 // 一些操作 compareName = null;// 解除对返回的比较函数的引用
也就是当这个闭包不再被引用,闭包的执行上下文,与其外部函数的执行上下文都将一起被回收
闭包与变量
闭包虽然可以访问外部函数的值
但是其作用不是万能的,因为闭包引用的是外部函数的执行上下文
所以闭包只能获得闭包执行时的外部函数执行上下文中变量的最后一个值
function createFunctions(){ var result = new Array(); for (var i=0;i<10;i++){ result.push(function(){return i;}) } return result; }
在浏览器中运行结果如下
按照我们上面的结果来看,肯定是不符合我们的预期的
我们或许希望,array 数组中每个对象都返回对应执行时的值
那么我们可以通过JS中的参数传递都是值传递来完成这一点
将之前的函数改写为
function createFunctions(){ var result = new Array(); for (var i=0;i<10;i++){ result.push((function(num){ return function(){return num;} })(i)); } return result; }
使用自执行函数来将每次循环的i值保存到不同的执行上下文中
我们来看看结果
这种方法相当于就是创建了十个执行上下文,每个返回的闭包都引用不同上下文,来实现的
所以十分耗费内存,在实践中不推荐使用
关于this对象
要注意的是,虽然闭包可以访问外部函数的执行上下文
但是并不意味着闭包可以直接访问外部函数的 this 和 arguments对象
因为每个函数在创建时都会自动地取得这两个变量,而不会去获取外部的this
所以如果希望在闭包中访问外部函数的this变量,那么需要在外部函数中创建一个变量来保存 this
内存泄漏
我们知道js的内存是由JS自己回收的
所以我们在获得便利的同时,也增加了内存泄漏的风险
因为这是我们不能控制的
我们只能尽量避免这种情况的发生
而跟闭包有关的主要是在DOM事件中,这里就先不展开讲了,感兴趣的小伙伴可以留言,给我说不定可以开个番外篇