JavaScript闭包的原理与缺陷
来源:互联网 发布:mac wine 安装软件 编辑:程序博客网 时间:2024/04/29 14:56
闭包的原理
闭包是指有权访问另一个函数作用域中的变量的函数。根据下面的代码示例来理解什么是闭包,在add函数内部的匿名函数中,访问到外部函数的变量outerArg,在执行add(10)之后外部函数返回了,并且将内部的匿名函数赋值给了变量addTen,此时通过addTen调用函数,依然可以访问到outerArg,也就是10。这个闭包中的变量,只能通过调用addTen函数访问,无法通过其他渠道访问到,下面代码最后一行通过输出属性的方式尝试访问结果是输出undefined。outerArg是属于add函数的作用域中的变量,addTen有权访问add函数作用域中的变量,因此addTen是一个闭包。闭包产生的本质是:在一个函数(外部函数)内部定义的函数(内部函数)会将外部函数作用域中的活动对象添加到自己的作用域链中,下面代码中inner函数将add函数的outerArg添加到自己的作用域链上。在add函数执行完之后,其执行环境会被销毁,但由于inner函数还在引用outerArg,所以outerArg不会被销毁,依然保留在inner函数的作用域链中。直到inner函数(addTen函数)被销毁之后,outerArg才会跟着其作用域链一起被销毁。由于闭包变量是位于作用域链上,因此必须调用闭包函数进入其作用域之后才能访问到闭包变量。
function add(outerArg) { function inner(innerArg) { return innerArg + outerArg; } return inner;}var addTen = add(10);console.log(addTen(1)); // 输出11console.log(addTen(2)); // 输出12console.log(addTen(3)); // 输出13console.log(addTen.outerArg); // undefined
闭包的缺陷
首先,闭包会将外部函数的活动对象都添加到自己的作用域链中,因此相对于普通的函数会更加耗费内存。
其次,闭包只能获取到外部函数中任何变量的最后一个值。如下面代码所示,在for循环中的匿名函数可以访问到闭包变量i,但是由于闭包所保存的是整个变量对象,因此所有闭包函数中访问到的变量i其实就是同一个变量i(Outer函数的变量i),而Outer函数在执行完毕后,其变量i的值为5,所以5个闭包函数访问到的值都是5。
function Outer() { var arr = new Array(); for (var i = 0; i < 5; ++i) { arr[i] = function() { return i; }; } return arr;}var arr = Outer();console.log(arr[0]()); // 输出5console.log(arr[1]()); // 输出5console.log(arr[2]()); // 输出5console.log(arr[3]()); // 输出5console.log(arr[4]()); // 输出5
最后,this对象的指向可能与预期的不一致。以下面代码为例,getName的执行结果是输出Window而不是someObj,原因就是getNameFunc函数返回之后,它的执行环境会被销毁,返回的函数赋值给getName,当我执行getName的时候执行环境实在全局环境下,this指向的对象是window,this.name引用到的是全局作用域下的name,也就是’Window’。
var name = 'Window';var someObj = { name: 'someObj', getNameFunc: function() { return function() { return this.name; } }};var getName = someObj.getNameFunc();console.log(getName()); // 输出'Window'
闭包缺陷的解决方案
对于闭包只能获取到外部函数中任何变量的最后一个值问题,可通过定义一个立即执行函数(IIFE)来解决。这种方法的原理其实是在执行立即调用函数时,传入变量i作为参数,而i是按值传递的,相当于复制了一次i的值,所以5次循环调用了5次函数复制了5个不同的i的值创建了5个值不同的变量num,而内部函数(这里指代码中注释inner处的函数)的闭包变量不在是引用i,而是引用了变量num。每个内部函数都有其对应的闭包变量num,这个时候闭包函数的行为就符合我们的预期效果了。
function Outer() { var arr = new Array(); for (var i = 0; i < 5; ++i) { arr[i] = function(num) { // inner return function() { return num; } }(i); } return arr;}var arr = Outer();console.log(arr[0]()); // 输出0console.log(arr[1]()); // 输出1console.log(arr[2]()); // 输出2console.log(arr[3]()); // 输出3console.log(arr[4]()); // 输出4
对于this对象问题,可以通过避免直接使用this对象的方式来解决。这种方式其实是给this对象起了个别名,用that指向外部函数作用域的this对象,然后在内部函数中引用that,将其加入到闭包中,这样就可以正确地访问到外部函数作用域的this对象了。
var name = 'Window';var someObj = { name: 'someObj', getNameFunc: function() { var that = this; // 别名 return function() { return that.name; } }};var getName = someObj.getNameFunc();console.log(getName()); // 输出'someObj'
最后,分享一个实用的闭包内存优化技巧。闭包会引用外部函数的整个活动对象,这种机制可能会导致保存多余的变量而造成内存浪费,以下面代码为例,内部函数仅仅引用了obj对象的id属性,而闭包会把整个obj对象都保存下来,如果该对象上有很多耗费内存的属性,那么这种简单的引用方式会导致产生一个很大的闭包。优化方式如AnotherOuter函数的写法所示,关键在于把需要引用的变量保存下来,然后通过把obj对象设置为null让它丢失引用以便被自动内存回收机制回收处理,这样,闭包函数引用的就只有id这个变量了。
function Outer() { var obj = {}; obj.id = '12345678'; obj.name = 'aha'; // ... 假设经过很多处理过程,最后obj上带有很多属性 return function() { return obj.id; };}function AnotherOuter() { var obj = {}; obj.id = '12345678'; obj.name = 'aha'; // ... 假设经过很多处理过程,最后obj上带有很多属性 var id = obj.id; obj = null; return function() { return id; };}
- JavaScript闭包的原理与缺陷
- JavaScript 的闭包原理与详解
- JavaScript闭包原理
- JavaScript闭包原理
- gprof原理与缺陷
- 修复一个闭包的缺陷
- JavaScript闭包实现原理
- 闭包与链式调用的原理
- arp工作原理与缺陷
- javascript的闭包与scope
- javascript的匿名函数与闭包
- JavaScript for in的缺陷
- JavaScript闭包定义及原理
- 闭包的原理
- javascript的闭包javascript
- JavaScript闭包与房子
- JavaScript函数与闭包
- javascript递归与闭包
- 举例说明使用JMeter做压力测试
- 暑期dp46道(38)--HDOJ 2845 Beans 最大不连续子序列和
- CodeForces - 429B Working out(dp)
- JZOJ4701. 【NOIP2016提高A组模拟8.15】Throw
- 网络编程2----UDP通信
- JavaScript闭包的原理与缺陷
- OJ------汽水瓶
- 移动端模板
- RDTSC指令实现纳秒级计时器
- 用命令测试安装好的OpenStack环境
- Jmeter测试结果分析(上)
- Datatables学习笔记--jquery表格插件
- Qt中文编码和QString类Unicode编码转换
- JavaScript中使用bind()方法