函数

基本概念

  • 在JS中,函数实际上是对象,是Function类型的实例。

  • 函数名是指向函数对象的指针。
function sum(num1, num2){
    return num1 + num2;
}
// 检查sum是否是Function类型的实例
console.log(sum instanceof Function); // true

let anotherSum = sum; // 指向同一个函数

console.log(anotherSum(1,2));   // 3
  • 函数将若干条语句封装起来,可以提高代码的可复用性。

创建函数

函数声明

语法

function funcName(params){
    // do something...
    return something;
}

函数表达式

语法

let funcName = function(params){
    // do something...
    return something;
}

因为函数本质上是对象,所以可以直接赋值给一个变量。这个变量将指向这个函数,调用函数时直接使用变量名即可。

箭头函数

语法

let funcName = (params)=>{
    // do something...
    return something;
}

:箭头函数是ES6新加入的语法。

构造函数

语法

let funcName = new Function("param1","param2",...,"body");

构造函数接收任意多个字符串参数,最后一个是函数体,前面的是函数参数。

:这种写法不推荐,因为作为JS语句,它首先会被当做常规代码解析,然后再通过Function构造函数解析形成函数对象,性能较差。


箭头函数

箭头函数是ES6新增的语法。任何可以使用函数表达式的地方,都可以使用箭头函数。

语法格式

  1. 如果没有参数,要写括号:
let getRandom = () => {return Math.random();};
  1. 如果参数只有一个,可以不写括号:
let double = x => {return x*2; };
  1. 如果有多个参数,要写括号:
let sum = (x, y) => {return x + y; };
  1. 如果函数体有多条语句,则需要大括号:
let division = (a, b)=>{
    if(b === 0){
        console.error("除数不能为零");
    }else{
        return a/b;
    }
}
  1. 如果函数体只有一条语句,可以不写大括号,此时会隐式返回这行代码的值:
let double = x => x*2;

特点

  • 箭头函数语法简洁。

  • 箭头函数不能使用argumentssuper,也不能用作构造函数,且箭头函数没有prototype属性。


参数列表

JS的函数参数和大多数其他语言不同,JS中的函数参数定义是无关紧要的。

假如定义函数时预设有两个参数,而调用时传入了0个、1个或者3个参数,解释器都不会报错。

原因是使用function关键字定义的函数,可以在函数内部访问arguments对象,从中获取传入的参数。

function testArgs(){
    console.log(arguments);
}

testArgs(1,2,3,4,5,6);

testArgs(1,"Hello",true);

arguments是一个类数组对象,它并不是Array的实例对象,并不是真正的数组,但是它具有类似数组的一些操作,比如可以像数组一样通过[]访问元素的值,可以通过length获取元素个数。

可以通过arguments[0]访问到传入的第一个参数,通过arguments.length获取传入多少个参数。

箭头函数的参数

:箭头函数不能使用arguments,所以箭头函数需要多少参数需要在定义函数时就写明。

不存在函数重载

在其他语言比如C++Java中,存在函数重载,即可以存在多个函数名相同的函数,只要它们的参数个数、参数类型不一样。在调用函数的时候,会根据传入的参数数据类型,匹配上对应的函数。

显然,在JS中,函数的参数列表是随意的(参数个数与参数数据类型都是随意的),也就不存在函数重载了。

:在JS中,如果有多个函数名相同的函数定义,只有最后一个是有效的,前面的定义都将被覆盖。

默认参数值

// 返回一个由随机数组成的数组,默认长度为5
function getRandomArray(length = 5){
    const arr = new Array();
    for(let i=0;i<length;i++){
        arr.push(Math.floor(Math.random()*100));
    }
    return arr;
}

const arr1 = getRandomArray();
console.log(arr1);
//[76, 86, 60, 21, 37]

const arr2 = getRandomArray(10);
console.log(arr2);
//[18, 98, 76, 43, 77, 24, 44, 74, 58, 3]
  • 在函数参数后面使用=就可以为其设置默认值。

函数声明和函数表达式

  • 函数声明指通过function functionName(){}来得到一个函数.

  • 函数表达式是指通过function(){}创建一个匿名函数,再讲这个匿名函数赋值给一个变量functionName.

区别

函数声明提升

所有函数声明在JS引擎执行代码之前会被提升到代码顶部,即以下两段代码是等价的。

console.log(sum(1,2));    // 不会报错,因为sum会被提升到顶部
function sum(x,y){
    return x+y;
}
function sum(x,y){
    return x+y;
}
console.log(sum(1,2));

而函数表达式不会被提升:

console.log(sum(1,2));    // 报错
let sum = function(x,y){
    return x+y;
}

sum作为一个变量:

  • 如果使用var声明会被提升,但是定义不会被提升,undefined无法被调用;

  • 如果使用let则声明和定义都不会被提升,一个未声明的变量显然无法被调用。


函数作为值

函数名是一个变量,因此函数可以作为函数参数传递给另一个函数,并且函数也可以作为一个函数的返回值

回调函数

如果当前无法决定调用哪个函数,可以先把函数作为参数传递,等到可以做出明确的选择时,再回过头来调用这个函数。这种情况下作为参数传递的函数就被叫做回调函数(callback).

典型应用

向后端接口发送网络请求时,一般会附带上两个回调函数,一个success函数,一个error函数。在发送网络请求时,是无法确定请求是否发送成功的。只有拿到了返回结果,才能确定执行success函数或error函数。


闭包

闭包是指引用了另一个函数作用域中变量的函数。通常在嵌套函数中实现。

// 获取累加器
function getAccumulator(){
    let num = 0;    // 内部属性
    return function(){    // 返回一个函数
        return num++;
    }
}

// 调用函数获取累加器
let add = getAccumulator();
console.log(add());    // 0
console.log(add());    // 1
console.log(add());    // 2    
console.log(add());    // 3
console.log(add());    // 4

在函数getAccumulator的作用域中,有一个num属性,而返回的函数中,引用了这个num. 即返回的这个函数引用了另一个函数作用域中的变量.

num这个变量和add函数绑定在了一起,当getAccumulator执行结束后也不会被回收,并且无法被直接访问。


立即调用的函数

立即调用的函数表达式(IIFE, Immediately Invoked Function Expression)

ES5没有块级作用域,可以使用立即调用的函数表达式来模拟块级作用域。

var funcName = function(){...};
funcName();

使用立即调用的函数表达式可以省略一个不必要的函数名。

(function(){
...
})();

ES6之后,IIFE就没有那么必要了,因为可以很方便地使用块级作用域。