详解JavaScript的匿名函数(面试重点)

2,085 阅读4分钟

什么是匿名函数:没有实际名字的函数

匿名函数的作用:

1、通过匿名函数可以实现闭包(必须掌握的知识点)

2、模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,使用块级作用域,会大大降低命名冲突的问题,不必担心搞乱全局作用域了。

详解匿名函数:

声明一个普通函数

function zxx () {
    console.log('good girl')
}

将函数的名字去掉

function () { // 此时浏览器会报错
    console.log('good girl')
}

正确定义的匿名函数

(function () {
    // 由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
    console.log('zxx')
})

对去掉名字的函数加入括号后就是一个匿名函数了:

小括号的作用:

小括号能把我们的表达式组合分块,并且每一块,也就是每一对小括号,都有一个返回值。这个返回值实际上也就是小括号中表达式的返回值。所以,当我们用一对小括号把匿名函数括起来的时候,实际上小括号返回的就是一个匿名函数的Function对象。因此,小括号对加上匿名函数就如同有名字的函数般被我们取得它的引用位置了。所以如果在这个引用变量后面再加上参数列表,就会实现普通函数的调用形式。 通俗点讲就是,加入小括号后就实现了和具名函数一样的形式。

匿名函数自执行,也称为立即执行函数表达式(IIFE)

  • 方式一
    // 无参数的匿名函数
    (function () {
        console.log('zxx')
    })();
 
    // 带参数的匿名函数
    (function (a, b, c) {
        console.log('参数一:', a) // 参数一: 这是普通函数传参的地方
        console.log('参数二:', b) // 参数二: 我是参数二
        console.log('参数三:', c) // 参数三: zxx
    })('这是普通函数传参的地方', '我是参数二', 'zxx')
  • 方式二
    // 推荐使用
    (function () {
        console.log('zxx')
    }())
  • 方式三
    !function (zxx) {
        console.log(zxx)
    }('zxx')
  • 方式四
    let zxx = function (zxx) {
        console.log(zxx)
    }('zxx')

  IIFE常用用法

    IIFE 的另一个非常普遍的进阶用法是把它们当作函数调用并传递参数进去。
    var a = 2;
    (function IIFE (global) {
        var a = 3
        console.log(a) // 3
        console.log(global.a) // 2
    })(window)
    console.log(a) // 2
 
    IIFE 还有一种变化的用途是倒置代码的运行顺序,
    将需要运行的函数放在第二位,
    在 IIFE 执行之后当作参数传递进去
    var a = 2;
    (function IIFE (def) {
        def(window)
    })(function def (global) {
        var a = 3
        console.log(a) // 3
        console.log(global.a) // 2
    })

匿名函数应用场景

    1.事件
    $('#zxx').onclick = function () {
        console.log('给按钮添加点击事件')
    }
 
    2.对象
    var obj = {
        name: 'zxx',
        zxx: function () {
            return this.name + ' is' + ' good girl'
        }
    }
    console.log(obj.zxx()) // zxx is good girl
    
    3.函数表达式
    var zxx = function () {
        return 'zxx is good girl'
    }
    console.log(zxx()) // zxx is good girl
 
    4.回调函数
    setInterval(function () {
        console.log('zxx is good girl')
    }, 1000)
 
    5.作为函数的返回值
    function zxx () {
        // 返回匿名函数
        return function () {
            return 'zxx'
        }
    }
    console.log(zxx()()) // zxx

匿名函数模仿块级作用域

    if (true) {
        var a = 12 // a为全局变量
    }
    console.log(a) // 12
 
    for (var i = 0; i < 3; i++) {
        // console.log(i)
    }
    console.log(i) // 3 for没有自己的作用域,所以当循环结束后i就成为全局变量
    if () {}for () {} 等没有自己的作用域。
    如果有,出了自己的作用域,
    声明的变量就会立即被销毁了。
    但可以通过匿名函数来模拟块级作用域:
 
    function fn () {
        (function () { // 这里是我们的块级作用域(私有作用域) 
            var zxx = 'good girl!' // 此变量在外部并未定义
            console.log(zxx) // good girl!
        })()
        console.log(zxx) // 报错Uncaught ReferenceError: zxx is not defined
    }
    fn()

习题一

function test(a, b, c, d){
    console.log(a + b + c + d);
}(1, 2, 3, 4);
// 不执行也不报错
 
 
==============
 
function test(){
    console.log(a + b + c + d);
}();
// 报错:Uncaught SyntaxError: Unexpected token )

习题二

function zxxFn (){
    var arr = [];
    for(var i = 0; i < 10; i ++){
        arr[i] = function (){
            console.log(i);
        }
    }
    return arr;
}
var zxx = zxxFn();
for(var j = 0; j < 10; j++){
    zxx[j]();
}

详解

zxxFn中由于for不是块级作用域,所以var i 变成 zxxFn的局部变量,每次新的i都会覆盖原来的,最终i=10。所以会输出10个10

习题三

function zxxFn(){
    var arr = [];
    for(var i = 0; i < 10; i ++){
        (function(j){
            arr[i] = function (){
            console.log(j + " ");
        }
        }(i))
    }
    return arr;
}
var zxx= zxxFn();
for(var j = 0; j < 10; j++){
    zxx[j]();
}

详解:

这题使用了立即执行函数,把zxxFn中的i当参数传给了,匿名函数的j,所以每次执行j的状态都会更新,所以会输出0 1 2 3 4 5 6 7 8 9

匿名函数的缺点

1. 匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。

2. 如果没有函数名,当函数需要引用自身时只能使用已经过期的 arguments.callee 引用, 比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。

3. 匿名函数省略了对于代码可读性 / 可理解性很重要的函数名。一个描述性的名称可以让代码不言自明。