基本概念
JavaScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,无论你在函数创建的时候声明了多少形参,都可以传入任意数量的参数(没有传递值的命名参数将自动被赋予undefined值)。之所以会这样,原因是JavaScript中的参数在内部是用一个数组来表示。
Arguments
在函数体内可以通过arguments对象来访问这个由参数组成的数组,但arguments并不是Array的实例。它只是可以使用方括号语法访问它的每一个元素,也可以使用length属性来确定传递进来多少个参数(不是创建函数时形参的个数),其他数组的方法,arguments并不能直接使用,但也可以把arguments转换成真正的数组。
例子1:
function demo(n1, n2) {
var arr = [];
var len = arguments.length;
for (var i = 0; i < len; i++) {
if (typeof arguments[i] == "number") {
arr.push(arguments[i]);
}
}
console.log(len);
console.log(arr);
}
demo(); //输出:0,[]
demo(1); //输出:1,[1]
demo(1, 2); //输出:2,[1,2]
demo(1, 2, 3); //输出:3,[1,2,3]
例子2:
function demo(n1, n2) {
//把arguments变成Array的实例
var args = Array.prototype.slice.call(arguments);
var newArgs = args.map(function(v) {
return v + 1;
})
console.log(Array.isArray(arguments)); //输出:false
console.log(Array.isArray(args)); //输出:true
console.log(newArgs); //输出:[2,3,4]
}
demo(1, 2, 3);
在ES5非严格模式下,命名参数的变化会动态更新到arguments对象中,arguments值的变化也会更新到对应的命名参数上。没有传递值的命名参数如果在函数体内部进行赋值,不会更新到arguments对象中,反之对arguments赋值也不会同步更新到对应的命名参数上。
例子:
function demo1(n1, n2) {
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
console.log(arguments[1]); //输出:undefined
console.log(n2); //输出:undefined
n1 = 3;
n2 = 4;
console.log(arguments[0]); //输出:3
console.log(n1); //输出:3
console.log(arguments[1]); //输出:undefined
console.log(n2); //输出:4
}
demo1(1);
function demo2(n1, n2) {
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
console.log(arguments[1]); //输出:undefined
console.log(n2); //输出:undefined
arguments[0] = 3;
arguments[1] = 4;
console.log(arguments[0]); //输出:3
console.log(n1); //输出:3
console.log(arguments[1]); //输出:4
console.log(n2); //输出:undefined
}
demo2(1);
然而在ES5的严格模式下,取消了这种命名参数与arguments对象同步更新的现象。
例子:
function demo1(n1, n2) {
"use strict"
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
n1 = 3;
console.log(arguments[0]); //输出:1
console.log(n1); //输出:3
}
demo1(1);
function demo2(n1, n2) {
"use strict"
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
arguments[0] = 3;
console.log(arguments[0]); //输出:3
console.log(n1); //输出:1
}
demo2(1);
arguments对象还有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。一般在阶乘函数中会用到这个属性。
例子:
function fn(n) {
if (n > 1) {
return n * arguments.callee(n - 1);
} else {
return 1;
}
}
fn(3);
//输出:6
箭头函数没有arguments的绑定,箭头函数中的arguments的值由最近一层非箭头函数决定。
例子:
function demo(n1, n2) {
(() => {
console.log(arguments[0]);
})();
}
demo(1);
//输出:1
ES6中对函数参数的扩展
参数默认值
ES6允许为任意参数指定初始值。
例子:
let demo1 = (str1, str2 = "world") => {
console.log(str1 + " " + str2)
}
demo1(); //输出:undefined world
demo1("hello"); //输出:hello world
demo1("hello", "China"); //输出:hello China
demo1("hello", ""); //输出:hello
demo1("hello", undefined); //输出:hello world
let demo2 = (str1, str2 = "B", str3) => {
console.log([str1, str2, str3]);
}
demo2(); //输出:[undefined, "B", undefined]
demo2("A"); //输出:["A", "B", undefined]
demo2("A", undefined); //输出:["A", "B", undefined]
demo2("A", undefined, "C"); //输出:["A", "B", "C"]
demo2("A", , "C"); //输出:报错
*通过上面的两个例子可以发现:只有当不为第二个参数传入值或主动为第二个参数传入undefined时才会使用str2的默认值。
参数的默认值除了可以是原始值,也可以是变量、表达式或函数。
例子:
参数默认值为变量
let n = 1;
let fn = (a, b = n) => {
console.log(a + b);
}
fn(1);
//输出:2
参数默认值为表达式
let n = 1;
let fn = (a, b = n + 1) => {
console.log(a + b);
}
fn(1);
//输出:3
参数默认值为函数执行的值
let n = () => 1;
let fn = (a, b = n()) => {
console.log(a + b);
}
fn(1);
//输出:2
*通过上面的三个例子可以发现:只有当调用函数fn且不传入第二个参数的时候,第二个参数才会进行求值。
默认参数的临时死区
一旦为参数设置了默认值,当调用函数时,函数的参数会被添加到一个专属于函数参数的临时死区(与let的行为类似),其与函数体的作用域是各自独立的。等到参数初始化结束,这个临时死区就会消失。
例子1:
let n = 1;
let fn = (a, b = a + n) => {
console.log(a + b);
}
fn(1, 1);
//输出:2
*调用 fn(1, 1) 相当于在引擎的背后做了如下事情
{
let a = 1;
let b = 1;
}
例子2:
let n = 1;
let fn = (a, b = a + n) => {
console.log(a + b);
}
fn(1);
//输出:3
*调用 fn(1) 相当于在引擎的背后做了如下事情
{
let a = 1;
let b=1+1;
}
注意区分下面两种情况:
例子1:
let n = 1;
let fn = (a, b = n) => {
let b = 9;
console.log(a + b);
}
fn(1);
//输出:报错
上例中,参数变量b是默认声明的,在函数体中,不能用let或const再次声明(var可以),否则会报错。
例子2:
let n = 1;
let fn = (a, b = n) => {
let n = 9;
console.log(a + b);
}
fn(1);
//输出:2
上例中,函数fn调用时,参数a、b被添加到一个专属于函数参数的临时死区,这个死区里面,变量n没有定义,所以指向外层的全局变量n。而函数体内部的局部变量n与临时死区的变量n各自独立,影响不到默认值n。
如果此时,全局变量n不存在,就会报错。
例子:
let fn = (a, b = n) => {
let n = 9;
console.log(a + b);
}
fn(1);
//输出:报错
上例中,如果同时存在全局变量n和参数n,默认值变量n会指向第一个参数 n。
例子:
let n = 1;
let fn = (n, b = n) => {
console.log(n + b);
}
fn(2);
//输出:4
调用函数fn时,参数形成一个单独的临时死区。在这个死区里面,默认值变量n指向第一个参数n,而不是全局变量n。
如果参数n与b交换位置,当b初始化时,n尚未初始化,所以会导致错误。
例子:
let n = 1;
let fn = (b = n, n) => {
console.log(n + b);
}
fn();
//输出:报错
默认参数值对arguments对象的影响
在ES6中,如果函数使用了默认参数值,则无论是否定义了严格模式,arguments对象的行为都将与ES5严格模式下保持一致。默认参数值的存在使得arguments对象保持与命名参数的分离,我们随时都可以通过arguments对象将参数恢复为初始值。
例子:
function demo1(n1, n2 = 2) {
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
n1 = 3;
console.log(arguments[0]); //输出:1
console.log(n1); //输出:3
}
demo1(1);
function demo2(n1, n2 = 2) {
console.log(arguments[0]); //输出:1
console.log(n1); //输出:1
arguments[0] = 3;
console.log(arguments[0]); //输出:3
console.log(n1); //输出:1
}
demo2(1);
不定参数
在函数的命名参数前添加三个点(…)就表明这是一个不定参数。不定参数是一个数组,会将命名参数后面的多余参数放入数组中。不定参数之后不能再有其他参数。
例子:
let fn = (obj, ...items) => {
let result = {};
for (let i = 0; i < items.length; i++) {
result[obj + "_" + i] = items[i];
}
console.log(result);
}
fn("name", "Tom", "Amy");
//输出:{name_0: "Tom", name_1: "Amy"}
不定参数与arguments对象的区别
1、arguments对象不是数组,而是一个类数组对象。所以为了使用数组的方法,必须使用Array.prototype.slice.call先将其转为数组;不定参数是一个真正的数组,数组特有的方法都可以使用。
例子:
((...items) => {
console.log(Array.isArray(items));
})();
//输出:true
2、arguments对象包含所有传入函数的参数;不定参数包含自它之后的所有参数。
例子:
(function(n1, n2, ...nx) {
console.log(arguments); //输出:[1, 2, 3, 4, 5]
console.log(nx); //输出:[3, 4, 5]
})(1, 2, 3, 4, 5)
解构参数
解构参数需要使用对象或数组解构模式代替命名参数。
有关于对象和数组的解构,可以参考下面的手记内容:
例子:
以数组字面量的语法进行解构赋值
(function([n1, n2]) {
console.log(n1 + n2);
})([1, 1]);
//输出:2
以对象字面量的语法进行解构赋值
(function(name, {
age,
job
}) {
console.log(age);
})("Tom", {
age: 19
})
//输出:19
解构参数有一点需要特别注意:在调用函数时,如果不提供以供解构的参数会导致错误。
例子:
调用函数时提供以供解构的对象字面量,不会报错
(function(name, {
age,
job
}) {
console.log(age);
})("Tom", {})
//输出:undefined
调用函数时没有提供以供解构的对象字面量,报错
(function(name, {
age,
job
}) {
console.log(age);
})("Tom")
//输出:报错
我们可以像为命名参数提供默认值那样,为解构参数提供默认值来解决这个问题。
例子:
(function(name, {
age,
job
} = {}) {
console.log(age);
})("Tom")
//输出:undefined
*上例中,我们将解构参数{age,job}看成一个命名参数,并为它提供默认值{}。这样即使在调用函数时,没有提供第二个参数,也不会报错。
参数默认值与解构参数默认值相结合
注意区分下面两种写法:
例子1:
//解构内部的变量默认值为{x = 0,y = 0}
//解构作为参数的默认值为{}
let fn = ({
x = 0,
y = 0
} = {}) => {
console.log([x, y])
}
fn(); //输出:[0, 0]
fn({}); //输出:[0, 0]
fn({
x: 1,
y: 1
}); //输出:[1, 1]
fn({
y: 1
}); //输出:[0, 1]
例子2:
//解构内部的变量默认值为{x ,y}
//解构作为参数的默认值为{x = 0,y = 0}
let fn = ({
x,
y
} = {
x: 0,
y: 0
}) => {
console.log([x, y])
}
fn(); //输出:[0, 0]
fn({}); //输出:[undefined, undefined]
fn({
x: 1,
y: 1
}); //输出:[1, 1]
fn({
y: 1
}); //输出:[undefined, 1]
如有错误,欢迎指正,本人不胜感激。