用一道面试题考察对闭包的理解
2015年2月6日 / BY AARONJS
关于闭包的用法,几乎是所有前端面试中必点的菜之一,也是考察javascript掌握程度的重要知识之一,下面这题,是某知名IT企业出的题型,我稍加修改,分享如下:
~~~
var name = 'global';
var obj = {
name : 'obj',
dose : function(){
this.name = 'dose';
return function(){
return this.name;
}
}
}
alert(obj.dose().call(this))
~~~
请写出执行结果? //global
关于这样的题型,应当怎样去分析呢?
~~~
obj.dose().call(this)
~~~
这个表达式有点长,看着有点眼晕,不妨进行一个等价变形。
~~~
var xxx = obj.dose();
xxx.call(this);
~~~
这样就清晰多了。这样一眼就看出是在考察call的用法和this的指向。稍有点基础的,一眼就可以看出此处的this就是window对象。如果你看不出,就再去看看那本红宝书3
即使知道此处的this是window还没完。还要顺路普及一下call的用途:
// 1. 替换函数运行环境中的this
// 2. 传递参数
// 3. 运行函数
通过前面的分析可以知道xxx是这样一个函数:
~~~
function(){
return this.name;
}
~~~
由于call指定了this是window,所以return this.name 就是 window中的name,即global;
如果是面试呢,这题到此就结束了,不过举一反三才是我的目的。因此,下面我稍改一下题目:
~~~
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)
}
~~~
所以,不要认为闭包有缺点就不敢用,也不要因为闭包的优点而滥用。 是否选择闭包,视我们的需要来定。
## 总结
总结一点:学东西不可浅尝则止,一定要深入原理,举一反三,利用它的优点,规避它的缺点。
- 前言
- 【00】如何写
- 【STAT法则写简历】
- 【01】前端
- 【20160829 前端面试题】
- 【腾讯IMWeb】笔试题(没有答案)
- 【桑世龙】前端笔试题(没有答案)
- 【浏览器输入URL后发生了什么】
- 【JS截图并生成图片】
- 【20160924】Sass 入门
- 【02】技巧
- 【01】GOOGLE搜索技巧
- 【02】Chrome跨域访问线上接口
- 【One Day One Tip】
- 【20160830】~ 闭包
- 【20160831】~ 继承的几种实现方式
- 【20160901】~浏览器输入URL到页面展示完成,发生了什么?(一)
- 【20160902】~浏览器输入URL,发生过程系列(转载)
- 【20160903】~ video在不同平台下的差异性
- 【20160906】~webpack之sourceMap
- 【20160909】ACE自定义代码提示
- 【20160910】Mac Nw.js 环境安装
- 【99】转载笔记
- 用一道面试题考察对闭包的理解