一文详解JavaScript函数中的参数_第1页
一文详解JavaScript函数中的参数_第2页
一文详解JavaScript函数中的参数_第3页
一文详解JavaScript函数中的参数_第4页
一文详解JavaScript函数中的参数_第5页
已阅读5页,还剩9页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第一文详解JavaScript函数中的参数二、参数传递

函数允许我们将数据传递进去,通过传递的数据从而影响函数执行结果,使函数更灵活、复用性更强。

functionfoo(a,b){

console.log([a,b]);

foo(1,2);//输出[1,2]

这个例子中,a和b属于函数中的局部变量,只能在函数中访问。调用函数时,传递的数据会根据位置来匹配对应,分别赋值给a和b。

创建函数时,function函数名后面括号中设定的参数被称为形参;调用函数时,函数名后面括号中传入的参数被称为实参。上面例子中,a和b是形参,传入的1和2是实参。

因为形参是已声明的变量,所以不能再用let和const重复声明。

functionfoo(a,b){

leta=1;//报错,a已声明

constb=1;//报错,b已声明

}

JavaScript中所有函数传递都是按值传递的,不会按引用传递。所谓的值,就是指直接保存在变量上的值,如果把对象作为参数传递,那么这个值就是这个对象的引用,而不是对象本身。这里实际上是一个隐式的赋值过程,所以给函数传递参数时,相当于从一个变量赋值到另一个变量。

原始值:

functionadd(num){

returnnum+1;

letcount=5;

letresult=add(count);//此处参数传递的过程可以看作是num=count

console.log(count);//5

console.log(result);//6

引用值:

functionsetName(obj){

=小明

letperson={};

setName(person);//此处参数传递的过程可以看作是obj=person;

console.log(person);//{name:小明}

三、理解参数

JavaScript中的函数既不会检测参数的类型,也不会检测传入参数的个数。定义函数时设置两个形参,不意味着调用时必须传入两个参数。实际调用时不管是传了一个还是三个,甚至不传参数也不会报错。

所有函数(非箭头)中都有一个名为arguments的特殊的类数组对象(不是Array的实例),它保存着所有实参的副本,我们可以通过它按照数组的索引访问方式获取所有实参的值,也可以访问它的arguments.length属性来确定函数实际调用时传入的参数个数。

例如:

functionfoo(a,b){

console.log(arguments[0]);

console.log(arguments[1]);

console.log(arguments.length);

foo(10,20);//依次输出10、20、2

上面例子中,foo()函数的第一个参数是a,第二个参数是b,可以通过arguments[x]的方式来分别获取同样的值。因此,你甚至可以在声明函数时不设置形参。

functionfoo(){

console.log(arguments[0]);

console.log(arguments[1]);

foo(10,20);//依次输出10、20

由此可见,JavaScript函数的形参只是方便使用才写出来的。想传多少个参数都不会产生错误。

还有一个要注意的是,arguments可以跟形参一起使用,并且arguments对象中的值会和对应的形参保持同步。例如:

functionfoo(a){

arguments[0]++;

console.log(a);

foo(10);//输出11

//------------------------------------

functionfoo2(a){

a++;

console.log(arguments[0]);

foo2(10);//输出11

当修改arguments[0]或a的值时,另一个也被改变了。这并不意味着它们访问同一个内存地址,毕竟我们传入的是一个原始值。它们在内存中还是分开的,只是由于内部的机制使它们的值保持了同步。

另外,如果缺少传参,那这个形参的值就不会和arguments对象中的对应值进行同步。例如下面这个例子,只传了一个参数,那么arguments中只有一个实参值,这时候在函数中把arguments[1]设置为某个值,这个值并不会同步给第二个形参,例如:

functionfoo(a,b){

arguments[1]=2;

console.log(b);

foo(1);//输出undefined

这个例子中,形参b没有传入实参,它的值会默认为undefined。但如果:

foo(1,undefined);//输出2

手动传入undefined时,arguments数组中会出现一个值为undefined的元素,依然能和b的值进行同步。

严格模式下,arguments对象中的值和形参不会再同步,当然,如果传入的是引用值,它们依然会互相影响,但这只是引用值的特性而已。因此,在开发中最好不要依赖这种同步机制,也就是说不要同时使用形参和它在arguments对象中的对应值。

箭头函数中没有arguments

如果函数是使用箭头语法定义的,那么函数中是没有arguments对象的,只能通过定义的形参来访问。

letfoo=()={

console.log(arguments[0]);

}foo();//报错,arguments未定义

在某些情况可能会访问到arguments:

functionfn1(){

letfn2=()={

console.log(arguments[0]);

fn2();

}fn1(5);

但这个arguments,并不是箭头函数的,而是属于外部普通函数的,当箭头函数中访问arguments时,顺着作用域链找到了外部函数的arguments。

四、将对象属性用作实参

当一个函数包含的形参有多个时,调用函数就成了一种麻烦,因为你总是要保证传入的参数放在正确的位置上,有没有办法解决传参顺序的限制呢?

由于对象属性是无序的,通过属性名来确定对应的值。因此可以通过传入对象的方式,以对象中的属性作为真正的实参,这样参数的顺序就无关紧要了。

functionfoo(obj){

console.log(,obj.sex,obj.age);

foo({sex:男,age:18,name:小明});//小明男18

五、参数默认值

如果调用函数时缺少提供实参,那么形参默认值为undefined。

有时候我们想要设置特定的默认值,在ES6之前还不支持显式地设置默认值的时候,只能采用变通的方式:

functionsayHi(name){

name=name||everyone

console.log(Hello+name+!

sayHi();//输出Helloeveryone!

通过检查参数值的方式判断有没有赋值,上面的做法虽然简便,但缺点在于如果传入的实参对应布尔值为false,实参就不起作用了。需要更精确的话可以用if语句或者三元表达式,判断参数是否等于undefined,如果是则说明这个参数缺失:

//if语句判断

functionsayHi(name){

if(name===undefined){

name=everyone

console.log(Hello+name+!

//三元表达式判断

functionsayHi(name){

name=(name!==undefined)name:everyone

console.log(Hello+name+!

}

ES6就方便了许多,因为它支持了显式的设置默认值的方式,就像这样:

functionsayHi(name=everyone){//定义函数时,直接给形参赋值

console.log(Hello+name+!

sayHi();//输出Helloeveryone!

sayHi(Tony//输出HelloTony!

sayHi(undefined);//输出Helloeveryone!

这些结果表明了,它也是通过参数是否等于undefined来判定参数是否缺失的。

默认值不但可以是一个值,它还可以是任意合法的表达式,甚至是函数调用:

functionsayHi(name=every+one){

console.log(Hello+name+!

sayHi();//输出Helloeveryone!

//--------------------------------------

functionfoo(){

console.log(调用foo

returnTony

functionsayHi(name=foo()){

console.log(Hello+name+!

sayHi();//输出调用foo

//输出HelloTony!

sayHi(undefined);//输出调用foo

//输出HelloTony!

sayHi(John//输出HelloJohn!

可以看到,函数参数的默认值只有在函数调用时,参数的值缺失或者是undefined才会求值,不会在函数定义时求值。

参数默认值的位置

通常我们给参数设置默认值,是为了调用函数时可以适当省略参数的传入,这里要注意的是,有多个参数时,设置了默认值的参数如果不是放在尾部,实际上它是无法省略的。

functionfn(x=1,y){

console.log([x,y]);

fn();//输出[1,undefined]

fn(2);//输出[2,undefined]

fn(,2);//报错,语法错误(这里不支持像数组那样的空槽)

fn(undefined,2);//输出[1,2](那还不如传个1方便呢!)

上面例子中,给形参x设置的默认值就显得没有任何意义了。因此,设置默认值的参数放在尾部是最好的做法:

functionfn(x,y=2){

console.log([x,y]);

fn();//输出[undefined,2]

fn(1);//输出[1,2]

fn(1,1)//输出[1,1]

参数的省略问题

在多个参数设置了默认值的情况下,那么问题又来了,你并不能省略比较靠前的参数,而只给最后的一个参数传入实参。

functionfn(x,y=2,z=3){

console.log([x,y,z]);

fn(1,,10)//报错

前面我们知道,可以通过传入对象的这种方式去避免参数顺序的限制。那参数默认值如何实现呢?用||、if语句或者三元表达式去判断也是解决办法,但这样就显得有些落后了。接下来要讨论的是另外两种ES6中的全新方式。

参数默认值和Object.assign()结合使用

functionfn(obj={}){

letdefaultObj={

x:undefined,

y:2,

z:3

letresult=Object.assign(defaultObj,obj);

console.log([result.x,result.y,result.z]);

fn();//输出[undefined,2,3]

fn({x:1,z:10});//输出[1,2,10]

上面的例子中,在函数中定义了一个对象defaultObj,变通地利用其中的属性作为参数的默认值,然后利用Object.assagin()把传入的对象和默认对象进行合并,defaultObj中的属性会被obj的相同属性覆盖,obj中如果有其他属性会分配给defaultObj。这里用一个变量接收返回的合并对象。

同时形参obj也设置了默认值为一个空对象,防止函数调用时不传任何参数,因为这会导致Object.assign()接收的第二个参数是undefined,从而产生报错。

参数默认值和解构赋值结合使用

函数调用时,实参和形参的匹配实际上是一个隐式的赋值过程,所以,参数传递也可以进行解构赋值:

functionfn({x,y=2,z=3}){

console.log([x,y,z]);

fn({});//输出[undefined,2,3]

fn({x:1,z:10});//输出[1,2,10]

在这个例子中,使用的只是对象的解构赋值默认值,还没有使用函数参数的默认值。如果函数调用时不传任何参数,也会产生报错,因为这导致了参数初始化时解构赋值失败,相当于执行了{x,y=2,z=3}=undefined这样的代码。

同样的,你可以利用参数默认值的语法,给{x,y=2,z=3}设置一个默认的解构对象,使得不传参函数也能够顺利执行:

functionfn({x,y=2,z=3}={}){

console.log([x,y,z]);

fn();//输出[undefined,2,3]

这里出现了双重的默认值,可能有些绕,那么用一段伪代码来解释以上的参数初始化过程就是:

if(实参==={...}){//当fn({...});

{x,y=2,z=3}={...};

}elseif(实参===undefined){//当fn();

{x,y=2,z=3}={};

}

双重默认值有一点细节需要特别注意,就是解构赋值默认值和函数参数默认值的差别,看下面例子:

functionfn({x=1}={},{y}={y:2}){

console.log(x,y);

fn();//输出12

fn({x:10},{y:20});//输出1020

fn({},{});//1undefined

这个函数中,有两组参数采用了解构赋值的方式,看似x和y都设置了默认值,虽然是不同的两种形式,但显然不是任何情况下结果都相同的。当传入的参数是{}时,y并没有获取到默认值2,为什么会这样呢?结合前面的伪代码例子来看:

fn({x:10},{y:20});//初始化时:{x=1}={x:10},{y}={y:20}

fn({},{});//初始化时:{x=1}={},{y}={}

当传入的参数是{}时,函数参数没有缺失也不是undefined,所以函数参数默认值是不起作用的。同时{}里面也没有x和y的对应值,x得到的1是解构赋值默认值,而y由于没有设置解构赋值默认值,所以它默认是undefined。

参数默认值的作用域与暂时性死区

还有一个小细节,一旦有参数设置了默认值,那么它们会形成自己的作用域(包裹在(...)中),因此不能引用函数体中的变量:

functionfoo(a=b){

letb=1;

foo();//报错,b未定义

但这个作用域只是临时的,参数初始化完毕后,这个作用域就不存在了。

它也符合普通作用域的规则:

letb=2;

functionfoo(a=b){

letb=1;

returna;

foo();//2

上面例子中,存在一个全局变量b,那么形参a会获取到全局变量b的值。

当然,如果形参作用域中存在一个形参b的话,它优先获取到的是当前作用域的:

letb=2;

functionfoo(b=3,a=b){

returna;

foo();//3

给多个参数设置默认值,它们会按顺序初始化的,遵循暂时性死区的规则,即前面的参数不能引用后面的参数:

functionfoo(a=b,b=2){

returna+

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论