javascript进阶知识10-函数的定义与参数

199 阅读4分钟

哈哈终于要开始函数和对象了~~~

JS很奇怪,函数是对象,对象又是由函数创造的。所以函数和对象是息息相关的。

1 函数的创建以及箭头函数

通常使用函数声明的方式定义:

function sum(num1,num2) {
    return num1+num2;
}

还可以使用函数表达式:

let sum = function(num1,num2) {
    return num1+num2
}

函数是对象,还可以使用Function构造函数创建:(最后一个参数会被当做函数体,而之前的参数都是新函数的参数)

let sum = new Function('num1','num2','return num1+num2'); // 不推荐

箭头函数: 箭头函数是ES6新增的。箭头函数实例化的函数对象与正式的函数表达式创建的函数对象行为是相同的。任何可以使用函数表达式的地方,都可以使用箭头函数。

let sum = (a,b) => {
    return a+b
}

如果只有一个参数,可以简写不要括号:

let double = x => {return 2*x}

如果函数体内只有一条返回语句,那么也可以省略大括号(省略大括号就不能用return):

let double = x => 2*x;

无效的写法
let double = x => return 2*x;

箭头函数虽然语法简洁,但是也有一些地方不适合用箭头函数。比如箭头函数没有arguments、super和new.target,箭头函数也不能作为构造函数,因此也就没有prototype属性。而且箭头函数的this指向也和普通函数不同。

函数的参数

定义函数时要接收两个参数,但是这不意味着你要传两个参数。你可以传一个、三个,甚至0个,都不会报错。这是因为函数的参数在内部表现为一个数组。函数被调用时总会接收一个数组,但是函数并不关心数组中包含什么。

每一个函数初始化时都有一个属性,arguments(箭头函数没有),arguments对象是一个类数组,因此想看参数个数,就可以使用arguments.length查看。

function howManyArgs() {
    console.log(arguments.length);
}

howManyArgs('str', 45); //2
howManyArgs();          //0
howManyArgs(12);        //1

arguments对象的值始终会与对应的命名参数同步:

function doAdd(num1, num2) {
    arguments[1] = 10;
    console.log(arguments[0] + num2);
}
doAdd(1, 1) // 11

这个doAdd()函数把第二个参数的值重写为10.因为arguments对象的值会自动同步到对应的命名参数,所以修改arguments[1]也会修改num2的值,因此两者的值都是10.但是这不意味着他们都访问同一个内存地址,他们在内存中还是分开的,只不过会保持同步而已。 此外,如果只传了一个参数,但是吧arguments[1]设置为某个值,那么这个值并不会反映到第二个命名参数。这是因为arguments对象的长度是根据传入的参数个数,而非定义函数时给出的命名参数个数确定的。

箭头函数的参数

如果函数是用箭头函数定义的,那么传给函数的参数将不能使用arguments关键字访问,而只能通过定义的命名参数访问。 不过,虽然箭头函数没有arguments对象,但是可以在包装函数中把它提供给箭头函数。

function foo() {
    let bar = () => {
        console.log(arguments[0])
    }
    bar(); // 5
}

foo(5);

注意: JS中的所有参数都按值传递的。不可能按引用传递参数。如果把对象作为参数传递,那么传递的值就是这个对象的引用。

function change(obj) {
    obj.name = 'ly';
    obj.age = 15;
    obj = {
        name: 'ss'
    }
    console.log(obj); // { name: 'ss' }
}

let obj = {
    name: 'hh'
}
change(obj);
console.log(obj); //{ name: 'ly', age: 15 }

默认参数

ES6支持显示地定义默认参数了。

function makeKing(name = 'herry') {
    return `King ${name} VIII`;
}

console.log(makeKing()); //King herry VIII
console.log(makeKing('Louis')); //King Louis VIII

给参数传递undefined相当于没有传值,不过这样可以利用多个独立的默认值:

function makeKing(name = 'herry', numerals = 'VIII') {
    return `King ${name} ${numerals}`;
}

console.log(makeKing()); //King herry VIII
console.log(makeKing('Louis')); //King Louis VIII
console.log(makeKing(undefined, 'VI')); // King herry VI

在使用默认参数的时候,arguments对象的值也不反映参数的默认值,它只反映传给函数的参数。

箭头函数也可以使用默认参数,但是使用默认参数就不可以省略括号了。

let makeKing = (name='Henry') => `King ${name}`;
console.log(makeKing()); //King Henry

默认参数作用域

函数的参数的作用域都是属于函数作用域,不属于全局作用域。

给参数定义默认值实际上跟使用let关键字顺序声明一样。

function makeKing() {
    let name = 'Henrry';
    let numerals = 'VIII';
    return `King ${name} ${numerals}`;
}

同时默认参数还存在暂时性死区,即前面的参数不能引用后面的定义的,但是后面的参数可以引用前面定义的。

扩展操作符与参数

在给函数传参时,有时候不需要传一个数组,而是分别传入数组的元素。比如定义一个这样的函数:

let values = [1, 2, 3, 4];

function getSum() {
    let sum = 0;
    for (let i = 0; i < arguments.length; i++) {
        sum += arguments[i]
    }
    return sum;
}

这个函数希望累加,这样就得把传递的数组拆分。那么就可以使用扩展操作符。

console.log(getSum(...values)) //10

同时也可以使用apply方法

console.log(getSum.apply(null,values)) //10

因为数组的长度已知,所以在使用扩展操作符传参的时候,并不妨碍在其前面或后面在传其他的值,包括使用扩展操作符。

console.log(getSum(-1,...values)) //9
console.log(getSum(...values,5)); //15
console.log(getSum(...values,...[5,6,7])) //28

同时,也可以使用扩展操作符把不同长度的独立参数组合成一个数组。这有点类似arguments对象的构造机制,只不过收集参数的结果会得到一个Array实例

function getSum(...values) {
    return values.reduce((x,y) => x+y,0)
}

console.log(getSum(1,2,3)); // 6

收集参数的前面如果还有命名参数,则只会收集其余的参数;如果没有则会得到空数组。因为收集参数的结果可变,所以只能把它作为最后一个参数:

//不可以
function getProduct(...values,lastValue) {}

//可以
function ignoreFirst(firstValue,...values) {
    console.log(values);
}

ignoreFirst(); //[]
ignoreFirst(1); //[]
ignoreFirst(1,2); //[2]
ignoreFirst(1,2,3); //[2,3]

箭头函数也支持收集参数的定义方式,因此也可以实现与使用arguments一样的逻辑。

另外,使用收集参数并不影响arguments对象,它仍然反映调用时传给函数的参数:

function getSum(...values) {
    console.log(arguments.length);  //3
    console.log(arguments) //[1,2,3]
    console.log(values);  //[1,2,3]
} 
console.log(getSum(1,2,3));