关于闭包的用法,几乎是全部前端面试中必点的菜之一,也是考察javascript掌握程度的重要知识之一,下面这题,是某知名IT企业出的题型,我稍加修改,分享以下:javascript
var name = 'global'; var obj = { name : 'obj', dose : function(){ this.name = 'dose'; return function(){ return this.name; } } }
alert(obj.dose().call(this))
请写出执行结果?html
关于这样的题型,应当怎样去分析呢?前端
obj.dose().call(this) 这个表达式有点长,看着有点眼晕,不妨进行一个等价变形。java
var xxx = obj.dose(); xxx.call(this);
这样就清晰多了。这样一眼就看出是在考察call的用法和this的指向。稍有点基础的,一眼就能够看出此处的this就是window对象。若是你看不出,就再去看看那本红宝书3jquery
即便知道此处的this是window还没完。还要顺路普及一下call的用途:面试
// 1. 替换函数运行环境中的this
// 2. 传递参数
// 3. 运行函数浏览器
经过前面的分析能够知道xxx是这样一个函数:缓存
function(){ return this.name; }
因为call指定了this是window,因此return this.name 就是 window中的name,即global;闭包
若是是面试呢,这题到此就结束了,不过触类旁通才是个人目的。所以,下面我稍改一下题目:app
var name = 'global'; var obj = { name : 'obj', dose : function(){ this.name = 'dose'; return function(){ return this.name; }.bind(this) } } alert(obj.dose().call(this))
因为return的function中用了bind,因此至关于固定了this,外边再call什么进来,也只是碍眼法而已。因为函数内部邦定了this,因此此处的状况要另外分析了
首先,obj对象定义了name属性为'obj';接着在dose 方法内,又改写了name属性为'dose'; 根据做用域链的就近原则,alert访问的确定就是'dose'这个值了。
然而ie派认为在return中用bind不常见,兼容性也不高。那不妨再变一下:
var name = 'global'; var obj = { name : 'obj', dose : function(){ var that = this; this.name = 'dose'; return function(){ return that.name; } } } alert(obj.dose().call(this))
这种写法,天然你们都比较认同了。考察仍是相同的内容,只不过是邦定this的手法不同而已。与其说是考察闭包,不如说是考察对基础知识的理解,由于bind,call,apply之类的方法都是平时使用频率很高的,对它们多花点时间琢磨一下,必然是有好处的。
最后呢再分享一个面试的趣事。面试官问我,用闭包有什么好处?
1. 延长做用域链。
这一点你们是熟知的,由于闭包函数能够访问外层函数做用域中的变量及对象,以代码来演示一下:
function wrap () { var out = '外部变量'; return function (){ //这里能够访问外部函数中的变量 //实际上就是建立了一个闭包函数 alert(out); } } var inner = wrap(); //虽然wrap运行完毕了,可是inner依然能够访问它所建立的做用域中的变量 //这就是闭包第一个用法 inner();
2. 生成预编译函数。
这一点是借用jquery中的说法,实际上就是经过闭包把外层函数提供的参数保存起来,在闭包运行的时候就能够获得预先指定的参数
var fn = []; for(var i = 0;i<3;i++){ (function(n){ fn.push(function(){ alert(n) }) })(i) }
说化函数的curry化,可能知道的人会更多一点,和上面的例子类似,不过它强调的是参数的积累。
function addGenerator(num){ return function(toAdd){ return num + toAdd; }; } //建立一个新的函数 var addFive = addGenerator(5); alert(addFive(4)==9) //true
3.更好的组织代码,好比模块化,异步代码转同步等。
Deferred.next(function(){ alert(1) return Deferred.wait(3) }).next(function(){ alert(2) }).next(function(){ alert(3); });
4. 处理异步形成的变量不能即时传递的问题
/** * html结构: * <ul> * <li> 0</li> * ...... * <li> 9 </li> * </ul> */ //点击弹出对应的数字 var items = document.querySelectorAll('li'); for(var i=0;i<items.length;i++){ items[i].onclick = function(){ alert(i) } } //上面的程序结果是:每次都弹出10; //为了在用户点击的时候,能弹出对应的数字 // 须要构建一个闭包,将参数缓存起来 for(var i=0;i<items.length;i++){ items[i].onclick = (function(n){ return function(){ alert(n) } })(i) } // 这时点击的时候就会弹出邦定的数字了,强烈推荐试一下
暂时就想到这些,其它的想到再补充。
他接着又问我,那用闭包又有什么坏处?
1. 增长了内存的消耗。
2. 某些浏览器上由于回收机制的问题,有内存溢出风险。
3. 增长了代码的复杂度,维护和调试不便。
我balabala说了一些,他就笑了。说你这一边是矛,一边是盾,究竟是矛好呢仍是盾好呢?当时也没想这么多,反射性的回答说,看状况选用咯。他说这样是不行的。
原来挖了个坑在这里等着我呢,真是太不厚道了。凡事都有两面性嘛,只要撑握的好,天然是能够避害用利。咱们都知道电是很危险,也颇有用,只要掌握了它的特性,就能很好的利用,而不是受其害,因此并非矛盾的就不可取。拿最后一个例子来讲,若是不用闭包,难道就没有办法了吗?显然不是的,好比下面的代码就不用闭包,照样解决该问题:
function fn(n){ items[n].onclick = function(){ alert(n) } } for(var i=0;i<items.length;i++){ fn(i) }
因此,不要认为闭包有缺点就不敢用,也不要由于闭包的优势而滥用。 是否选择闭包,视咱们的须要来定。
总结一点:学东西不可浅尝则止,必定要深刻原理,触类旁通,利用它的优势,规避它的缺点。