首页 > 编程语言 >JavaScript函数传参原理详解——值传递还是引用传递

JavaScript函数传参原理详解——值传递还是引用传递

时间:2023-05-29 09:33:20浏览次数:63  
标签:传参 形参 JavaScript 传递 引用 类型 实参 example

讨论JavaScript的传参原理之前,我们先来看一段曾经让笔者困惑了一段时间的代码

var testA=1;
var testB={};
function testNumber(example){
    example=2;
}
 
function testObj(example) {
    example.test=1;
}
 
testNumber(testA);
testObj(testB);
console.log(testA);//输出1
console.log(testB);//输出{test:1}

上述代码展示了一个比较纠结的问题:传入一个变量到函数中,函数对这个变量进行修改,到底会不会影响到原变量?从上面的代码我们可以发现,两者都可能出现。那到底是为什么呢?

解答这个问题之前,我们先来了解一下编程语言中函数传参的常用方式

穿插科普——实参和形参
所谓形参,是指我们定义函数的时候,函数定义的参数,例如上述代码中,testNumber函数定义中,example就是形参。

所谓实参,是指我们调用函数的时候,实际传入的值,例如上述代码中,testNumber(testA),此时testA为实参。

穿插科普——值类型和引用类型

(1)值类型:

1、占用空间固定,保存在栈中(当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;栈中存储的是基础变量以及一些对象的引用变量,基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。)

2、保存与复制的是值本身

 

3、使用typeof检测数据的类型

 

4、基本类型数据是值类型

(2)引用类型:

1、占用空间不固定,保存在堆中(当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。)

 

2、保存与复制的是指向对象的一个指针

 

3、使用instanceof检测数据类型

 

4、使用new()方法构造出的对象是引用型

 

按值传递
按值传递是一种比较容易理解又使用比较广泛的传参方式,这种方式在传参的时候,在内存中会直接把实参的值复制一份再把副本传递给形参,对于形参的修改并不会影响到实参

按引用传递
按引用传递相对来说比较难理解,如果函数使用按引用传递,那么形参将会直接接收实参的引用,而不经过复制,那么此时对于形参的修改则会影响到实参。难以理解?看图(注意,图示仅为举例用于说明按引用传递,事实上图示的情况在js中并不会出现,具体原因下文会有说明)

 

由于在js中,引用类型在内存中分两部分存放,实际的值存放在堆中,在栈中会存放引用类型位于堆中的地址,而我们平时操作的,都是通过栈中的地址对对象进行操作的,那么如果使用按引用传递,就意味着操作的是同一个地址,对于形参的修改就会影响到实参

js中的传参策略
那么按照上面的分析,可能会有人认为,在js中,对于值类型是按值传递,对于引用类型是按引用传递,然而,这是错误的。事实上,在js中,不管对于值类型还是引用类类型,都是按值传递的,区别在于,对于值类型,传参发生时,复制的是类型本身的值,而对于引用类型,复制的是类型的地址。我们来看下面这段代码,可以用来否定引用类型是按引用传参这个观点

var testC={};
function testObject(example){
    example={b:1};
}
testObject(testC)
console.log(testC);//输出{},实参并没有改变

通过上面的代码我们可以看出,如果是按引用传参,那么直接修改形参,是会对实参造成影响的,但是我们发现事实上并没有,为了方便理解,下面给出JavaScript中值类型和引用类型进行传参时在内存中的实际复制情况

值类型

 

 

引用类型

 

对于js中的变量,值类型存放在栈中,引用类型的地址存放在栈中,对应的值存放在堆中。当传参发生的时候,值类型会直接将栈中的值进行复制,形参和实参此时实际上是两个完全不相干的变量。对于引用类型,传参发生时,会将实参变量位于栈中的地址进行复制,此时栈中会有两个指向同一个堆地址的指针。

啰嗦一下对比testObj和testObject两个函数的不同效果
我们回头看一下上面举例的两个函数,都是直接对形参进行修改,为什么一个影响到了实参,而另一个却完全没有影响呢?为了方便对比,我们把它们放到一起

对比testObj和testObject两个函数的不同效果

我们回头看一下上面举例的两个函数,都是直接对形参进行修改,为什么一个影响到了实参,而另一个却完全没有影响呢?为了方便对比,我们把它们放到一起

var testB={};
var testC={};
function testObject(example){
    example={b:1};
}
 
function testObj(example) {
    example.test=1;
}
 
 
testObject(testC);
testObj(testB);
console.log(testC);//输出{}
console.log(testB);//输出{test:1}

事实上,由于对引用对象的地址复制,形参和实参之间还是存在关联的,地址指向了同一个对象,也就是说我们调用testObj操作形参时,对应操作的对象也是实参。但是调用testObject时,就是另一种状况了,当我们直接将形参替换成另一个值,在内存中会形成下图的情况

 此时,形参会指向堆中的另一个值,形参和实参从此彻底分道扬镳,无论怎么修改,都不会互相影响了

由上例子我们可总结:

  JS的函数参数传递是按值传递, 只不过这里的值是指栈区的值。

  无论形参是值类型还是引用类型的值,在函数里都会复制出一份新的栈区值指向它们,

  如果形参是值类型的,新复制出来的栈区值是自己本身的值,此时无论如何修改形参的值都不会影响到实参。

  如果形参是引用类型,新复制出来的栈区值是指向堆中具体的某一个对象,

    1、当传入形参的对象是原先就有的,那么形参与实参都共同指向该对象,此时修改形参的值就会影响到原先对象的值。

    2、当传入形参的对象是新的对象,那么形参指向该新对象,此时无论如何修改形参的值都不会影响到实参。

 

参考链接: JavaScript函数传参原理详解——值传递还是引用传递

标签:传参,形参,JavaScript,传递,引用,类型,实参,example
From: https://www.cnblogs.com/caihongmin/p/17439434.html

相关文章

  • 用JavaScript求1000以内的质数
    varprimes=[2];//2是质数,先将其加入质数数组中for(vari=3;i<=1000;i++){varisPrime=true;//假设i是质数for(varj=0;j<primes.length&&primes[j]<=Math.sqrt(i);j++){if(i%primes[j]===0){isPrime=false;//如果i可......
  • this in Javascript
    Whatis this?InJavaScript,the this keywordreferstoan object.Which objectdependsonhow this isbeinginvoked(usedorcalled).The this keywordreferstodifferentobjectsdependingonhowitisused:Inanobjectmethod, this refersto......
  • 数组指针、二级指针传参
    voidtest(int**p){}//二级指针接受intmain(){ inta=0; int*p=&a; int**pp=&p; int*arr[10]={0}; test(pp);//二级指针传参 test(&p);//一级指针的地址 test(arr);//指针数组,存放指针地址的数组 return0;}//voidtest(intarr[][5])//{}//arr[][]arr[3][]错误......
  • JavaScript中 == 和 === 的区别
    1.概念上JS中==是相同的意思,===代码严格相同 (1)操作数1==操作数2:也就是进行双等号比较时,先检查两个操作数的数据类型,如果相同,就进行===的比较,如果不同,则进行一次类型转换,转为相同类型后再进行比较比较过程:a)如果两个值类型相同,再进行三个等号的比较b)如果两个值类......
  • 函数之传参
    一、参数的两大分类1、形式参数 在'函数定义阶段'括号内依次写入的变量名就叫形式参数,简称"形参"defindex(a,b,c,d,e):pass#a,b就称之为是函数的形参2、实际参数 在'函数调用阶段'括号内依次传入的变量值就叫实际参数,简称"实参"index(1,2,3) #1,2,3就称之为是函......
  • 函数传递二维数组方法
    二维数组这样定义的时候:intx[n][m]感觉就不再是一个真正意义上的数组了,因为没有办法通过头指针进行访问,传递的时候编译器报错没有这个函数,直接找不到了。一种新的定义方式int**x=newint*[n];for(inti=0;i<n;++i)x[i]=newint[m];这样的话,就按定义一维数组的方......
  • Javascript 指南:条件语句
    if/elseif/else语句是程序如何以编程方式处理是/否问题。如果第一个条件的计算结果为true,则程序将运行第一个代码块。否则,它将运行else块。让天气=“下雨”;如果(天气===“下雨”){控制台。log("今天别忘了带伞!");}否则{控制台。日志(“今天可能会很好”!);}输出:Don......
  • Javascript 指南:数组
    数组数组是JavaScript的有序列表,可以存储任何数据类型,包括字符串、数字和布尔值。数组中的每个项目都位于一个编号位置。句法数组由方括号和里面的内容表示。数组中的元素应该用逗号分隔。让colors=["red","blue","green","yellow"];访问和更新元素要访问或更改数组中......
  • vue3 router 路由传参
    路由跳转importrouterfrom"@/router";router.push({path:"/iframe",query:{url:frameurl.value}});获取参数importrouterfrom"@/router";import{useRoute}from"vue-router";constroute=useRoute();const......
  • vue3 组件传参
    父组件  子组件<iframe:src="props.src"width="100%"height="100%"frameborder="0"id="_iframe"></iframe>接收参数constprops=defineProps({src:{type:......