《JavaScript高级程序设计》——函数参数按值传递

疑惑

在《javascript高级程序设计》中,看到第四章4.1.3传递参数这一节,看完有一些疑惑,原著中有一句话说:

ECMAScript中所有函数的参数都是按值传递的。

这句话让我思考了几天,这个值到底是个啥玩意儿哇?因为我们知道有按值传递按引用传递这两种方式。

回顾知识

JavaScript中有5种基本数据类型:UndefinedNullStringBooleanNumber

当我们给变量赋值这5种基本类型的时候,其实是在栈内存中开辟了一块内存空间用于存放变量,例如下面的代码:

var a = 5;

其实如下图所示:
基本

从一个变量向另一个变量复制基本类型的值的时候,其实是会在栈内存中重新开辟一块内存空间,将原变量的值复制到新变量分配的栈内存位置上。

如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。
——《JavaScript高级程序设计》

当你改变复制后的变量的值的时候,其实是不会影响被复制的变量的值滴,话不多说,我还是来段代码,画个图更清楚~~~~

var a = 5;
var b = a;
b = 886;
console.log(a);      // a还是5

话不多说,又来上图:

在这里插入图片描述
所以他是不会改变被复制的变量a的值。

但是如果是引用类型 Object 呢?

首先我们来看看给一个变量赋值为object类型的时候,会进行啥操作吧。

var a = {
    
    
	name: "小可爱"
};

它其实是进行了如下操作:

在这里插入图片描述

引用类型的值是保存在内存中的对象。引用的类型的值是按引用访问的。
——《JavaScript高级程序设计》

也就是说明啥呢?就是说我们的引用类型,其实是在堆内存中开辟了一块内存空间,这一块内存大小是不确定的。当我们声明一个变量的时候,其实都是在栈内存中开辟了一块内存空间,这一块内存大小是确定的,当我们把引用类型的值赋值给变量的时候,其实是用一个 引用 (也就是那一根箭头),将栈内存中的变量指向堆内存中的那一块开辟的对象的内存空间。

当我们将一个值是引用类型的变量,赋值给另外一个变量,并且改变另外一个变量的值的时候,原来的变量会咋样呢?

答案就是两个都会改变啊!!!话不多说,上代码:

var obj = new Object();
var newObj = obj;
obj.name = "不可爱";
console.log(newObj.name);     // 当然是不可爱啦!!!!

如下图所示:

在这里插入图片描述

从上图可以看到,两个在栈内存中的变量都指向了堆内存的同一块内存空间里,所以当我们改变一个变量存放的对象的属性值,也会在另一个对象中反映出来。

函数参数按值传递

首先在红宝书里面提到:

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量(即命名参数,或者用ECMAScript 的概念来说,就是 arguments 对象中的一个元素)。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部。

????这句话我是反复看了几遍,才了解了它的含义。
重要的是理解这句话理解这句话理解这句话:

可以把ECMAScript的函数参数想象成局部变量!!!!!!

上代码来解释:

这是向参数传递 基本类型 的值的情况:

function add(num) {
    
    
	num = num + 10;
	return num;
}
var a = 10;
var result = add(a);
console.log('a没进函数add之前的值为:' + a); // 10
console.log('a进了函数add之前的值为:' + result)// 20

这里根据上面的基础知识可以了解到,这里的add()函数参数num其实是这个函数的局部变量(!!!!划重点!!!!),调用这个函数的时候,其实是将a作为参数传递给了函数add()。因为这个变量a的值是10,数值10就被赋值给了这个add()函数的参数num使用(其实就相当于在栈内存中开辟了一个新的内存存放这个局部变量num以及它的值20,和原来的变量a没有关系)。在函数add()内部,参数num的值 + 10,和外边的全局变量a一点关系都没有。

这个好理解,那换成 引用类型 的情况呢?

上代码:

function reset(obj) {
    
    
	obj.name = "超级可爱";
}
var person = new Object();
reset(person);
console.log(person.name); // 超级可爱

在这段代码里面,我们创建了一个对象,并保存在了变量person中。这个变量被传递到了reset()函数中复制给了形参obj(相当于复制了引用,没错就是那根箭头),所以obj和person在函数内部在堆内存中引用的是同一个对象!

话不多说,又来上图:

在这里插入图片描述

看到这里,是不是又有点蒙蔽了?解释成按引用传递不是更好咩?

再看一段代码:

function reset(obj) {
    
    
	obj.name = "超级可爱";
	obj = new Object();
	obj.name = "不可爱";
} 
var person = new Object();
reset(person);
conosle.log(person.name); // 超级可爱

看到这里,如果person是按引用传递的,那么改变形参obj的属性name,也会反映在person变量中,但是实际上是没有的,为什么呢?上图:

在这里插入图片描述

在上图中,在函数内部,形参obj其实是将引用指向了一个新的堆内存中,所以我们在改变这个新obj的name属性值的时候,对person变量指向的堆内存对象其实是没有影响的。
倘若函数的参数是按引用传递的,那么person变量的指向也会指向那个属性name为不可爱的对象中,但是实际上是没有的,所以,函数参数是按值传递的。
多说一句,这个在函数内部的新开辟的堆内存对象,其实是一个局部对象,这个局部对象会在函数执行完毕后立刻被销毁掉。

新增一段代码,可以理解一下这个状态:

var a = {
    
    name: "aaa"};
var b = a;
b = {
    
    name: "bbb"};
console.log(a.name); // 'aaa'

这段代码就类似于函数里面发生的东西,只不过函数内部是局部变量,这里是全局变量。

总结

  1. 函数的参数我们可以把它看做是局部变量。
  2. 函数的参数传递是按值传递的。当参数传递的是基本类型的值的时候,按值传递是没问题的;当参数传递的是引用类型的值的时候,我认为其实传递的是这个引用值(也就是这个箭头)。
  3. 我在网上还看到一种说法,就是说参数传递的本质都是值传递,因为值类型和引用类型的访问方式不同,所以可以分为值传递和引用传递。
  4. 一定要记住5大基本数据类型:Undefined、Null、String、Number、Boolean。

欢迎大家向小弟指出问题。

猜你喜欢

转载自blog.csdn.net/weixin_38040996/article/details/90236117