JavaScript-函数基础学习

JavaScript-函数基础学习

任何编程语言都有函数,函数封装了一组语句,用以完成某些特定的功能,本篇一起来学习一下JavaScript的函数。

函数定义有很多方式:函数声明,函数表达式,箭头函数,new Function,生成器函数等,本篇介绍最基础常用的,本篇内容:

  • 函数声明
    • 函数参数
    • arguments
    • 扩展表达式
    • 参数默认值
  • 函数表达式
  • 箭头函数
    • 箭头函数没有this
    • 箭头函数没有arguments

函数申明

JavaScript函数用function关键字来申明:

function functionName(parameter){
    //函数体
}

如下声明一个函数,函数名为hello

function hello(){
    console.log('world');
}
hello();

通过hello()可以调用该函数,本例将在控制台输出world
函数声明以后,可以多次调用。

函数参数

可以给函数添加参数,如下所示我们声明一个add函数,此函数接收两个整型参数,返回二者之和。

function add(num1, num2){
    return num1 + num2;
}
add(1, 2);

当参数是简单类型时如Number,Boolean等,传递给函数的是变量的拷贝,也就是说在函数内修改参数的值,不会改变原变量的值

function add(num1, num2){
    num1 = 2;
    return num1 + num2;//4
}
let num1 = 1;
console.log(num1);//在调用之前,num1为1
add(num1,2);
console.log(num1);//在调用之后,num1还是为1

可以看到,在函数里面修改了num1的值,但外部的变量num1并未改变。

如果传递的参数是引用类型如对象,情况则会有所不同:

function add(obj) {
    obj.num1 = 2;
    return obj.num1 + obj.num2;
}
let obj = { num1: 1, num2: 2 };
console.log(obj.num1); //在调用之前,obj.num1为1
add(obj);
console.log(obj.num1); //在调用之后,obj.num1为2

可以看到,在函数里面修改了参数obj的num1属性的值,实际改变了外部变量obj对象的属性num1的值,因为引用类型是按引用传递,二者会相互影响,任何一个改变都会影响另一个,但按引用传递有一种情况需要注意:

function add(obj) {
    obj = { num1:2, num2:3};
    return obj.num1 + obj.num2;
}
let obj = { num1: 1, num2: 2 };
console.log(obj.num1); //在调用之前,obj.num1为1
add(obj);
console.log(obj.num1); //在调用之后,obj.num1仍然为1

这里,在函数内部直接给参数obj赋值,通过结果我们可以看到并没有影响外部对象obj的值,因为你不能改变此对象的指向,你只能改变此对象的属性,重新赋值是不被允许的。

很多人在此讨论引用类型的变量在参数的传递时究竟是按值还是按引用传递,我觉得此处没必要纠结,我认为是按引用传递,但要记住在函数内部对对象重新赋值不被允许就行了。如果你有别的观点,请忽略我的。

隐藏的对象arguments

例:

function add(num1, num2){
    console.log(arguments.length);//2
    console.log(arguments[0]);//1
    console.log(arguments[1]);//2
}
add(1, 2);

可以通过arguments.length来获取参数的个数,arguments对象很像一个数组,但实际上不是数组

实际上,在声明函数时不加函数参数,同样也可以访问arguments对象

function add(){
    console.log(arguments.length);//2
    console.log(arguments[0]);//1
    console.log(arguments[1]);//2
}
add(1, 2);

另外要提的一点是,JavaScript里面没有函数的重载,即使参数个数不同也不行

function add(num1){
    return 10 + num1;
}

function add(num1, othernumber){
    return 20 + num1;
}

add(10);

此例将输出什么呢?答案是30,因为后面创建的函数add覆盖了前面的add

我们可以利用arguments来判断参数个数和类型,从而模仿重载:

function add(){
    if(arguments.length == 1){
        return arguments[0];
    } else (arguments.length == 2){
        return arguments[0] + arguments[1];
    }
}
console.log(add(1));//1
console.log(add(1, 2));//3

扩展表达式

ES6引进了扩展表达式(Rest Expression),通过三个点...来表示,扩展表达式接收函数参数,其类型是一个数组:

function add(...nums) {
    let sum = 0;
    for (let num of nums) {
        sum += num;
    }
    return sum;
}
console.log(add(1)); //1
console.log(add(1, 2)); //3
console.log(add(1, 2, 3)); //6

这里的nums是一个真正的数组,扩展表达式可以和普通参数一起使用,但必须放在最后面:

function add(firstnum, ...restnums) {
    let sum = 0;
    for (let num of restnums) {
        sum += num;
    }
    return firstnum + sum;
}
console.log(add(1)); //1,restnums为[]
console.log(add(1, 2)); //3,restnums为[2]
console.log(add(1, 2, 3)); //6,restnums为[2,3]

有了扩展表达式后,我们没必要再使用arguments了,因为arguments不是数组,所以不能使用数组方法,例如我们不能使用arguments.forEach(...),而且arguments包含的是所有的参数,而扩展表达式可以只收集部分参数,因此使用扩展表达式要优于arguments,这也是javascript-style-guide的建议。

参数默认值

可以给函数的参数指定默认值,在调用函数时,如果不给默认参数传值,则参数的值为默认值,如果给默认参数传值,则参数的值为实际传递的值,但默认参数必须列在参数的最后面:

function add(num1, num2 = 1){
    return num1 + num2;
}
add(1);//2,此时没有指定参数num2的值,num2为默认值1
add(1, 2);//3,此时指定了参数num2的值,num2为传递的值2

函数表达式

前面讲到的定义函数的方法,函数声明:

function functionName(parameter){
    //函数体
}
functionName();//调用

其实javascript里面还可以将函数赋值给一个变量,这种形式叫函数表达式:

let functionName = function(parameter){
    //函数体
}
functionName();//调用

这种写法是将一个匿名函数指定给了一个变量,调用方式同样也是通过functionName()

注意:
函数表达式只有在赋值之后,方能调用,而普通的函数声明在其创建前后都能被调用

foo();//函数声明前,调用foo,正常
function foo(){
    console.log('bar');
}
foo();//函数声明后,调用foo,正常
foo();//函数声明前,调用foo,抛出ReferenceError
let foo = function(){
    console.log('bar');
}
foo();//函数声明后,调用foo,正常

也可以将一个函数赋值给任意一个变量

function functionName(parameter){
    //函数体
}
let anotherfunctionName = functionName;

之后可以通过anotherfunctionName()来调用,与functionName()调用是一样的。

ES6箭头函数

ES6引入了箭头函数,写法比传统函数声明要简便,例如上面的add函数,可以改写成箭头函数:

let add = (num1, num2) => num1 + num2;

console.log(add(1, 2)); //3

箭头左侧用括号包起来的以逗号分隔的是参数,箭头右侧是表达式,如果表达式有多条语句,需要用大括号包起来:

let add = (num1, num2) => {
    let sum = num1 + num2;
    return sum;
};
console.log(add(1, 2)); //3

当箭头右侧只有一句表达式的时候,可以省略大括号,不必写return,这样写默认就返回了num1 + num2

let add = (num1, num2) => num1 + num2;

但如果你用大括号包起来,那么就必须写return,否则没有返回值,如下所示,将输出undefined

let add = (num1, num2) => {
    num1 + num2;
}
console.log(add(1, 2));//undefined

javascript-style-guide中就建议箭头函数右侧只有一条语句时不要写大括号,用隐式返回,否则使用大括号包裹,并使用return来返回

箭头函数没有this

既然箭头函数没有this,那么箭头函数的this在哪里?

先来看看普通函数的this,this与调用的对象有关,直接调用函数,this为undefined

function add(){
    console.log(this);//undefined
}
add();

通过对象调用函数:

let myObj = {
    num1:1,
    num2:2,
    add() {
        return this.num1 + this.num2;
    }
}
console.log(myObj.add());//3

此时add函数的this为其调用者myObj,故输出3

看一看箭头函数的this

let myObj = {
    num1: 1,
    num2: 2,
    add() {
        let sum = () => this.num1 + this.num2;
        return sum();
    }
}
console.log(myObj.add()); //3

由于箭头函数没有this,它的this实际上是上一层的this,本例箭头函数的this实际上是myObj

如果是普通的函数:

let myObj = {
    num1: 1,
    num2: 2,
    add() {
        let sum = function() {
            return this.num1 + this.num2;
        }
        return sum();
    }
}
console.log(myObj.add()); //NaN

由于普通的函数有this,本例中this取其默认值undefined,故this.num1this.num2都为undefinedundefined+undefined结果为NaN

箭头函数没有arguments

前面讲到函数声明,有个隐藏的arguments对象,但在箭头函数中是没有这个对象的,如果在其中使用arguments,将取自其上一层

function add(num1) {
    let sum = () => {
      arguments[0] + num1;//这里的arguments即为add的arguments
    };
    return sum();
}
console.log(add(1));//2
console.log(add(2));//4

总结

  1. 函数声明:function name(){},可以将函数赋值给任意一个变量:let anothername = name,之后可以通过anothername ()来调用
  2. 函数表达式:let name = function(){},不建议使用函数表达式,因为只有在赋值之后才能调用,而函数声明会被提升,在声明前后都可以调用。
  3. 函数参数:当传递的参数为简单类型时,传递的是变量的拷贝,当参数为引用类型时,传递的是变量的引用
  4. 隐藏的对象argumentsarguments类似数组,但实际是一个对象,可以通过arguments访问函数参数
  5. 扩展表达式:扩展表达式用来接收传递过来的参数,数据类型为数组,扩展表达式相比于arguments有几点优势,所以最好使用扩展表达式而不是arguments
  6. 箭头函数:剪头函数由ES6引入,写法比传统函数声明方式更简洁
  7. 箭头函数没有this,可以这样理解:箭头函数的this是其上一层的this
  8. 箭头函数没有arguments,可以这样理解:箭头函数的arguments是其上一层的arguments

参考

1.MDN-函数:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions
2.关于按值还是按引用传递:https://hackernoon.com/grasp-by-value-and-by-reference-in-javascript-7ed75efa1293

写作时间:2020-05-31

你可能感兴趣的:(javascript)