大部分人都会做错的经典JS闭包面试题
由工作中演变而来的面试题
这是一个我工作当中的遇到的一个问题,似乎很有趣,就当做了一道题去面试,发现几乎没人能全部答对并说出原因,遂拿出来聊一聊吧。
先看题目代码:
- function fun(n,o) {
- console.log(o)
- return {
- fun:function(m){
- return fun(m,n);
- }
- };
- }
- var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
- var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
- var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
- //问:三行a,b,c的输出分别是什么?
这是一道非常典型的JS闭包问题。其中嵌套了三层fun函数,搞清楚每层fun的函数是那个fun函数尤为重要。
可以先在纸上或其他地方写下你认为的结果,然后展开看看正确答案是什么?
- //答案:
- //a: undefined,0,0,0
- //b: undefined,0,1,2
- //c: undefined,0,1,1
答案
都答对了么?如果都答对了恭喜你在js闭包问题当中几乎没什么可以难住你了;如果没有答对,继续往下分析。
JS中有几种函数
首先,在此之前需要了解的是,在JS中函数可以分为两种,具名函数(命名函数)和匿名函数。
区分这两种函数的方法非常简单,可以通过输出 fn.name 来判断,有name的就是具名函数,没有name的就是匿名函数
注意:在低版本IE上无法获取具名函数的name,会返回undefined,建议在火狐或是谷歌浏览器上测试
或是采用兼容IE的获取函数name方法来获取函数名称:
- /**
- * 获取指定函数的函数名称(用于兼容IE)
- * @param {Function} fun 任意函数
- */
- function getFunctionName(fun) {
- if (fun.name !== undefined)
- return fun.name;
- var ret = fun.toString();
- ret = ret.substr('function '.length);
- ret = ret.substr(0, ret.indexOf('('));
- return ret;
- }
遂用上述函数测试是否为匿名函数:
可以得知变量fn1是具名函数,fn2是匿名函数
创建函数的几种方式
说完函数的类型,还需要了解JS中创建函数都有几种创建方法。
1、声明函数
最普通最标准的声明函数方法,包括函数名及函数体。
- function fn1(){}
2、创建匿名函数表达式
创建一个变量,这个变量的内容为一个函数
- var fn1=function (){}
注意采用这种方法创建的函数为匿名函数,即没有函数name
- var fn1=function (){};
- getFunctionName(fn1).length;//
3、创建具名函数表达式
创建一个变量,内容为一个带有名称的函数
- var fn1=function xxcanghai(){};
注意:具名函数表达式的函数名只能在创建函数内部使用
即采用此种方法创建的函数在函数外层只能使用fn1不能使用xxcanghai的函数名。xxcanghai的命名只能在创建的函数内部使用
测试:
- var fn1=function xxcanghai(){
- console.log("in:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">");
- };
- console.log("out:fn1<",typeof fn1,">xxcanghai:<",typeof xxcanghai,">");
- fn1();
- //out:fn1< function >xxcanghai:< undefined >
- //in:fn1< function >xxcanghai:< function >
可以看到在函数外部(out)无法使用xxcanghai的函数名,为undefined。
注意:在对象内定义函数如var o={ fn : function (){…} },也属于函数表达式
4、Function构造函数
可以给 Function 构造函数传一个函数字符串,返回包含这个字符串命令的函数,此种方法创建的是匿名函数。
5、自执行函数
- (function(){alert(1);})();
- (function fn1(){alert(1);})();
自执行函数属于上述的“函数表达式”,规则相同
6、其他创建函数的方法
当然还有其他创建函数或执行函数的方法,这里不再多说,比如采用 eval , setTimeout , setInterval 等非常用方法,这里不做过多介绍,属于非标准方法,这里不做过多展开
三个fun函数的关系是什么?
说完函数类型与创建函数的方法后,就可以回归主题,看这道面试题。
这段代码中出现了三个fun函数,所以第一步先搞清楚,这三个fun函数的关系,哪个函数与哪个函数是相同的。
- function fun(n,o) {
- console.log(o)
- return {
- fun:function(m){
- //...
- }
- };
- }
先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。
这个新的对象内部包含一个也叫fun的属性,通过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。
注意:所有声明的匿名函数都是一个新函数。
所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。
函数作用域链的问题
再说第三个fun函数之前需要先说下,在函数表达式内部能不能访问存放当前函数的变量。
测试1,对象内部的函数表达式:
- var o={
- fn:function (){
- console.log(fn);
- }
- };
- o.fn();//ERROR报错
测试2,非对象内部的函数表达式:
- var fn=function (){
- console.log(fn);
- };
- fn();//function (){console.log(fn);};正确
结论是:使用var或是非对象内部的函数表达式内,可以访问到存放当前函数的变量;在对象内部的不能访问到。
原因也非常简单,因为函数作用域链的问题,采用var的是在外部创建了一个fn变量,函数内部当然可以在内部寻找不到fn后向上册作用域查找fn,而在创建对象内部时,因为没有在函数作用域内创建fn,所以无法访问。
所以综上所述,可以得知,最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。
所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。
到底在调用哪个函数?
再看下原题,现在知道了程序中有两个fun函数(第一个和第三个相同),遂接下来的问题是搞清楚,运行时他执行的是哪个fun函数?
- function fun(n,o) {
- console.log(o)
- return {
- fun:function(m){
- return fun(m,n);
- }
- };
- }
- var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined,?,?,?
- var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
- var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
- //问:三行a,b,c的输出分别是什么?
1、第一行a
- var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
可以得知,第一个fun(0)是在调用第一层fun函数。第二个fun(1)是在调用前一个fun的返回值的fun函数,所以:
第后面几个fun(1),fun(2),fun(3),函数都是在调用第二层fun函数。
遂:
在第一次调用fun(0)时,o为undefined;
第二次调用fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用fun(2)时m为2,但依然是调用a.fun,所以还是闭包了第一次调用时的n,所以内部调用第一层的fun(2,0);所以o为0
第四次同理;
即:最终答案为undefined,0,0,0
2、第二行b
- var b = fun(0).fun(1).fun(2).fun(3);//undefined,?,?,?
先从fun(0)开始看,肯定是调用的第一层fun函数;而他的返回值是一个对象,所以第二个fun(1)调用的是第二层fun函数,后面几个也是调用的第二层fun函数。
遂:
在第一次调用第一层fun(0)时,o为undefined;
第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用 .fun(2)时m为2,此时当前的fun函数不是第一次执行的返回对象,而是第二次执行的返回对象。而在第二次执行第一层fun函数时时(1,0)所以n=1,o=0,返回时闭包了第二次的n,遂在第三次调用第三层fun函数时m=2,n=1,即调用第一层fun函数fun(2,1),所以o为1;
第四次调用 .fun(3)时m为3,闭包了第三次调用的n,同理,最终调用第一层fun函数为fun(3,2);所以o为2;
即最终答案:undefined,0,1,2
3、第三行c
- var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined,?,?,?
根据前面两个例子,可以得知:
fun(0)为执行第一层fun函数,.fun(1)执行的是fun(0)返回的第二层fun函数,这里语句结束,遂c存放的是fun(1)的返回值,而不是fun(0)的返回值,所以c中闭包的也是fun(1)第二次执行的n的值。c.fun(2)执行的是fun(1)返回的第二层fun函数,c.fun(3)执行的也是fun(1)返回的第二层fun函数。
遂:
在第一次调用第一层fun(0)时,o为undefined;
第二次调用 .fun(1)时m为1,此时fun闭包了外层函数的n,也就是第一次调用的n=0,即m=1,n=0,并在内部调用第一层fun函数fun(1,0);所以o为0;
第三次调用 .fun(2)时m为2,此时fun闭包的是第二次调用的n=1,即m=2,n=1,并在内部调用第一层fun函数fun(2,1);所以o为1;
第四次.fun(3)时同理,但依然是调用的第二次的返回值,遂最终调用第一层fun函数fun(3,1),所以o还为1
即最终答案:undefined,0,1,1
后话
这段代码原本是在做一个将异步回调改写为同步调用的组件时的代码,发现了这个坑,对JS的闭包有了更深入的了解。
关于什么是闭包,网上的文章数不胜数,但理解什么是闭包还是要在代码中自己去发现与领悟。
如果要我说什么是闭包,我认为,广义上的闭包就是指一个变量在他自身作用域外被使用了,就叫发生了闭包。
希望读者能通过本文对闭包现象有进一步的了解,如有其它见解或看法,欢迎指正或留言讨论。
(完)
原文地址-http://www.cnblogs.com/xxcanghai/p/4991870.html
大部分人都会做错的经典JS闭包面试题的更多相关文章
- 一篇常做错的经典JS闭包面试题
作者 | Jeskson 来源 | 达达前端小酒馆 1 究竟是怎么样的一道面试题,能让我拿出来说说呢?下面请看代码: function fun(a,b) { console.log(b) return ...
- 很多人都会做错的一道JVM题?【分享】
有关Java虚拟机类加载机制相关的文章一搜一大把,笔者这儿也不必再赘述一遍了.笔者这儿捞出一道code题要各位大佬来把玩把玩,假定你一眼就看出了端倪,那么祝贺你,你可以下山了: public cla ...
- 经典JS闭包面试题(来理解闭包)(转)
转载地址:http://www.cnblogs.com/xxcanghai/p/4991870.html 先看代码: function fun(n,o) { console.log(o) return ...
- 大部分人都会忽略的Python易错点总结
python中复数实现(-2) 0.5和开根号sqrt(-2)的区别** (-2)**0.5和sqrt(-2)是不同的,前者是复数后者是会报错的. print((-2)**0.5) #输出:(8.65 ...
- 这道js面试题号称99%的人会做错
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- javascript深入理解js闭包(转)
javascript深入理解js闭包 转载 2010-07-03 作者: 我要评论 闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. ...
- 一道经典JS面试题
超过80%的候选人对下面这道JS面试题的回答情况连及格都达不到.这究竟是怎样神奇的一道JS面试题?他考察了候选人的哪些能力?对正在读本文的你有什么启示? 不起眼的开始 招聘前端工程师,尤其是中高级前端 ...
- (转)Groupon前传:从10个月的失败作品修改,1个月找到成功 并不挶泥在这个点子上面,它反而往后站一步,看看他们已经做好的这个网站,可以再怎么包装成另一个完完全全不同的网站?所有的人所做的每件失败的事情中, 一定有碰到或含有成功的答案」在里面,只是他们不知道而已。 人不怕失败」,只怕宣布失败」
(转)Groupon前传:从10个月的失败作品修改,1个月找到成功 今天读到 一个非常励志人心的故事 ,就像现在「叶问」有「前传」,最近很火红的团集购网站Groupon 也出现了「Groupon前传」 ...
- Go 语言从新手到大神:每个人都会踩的五十个坑(转)
Go语言是一个简单却蕴含深意的语言.但是,即便号称是最简单的C语言,都能总结出一本<C陷阱与缺陷>,更何况Go语言呢.Go语言中的许多坑其实并不是因为Go自身的问题.一些错误你再别的语言中 ...
随机推荐
- iOS10遇到有推送的Demo真机报错的解决
1.打开project.pbxproj,搜com.apple.Push 改成enabled = 0(在projectName.xcodeproj文件上右键"显示包内容",用文本编辑 ...
- php cli配置文件问题
引言 今天在教别人使用protobuf的时候,无意中发现了一个php cli模式下的诡异问题,费了老半天的找到解决方法了,这里拿出来分享下. 问题描述 我们这边最先引入了protobuf协议,使用的是 ...
- 用File判断D盘下面是否还有txt文件
package cn.idcast; import java.io.File; public class File1 { public static void main(String[] args) ...
- 我的SqlHelper类!
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threa ...
- Android系统学习小记
序言 Android 应用的启动到一个页面显示出来,这个过程涉及到点击事件的处理,以及如何启动一个Activity,启动一个Activity之后,如何将Activity中我们的设置的ContentVi ...
- Unity Development with VS Code
https://code.visualstudio.com/Docs/runtimes/unity
- thinkphp上传
上传代码 // 缩略图上传 $upload = new \Think\Upload();// 实例化上传类 $upload->maxSize = ;// 设置附件上传大小 $upload-> ...
- 【转】ASP.NET MVC学习笔记-Controller的ActionResult
1. 返回ViewResult public ActionResult Index() { ViewData["Message"] = "Welcome ...
- cURL函数
PHP的cURL函数是通过libcurl库与服务器使用各种类型的协议进行连接和通信的,curl目前支持HTTP GET .HTTP POST .HTTPS认证.FTP上传.HTTP基于表单的上传.co ...
- CSS百分比定义高度的冷知识
当我们给块级元素设置响应式高度的时候,例如给div设置height=50%,往往没能看到效果. 原因是百分比的大小是相对其父级元素宽高的大小,如最外层元素设置的百分比是对应屏幕而言的. 需要了解的是对 ...