js 面试题 浅浅的花香味﹌ 2022-02-19 01:53 202阅读 0赞 闭包: ![复制代码][copycode.gif] 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的输出分别是什么? //答案: //a: undefined,0,0,0 //b: undefined,0,1,2 //c: undefined,0,1,1 ![复制代码][copycode.gif] 都答对了么?如果都答对了恭喜你在js闭包问题当中几乎没什么可以难住你了;如果没有答对,继续往下分析。 # JS中有几种函数 # 首先,在此之前需要了解的是,在JS中函数可以分为两种,具名函数(命名函数)和匿名函数。 区分这两种函数的方法非常简单,可以通过输出 fn.name 来判断,有name的就是具名函数,没有name的就是匿名函数 > 注意:在低版本IE上无法获取具名函数的name,会返回undefined,建议在火狐或是谷歌浏览器上测试 或是采用兼容IE的获取函数name方法来获取函数名称: ![复制代码][copycode.gif] ![复制代码][copycode.gif] /** * 获取指定函数的函数名称(用于兼容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; } ![复制代码][copycode.gif] ![复制代码][copycode.gif] 遂用上述函数测试是否为匿名函数: [![image][]][image 1] 可以得知变量fn1是具名函数,fn2是匿名函数 # 创建函数的几种方式 # 说完函数的类型,还需要了解JS中创建函数都有几种创建方法。 1、声明函数 最普通最标准的声明函数方法,包括函数名及函数体。 function fn1(){} 2、创建匿名函数表达式 创建一个变量,这个变量的内容为一个函数 var fn1=function (){} 注意采用这种方法创建的函数为匿名函数,即没有函数name var fn1=function (){}; getFunctionName(fn1).length;//0 3、创建具名函数表达式 创建一个变量,内容为一个带有名称的函数 var fn1=function xxcanghai(){}; > 注意:具名函数表达式的函数名只能在创建函数内部使用 即采用此种方法创建的函数在函数外层只能使用fn1不能使用xxcanghai的函数名。xxcanghai的命名只能在创建的函数内部使用 测试: ![复制代码][copycode.gif] ![复制代码][copycode.gif] 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 > ![复制代码][copycode.gif] ![复制代码][copycode.gif] 可以看到在函数外部(out)无法使用xxcanghai的函数名,为undefined。 > 注意:在对象内定义函数如var o=\{ fn : function ()\{…\} \},也属于函数表达式 4、Function构造函数 可以给 Function 构造函数传一个函数字符串,返回包含这个字符串命令的函数,此种方法创建的是匿名函数。 [![image][image 2]][image_image 2] 5、自执行函数 (function(){alert(1);})(); (function fn1(){alert(1);})(); 自执行函数属于上述的“函数表达式”,规则相同 6、其他创建函数的方法 当然还有其他创建函数或执行函数的方法,这里不再多说,比如采用 eval , setTimeout , setInterval 等非常用方法,这里不做过多介绍,属于非标准方法,这里不做过多展开 # 三个fun函数的关系是什么? # 说完函数类型与创建函数的方法后,就可以回归主题,看这道面试题。 这段代码中出现了三个fun函数,所以第一步先搞清楚,这三个fun函数的关系,哪个函数与哪个函数是相同的。 ![复制代码][copycode.gif] ![复制代码][copycode.gif] function fun(n,o) { console.log(o) return { fun:function(m){ //... } }; } ![复制代码][copycode.gif] ![复制代码][copycode.gif] 先看第一个fun函数,属于标准具名函数声明,是新创建的函数,他的返回值是一个对象字面量表达式,属于一个新的object。 这个新的对象内部包含一个也叫fun的属性,通过上述介绍可得知,属于匿名函数表达式,即fun这个属性中存放的是一个新创建匿名函数表达式。 > 注意:所有声明的匿名函数都是一个新函数。 所以第一个fun函数与第二个fun函数不相同,均为新创建的函数。 # 函数作用域链的问题 # 再说第三个fun函数之前需要先说下,在*函数表达式*内部能不能访问存放当前函数的变量。 测试1,对象内部的函数表达式: var o={ fn:function (){ console.log(fn); } }; o.fn();//ERROR报错 [![image][image 3]][image_image 3] 测试2,非对象内部的函数表达式: var fn=function (){ console.log(fn); }; fn();//function (){console.log(fn);};正确 [![image][image 4]][image_image 4] 结论是:使用var或是非对象内部的函数表达式内,可以访问到存放当前函数的变量;在对象内部的不能访问到。 原因也非常简单,因为函数作用域链的问题,采用var的是在外部创建了一个fn变量,函数内部当然可以在内部寻找不到fn后向上册作用域查找fn,而在创建对象内部时,因为没有在函数作用域内创建fn,所以无法访问。 所以综上所述,可以得知,最内层的return出去的fun函数不是第二层fun函数,是最外层的fun函数。 所以,三个fun函数的关系也理清楚了,第一个等于第三个,他们都不等于第二个。 # 到底在调用哪个函数? # 再看下原题,现在知道了程序中有两个fun函数(第一个和第三个相同),遂接下来的问题是搞清楚,运行时他执行的是哪个fun函数? ![复制代码][copycode.gif] ![复制代码][copycode.gif] 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的输出分别是什么? ![复制代码][copycode.gif] ![复制代码][copycode.gif] 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 题目: function Foo() { getName = function () { alert (1); }; return this; } Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; var getName = function () { alert (4);}; function getName() { alert (5);} //请写出以下输出结果: Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName(); ![复制代码][copycode.gif] function Foo() { getName = function () { alert (1); }; return this; } Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; var getName = function () { alert (4);}; function getName() { alert (5);} //答案: Foo.getName();//2 getName();//4 Foo().getName();//1 getName();//1 new Foo.getName();//2 new Foo().getName();//3 new new Foo().getName();//3 ![复制代码][copycode.gif] # 第一题 # 先看此题的上半部分做了什么,首先定义了一个叫Foo的函数,之后为Foo创建了一个叫getName的静态属性存储了一个匿名函数,之后为Foo的原型对象新创建了一个叫getName的匿名函数。之后又通过函数变量表达式创建了一个getName的函数,最后再声明一个叫getName函数。 第一问的 Foo.getName 自然是访问Foo函数上存储的静态属性,自然是2,没什么可说的。 # 第二题 # 第二问,直接调用 getName 函数。既然是直接调用那么就是访问当前上文作用域内的叫getName的函数,所以跟1 2 3都没什么关系。此题有无数面试者回答为5。此处有两个坑,一是变量声明提升,二是函数表达式。 ## 变量声明提升 ## 即所有声明变量或声明函数都会被提升到当前函数的顶部。 例如下代码: console.log('x' in window);//true var x; x = 0; 代码执行时js引擎会将声明语句提升至代码最上方,变为: var x; console.log('x' in window);//true x = 0; ## 函数表达式 ## var getName 与 function getName 都是声明语句,区别在于 var getName 是函数表达式,而 function getName 是函数声明。关于JS中的各种函数创建方式可以看 [大部分人都会做错的经典JS闭包面试题][JS] 这篇文章有详细说明。 函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。 例如下代码: console.log(x);//输出:function x(){} var x=1; function x(){} 实际执行的代码为,先将 var x=1 拆分为 var x; 和 x = 1; 两行,再将 var x; 和 function x()\{\} 两行提升至最上方变成: var x; function x(){} console.log(x); x=1; 所以最终函数声明的x覆盖了变量声明的x,log输出为x函数。 同理,原题中代码最终执行时的是: ![复制代码][copycode.gif] ![复制代码][copycode.gif] function Foo() { getName = function () { alert (1); }; return this; } var getName;//只提升变量声明 function getName() { alert (5);}//提升函数声明,覆盖var的声明 Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; getName = function () { alert (4);};//最终的赋值再次覆盖function getName声明 getName();//最终输出4 ![复制代码][copycode.gif] ![复制代码][copycode.gif] # 第三题 # 第三问的 Foo().getName(); 先执行了Foo函数,然后调用Foo函数的返回值对象的getName属性函数。 Foo函数的第一句 getName = function () \{ alert (1); \}; 是一句函数赋值语句,注意它没有var声明,所以先向当前Foo函数作用域内寻找getName变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到了,也就是第二问中的alert(4)函数,将此变量的值赋值为 function()\{alert(1)\}。 此处实际上是将外层作用域内的getName函数修改了。 > 注意:此处若依然没有找到会一直向上查找到window对象,若window对象中也没有getName属性,就在window对象中创建一个getName变量。 之后Foo函数的返回值是this,而JS的this问题博客园中已经有非常多的文章介绍,这里不再多说。 简单的讲,this的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this指向window对象。 遂Foo函数返回的是window对象,相当于执行 window.getName() ,而window中的getName已经被修改为alert(1),所以最终会输出1 此处考察了两个知识点,一个是变量作用域问题,一个是this指向问题。 # 第四题 # 直接调用getName函数,相当于 window.getName() ,因为这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1 # 第五题 # 第五问 new Foo.getName(); ,此处考察的是js的运算符优先级问题。 js运算符优先级: [![image\_thumb][image_thumb]][image_thumb_image_thumb] 参考链接:[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator\_Precedence][https_developer.mozilla.org_zh-CN_docs_Web_JavaScript_Reference_Operators_Operator_Precedence] 通过查上表可以得知点(.)的优先级高于new操作,遂相当于是: new (Foo.getName)(); 所以实际上将getName函数作为了构造函数来执行,遂弹出2。 # 第六题 # 第六问 new Foo().getName() ,首先看运算符优先级括号高于new,实际执行为 (new Foo()).getName() 遂先执行Foo函数,而Foo此时作为构造函数却有返回值,所以这里需要说明下js中的构造函数返回值问题。 ## 构造函数的返回值 ## 在传统语言中,构造函数不应该有返回值,实际执行的返回值就是此构造函数的实例化对象。 而在js中构造函数可以有返回值也可以没有。 1、没有返回值则按照其他语言一样返回实例化对象。 [![image\_thumb1][image_thumb1]][image_thumb1_image_thumb1] 2、若有返回值则检查其返回值是否为引用类型。如果是非引用类型,如基本类型(string,number,boolean,null,undefined)则与无返回值相同,实际返回其实例化对象。 [![image\_thumb2][image_thumb2]][image_thumb2_image_thumb2] 3、若返回值是引用类型,则实际返回值为这个引用类型。 [![image\_thumb3][image_thumb3]][image_thumb3_image_thumb3] 原题中,返回的是this,而this在构造函数中本来就代表当前实例化对象,遂最终Foo函数返回实例化对象。 之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。 遂最终输出3。 # 第七题 # 第七问, new new Foo().getName(); 同样是运算符优先级问题。 最终实际执行为: new ((new Foo()).getName)(); [copycode.gif]: /images/20220219/c88c20eeab824bc79e1efffac89a9bf7.png [image]: /images/20220219/416f5955fcc34e298f2d947f0f0e13d5.png [image 1]: http://images2015.cnblogs.com/blog/746158/201511/746158-20151124155219640-2082081917.png [image 2]: https://images2015.cnblogs.com/blog/746158/201511/746158-20151124155221577-1744963934.png [image_image 2]: /images/20220219/bbeb1389ee114e20ae9b6c12d7aae668.png [image 3]: https://images2015.cnblogs.com/blog/746158/201511/746158-20151124155222859-235244046.png [image_image 3]: /images/20220219/a2783a0935484a3ea8ebb24f561c640a.png [image 4]: https://images2015.cnblogs.com/blog/746158/201511/746158-20151124155223656-402578236.png [image_image 4]: /images/20220219/2f2d8b2072ad4ff896aeeca9f2a9a047.png [JS]: http://www.cnblogs.com/xxcanghai/p/4991870.html [image_thumb]: https://images2015.cnblogs.com/blog/746158/201602/746158-20160214172949325-418813184.png [image_thumb_image_thumb]: /images/20220219/34aab304741d444caad5509af40ca4dd.png [https_developer.mozilla.org_zh-CN_docs_Web_JavaScript_Reference_Operators_Operator_Precedence]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence [image_thumb1]: https://images2015.cnblogs.com/blog/746158/201602/746158-20160214172950685-343542103.png [image_thumb1_image_thumb1]: /images/20220219/46e3425de8ce4d1a8094ca97865a2b63.png [image_thumb2]: https://images2015.cnblogs.com/blog/746158/201602/746158-20160214172952435-211077752.png [image_thumb2_image_thumb2]: /images/20220219/7af7442c485e4c2aa71d95fcb212cfbc.png [image_thumb3]: https://images2015.cnblogs.com/blog/746158/201602/746158-20160214172953888-1817669513.png [image_thumb3_image_thumb3]: /images/20220219/db7722713d5149699a92d0af08d06f11.png
相关 JS面试题 > JS初级开始 > 1.基本数据类型有几种?(高频) String,Boolean,Number,Null,undefiend,Symbol(es6) Symbol: 左手的ㄟ右手/ 2022年09月10日 08:18/ 0 赞/ 156 阅读
相关 js面试题 一、作用域 1、写出console.log结果(作用域) (function(){ var a = b = 2; })() con ╰半橙微兮°/ 2022年05月29日 11:48/ 0 赞/ 176 阅读
相关 js(面试题) shuffle:顾名思义,将数组随机排序,常在开发中用作实现随机功能。 我们来看看一个 shuffle 可以体现出什么代码品味。 错误举例 function 小灰灰/ 2022年05月29日 02:38/ 0 赞/ 164 阅读
相关 js面试题 > 1.JavaScript中如何检测一个变量是一个String类型?请写出函数实现 方法①: function isString(obj){ 深藏阁楼爱情的钟/ 2022年04月02日 04:42/ 0 赞/ 184 阅读
相关 js 面试题 javascript的数据类型 原始类型:null,undefined,number(数字类型),string(字符串类型),boolean(布尔值类型),symbol(e 我会带着你远行/ 2022年03月18日 11:58/ 0 赞/ 214 阅读
相关 JS面试题 1.什么是JavaScript? JavaScript是一种客户端脚本语言,可以插入到HTML页面中,并且是目前较热门的Web开发语言。同时,JavaScript是基于对象 Dear 丶/ 2022年03月08日 10:22/ 0 赞/ 202 阅读
相关 js面试题 JavaScript 的组成 JavaScript 由以下三部分组成: ECMAScript(核心):JavaScript 语言基础 DOM(文档对象模型) 客官°小女子只卖身不卖艺/ 2022年02月22日 19:00/ 0 赞/ 363 阅读
相关 js 面试题 闭包: ![复制代码][copycode.gif] function fun(n,o) { console.log(o) retur 浅浅的花香味﹌/ 2022年02月19日 01:53/ 0 赞/ 203 阅读
相关 js 面试题 1:toString function A() { this.name = "a"; this.toString = 一时失言乱红尘/ 2021年09月20日 13:26/ 0 赞/ 304 阅读
还没有评论,来说两句吧...