匿名函数

在JavaScript这门脚本语言中,默认的定义函数格式如下:

function fn(a,b){
    console.log(a+b);
}

某些情况下定义函数时会省略掉函数名称如下:

function (a,b){
    console.log(a+b);
}

这样的函数就叫做匿名函数。

而众所周知,函数是通过函数名来调用的,当没有名字时该如何调用,用在什么地方呢?

 

调用方式

一:放进变量中
let myfn = function(a,b) {
console.log(a+b);
};
myfn(1,2);

这样一来,这个变量就相当于函数名,因此这个函数就等于是个普通函数了

 
二:直接执行

常用方式

  • (匿名函数)()
  • (匿名函数())
(function(a,b){
    console.log(a+b);
})(1,2)

(function(a,b){
    console.log(a+b);
}(1,2))
 
三:利用事件调用
let btn = document.getElementById("btn");
btn.onclick = function() {
    console.log("clicked!");
}
 
四:作为对象的方法调用
let myobj = {
    name:"yumefx",
    hello:function(){
        console.log("hello,"+this.name);
    }
};
myobj.hello();

这种方法与第一种方法赋予变量类似。

 
五:作为另一个函数的参数
function myfn(fn){
    fn();
}

myfn(function(){
    console.log("yes");
});

 

变量作用域

在ES6之前,当使用var声明变量时,只有全局作用域和函数作用域。

以函数声明为界,函数内部变量和外部变量是两个不同的作用域,内部声明的变量不可以在外部被访问,而内部可以调用和覆盖外部声明的所有变量,即使外层函数已经执行完毕(作用域嵌套)。

var tmp = new Date();
function f() {
    console.log(tmp);
    if (false){
        var tmp = "hello";
    }
}

f(); //undefined

这段代码中,if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是由于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。

所谓变量提升,就是将函数和变量的声明提升到当前作用域的最顶端,因此要想变量不是全局变量,那么就得用函数包起来。变量提升的结果就是声明的变量在声明前调用值为undefined而不会报错。

除此以外还有内存泄漏问题,例如用来计数的循环变量泄露为全局变量。

var s = "hello";
for (var i = 0; i < s.length; i++){
    console.log(s[i]);
}
console.log(i);

在ES6之后,有了一个新的变量声明符号let,可以声明块级作用域,只有在当前代码块内有效,并且无法在声明前调用(let不存在变量提升)。

a //undefined
{
    var a = 1;
    let b = 2;
}
a //1
b //ReferenceError: b is not defined.

另外,let不能在同一作用域内,重复声明同一个变量,例如不能在函数内部重新声明传入的参数。因此,匿名函数的闭包功能,其实就是在let出现前,将某些变量变成类似块级作用域的方式。

优点是可以防止内部变量污染外部变量,但要小心this指向、变量作用域、内存泄漏等问题。

 

this指向

var object = {
    name: "object",
    getName: function() {
        return function() {
            console.info(this.name)
        }
    }
}
object.getName()() //undefined

因为里面的闭包函数是在windows作用域下执行的,因此,this指向windows。

 

变量作用域

function outer() {
    var result = [];
    for (var i = 0; i < 10; i++){
        result[i] = function() {
            console.info(i)
        }
    }
    return result;
}

看起来似乎是输出等差数列0,1,2,3,4,…,9,实际上因为每个闭包函数访问变量i是outer执行环境下的变量i,随着循环的结束,i已经变成10了,所以最后打印出的是10,10,10,…,10

解决这个问题如下:

function outer() {
    var result = [];
    for (var i = 0; i < 10; i++){
        result[i] = function(num) {
            return function() {
                console.info(num)
            }
        }
    }
    return result;
}

这样访问的num,是上层函数执行环境的num,数组有10个函数对象,每个对象的执行环境下的number都不一样。

 

内存溢出

function showId() {
    var el = document.getElementById("app");
    el.onclick = function(){
        alert(el.id);
    }
}

上面这样写会导致闭包引用外层的el,当执行完alert后,el无法释放内存。修改如下:

function showId() {
    var el = document.getElementById("app");
    el.onclick = function(){
        alert(el.id);
    }
    el = null; //主动释放el
}

 

使用闭包解决递归调用问题

function factorial(num) {
    if(num <= 1) {
        return 1;
    }else{
        return num * factorial(num-1);
    }
}
var anotherFactorial = factorial;
factorial = null;
anotherFactorial(4);

这样写是有问题的,因为最后是return num* argument.callee(num-1),argument.callee指向当前执行函数,但是在严格模式下不能使用该属性也会报错。

借助闭包实现修改如下:

function factorial = (function f(num) {
    if(num <= 1) {
        return 1;
    }else{
        return num * factorial(num-1);
    }
})

这样实际起作用的是闭包函数f,而不是外面的函数factorial。

 


每个人都经历着受骗和伤痛,

最终掌握了在这条街道生活下去的本领。

《我的晃荡的青春》

——东野圭吾