先来看下这道面试题,写这篇博客也是由这道题引起的
如果你对这道题完全理解,没有任何疑惑,那么无须继续往下阅读了
匿名函数,意指没有名字的函数,常出现于对象方法、自执行函数、函数回调以及高阶函数(以函数为参数,或以函数为返回值)中,是函数的一种特殊形式。
从文字上可能不太好理解,直接看下面几个示例
对象方法
将一个匿名函数赋值给对象的sayHello属性
自执行函数
通过括号运算符将匿名函数包括起来,转换成函数表达式,然后再通过括号运算符来调用匿名函数
当声明定义一个函数后,函数是一种对象,存储在堆内存中,函数名其实就是指向函数体的指针,当函数名后直接添加括号时,该函数将会被调用。
12345678 (function () {console.log('Hello world!')})()// 另一种写法,执行结果与上述没有差别(function () {console.log('Hello world!')}())
注意:所有自执行函数都是全局的,也就是说自执行函数内部的this指针指向的是全局对象 window 。
函数回调
在函数回调的情况中,回调的函数有两种,其一是函数有函数名,另外就是匿名函数。
高阶函数
在高阶函数中,匿名函数常常被当做函数返回值,当返回一个匿名函数,匿名函数中又调用了它外层函数的变量时,就会形成闭包。
闭包,就是存在多个嵌套函数时,内层函数能够调用外层函数中的变量。
12345678910 var fn = function () {var outterWords = 'Hello outter'// console.log(innerWords)return function () {var innerWords = 'Hello inner'console.log(outterWords)}}console.log(fn()) // 'Hello outter'
在上述代码中,嵌套了两层函数,内层函数能够访问到外层函数作用域中的变量 outterWords ,但是外层函数不能访问内层函数作用域中的 innerWords ,这就是闭包的一种情形。
闭包在JavaScript中是非常重要的,要讲的东西还有很多,这里不再展开,还不清楚的小伙伴可以上网查找相关资料。
这里推荐一个很赞的系列博客文章:深入理解JavaScript原型和闭包
回到高阶函数,上述代码中,其实也就是一个高阶函数,它将一个匿名函数当作返回值返回。
介绍完这几种匿名函数的形式,我们回到文章开头的那道面试题
一步步解析:
这部分相信大家都知道,打印的是 a.fn() 返回的匿名函数的函数体
接着往下:
这部分我们首先要注意到,这里用到了两次括号运算符,第一次是将 a.fn() 返回的函数体转换为函数表达式,第二次则是调用这个函数表达式,也就是转换成了自执行函数,自执行函数的 this 指针指向的是全局对象 window 。
所以输出结果为 20
|
|
这部分我一开始也是比较困惑的,因为函数体后面直接加上括号运算符,是会报语法错误的,如下所示:
但是,当一个函数返回一个匿名函数,然后再加上括号运算符,则能够正常执行输出的。
其实,当函数返回匿名函数时,返回的是函数表达式,函数表达式后加上括号运算符,则该函数会被调用,函数内部的 this 指向全局对象 window 。所以输出20
知道了前两个的输出,那么自然第三个也明白了,输出 true 。
|
|
这部分主要是 call 方法的运用。call 与 apply 方法的作用是更改函数调用的对象。
上述代码中,this 指向的是全局变量 window ,调用a.fn()返回的匿名函数,输出的是全局的变量x: 20 。
|
|
这部分函数调用的对象为 a ,所以输出的是a内部的变量x: 15 。
扩展延伸:
|
|
可以看到这道题与上面那道题的第三个输出是有点相似,区别在于这道题返回的 scope 没有绑定在this指针上,而上面那道题是 this.x 。那么它们的输出结果是一样的吗?答案是否定的。
区别就是在于有没有 this 指针。我们知道当一个函数返回一个函数体后,加上括号,会被当做函数表达式执行,函数表达式内部的 this 指针是指向全局变量的。如果没有 this 指针,那么执行返回的变量 scope ,首先会顺着作用域链去寻找,也就是外层函数中寻找,找到了 scope ,所以输出’local scope’。有 this 指针,则会在 this 指针指向的对象上去寻找 scope 变量,所以如果这道题是
输出的就是 ‘global scope’
总结
这篇文章讲述了匿名函数的几种应用场景:对象方法、自执行函数、函数回调以及高阶函数。
最应该牢记的匿名函数的执行条件:将匿名函数通过 void , ~ , + ,- ,! ,() 等形式来转化为函数表达式,然后通过 () 调用。
如果匿名函数没有任何关键字、运算符,那么就会报错。
在一个函数中返回一个函数体,加上括号(),将变成函数表达式,如果这个函数表达式中返回一个普通变量,那么这个变量会在它的作用域链中去寻找;
如果这个变量是绑定在 this 指针上,那么会在 this 指向的对象上寻找,如果在对象上找不到,则会顺着这个对象的原型链去寻找。