详解JS按值传递与按引用传递的方法及区别
作者:admin 时间:2022-5-26 9:49:52 浏览:在 JavaScript 中,当调用函数时,参数可以通过两种方式传递,按值传递或按引用传递(地址)。 原始数据类型(如string、number、null、undefined、boolean)是按值传递的,而非原始数据类型(如对象、数组和函数)在 Javascript 中是按引用传递的。本文将了解两者之间的区别以及何时使用哪种方法。
原始和非原始数据类型
在理解 JavaScript 中的值传递和引用传递之前,我们先来了解一下什么是原始数据类型和非原始数据类型。因此,在 JavaScript 中,数据类型分为两大类:
- 原始
- 非原始
string、number、null、undefined、boolean等数据类型属于原始数据类型,而所有对象、数组和函数都属于非原始或引用数据类型。
原始数据类型按值进行比较。如果两个值相同,则它们严格相等。
var num1 = 40;
var num2 = 40;
numb1 === num2; // true
var string1 = 'webkka.com';
var string2 = 'webkaka.com';
string1 === string2; // true
非原始数据类型
var myArray = [ '卡卡网', 'WebKaka' ];
myArray[1] = 'Tutorial';
console.log(myArray) // [ '卡卡网', 'Tutorial' ];
对象和数组不按值进行比较。这意味着即使两个对象和数组分别具有相同的值和属性或相同的元素,它们也不是严格相等的。
var obj1 = {
'website': '卡卡网',
'topic': 'JavaScript'
};
var obj2 = {
'website': 'webkaka.com',
'topic': 'JavaScript'
};
obj1 === obj2; // false
var myArray1 = [ '卡卡网', 'webkaka.com' ];
var myArray2 = [ '卡卡网', 'webkaka.com' ];
arr1 === arr2; // false
只有当两个对象引用同一个对象时,它们才是严格相等的。
var obj1 = {
'website': 'webkaka.com',
'topic': 'JavaScript'
};
var obj2 = obj1;
obj1 === obj2; // true
非原始值有时也称为引用类型,因为它们不是值,而是通过引用进行比较。
在 JavaScript 中按值传递
JavaScript 中的按值传递意味着实际参数值的副本在内存中进行,即完成了新的内存分配,并且所有更改都在该新值中进行(即复制的值)。原始值和复制值彼此独立,因为它们在内存中的空间不同,即在更改函数内部的值时,函数外部的变量不受影响。
用简单的语言,我们可以理解为,在按值传递中,函数接收变量的副本,该副本独立于最初传递的变量。
JavaScript 中的按值传递需要更多空间,因为函数会获取实际内容的副本,因此会在内存中创建一个新变量。
在这个概念中,= 运算符起着很大的作用。当我们创建一个变量时,= 运算符会注意到你是为该变量分配原始值还是非原始值,然后相应地工作。
注意:当我们使用 = 运算符时,有一个函数调用(在幕后)按值(或引用)完成传递。
当我们为变量分配原始值时,= 运算符会在内存中(例如在地址2001处)设置一个空间(位置/地址),以将该变量(num1)的数据存储到该地址。当我们创建一个新变量num2(比如地址2002)并为其分配前一个变量num1的值时,= 运算符在内存中创建新空间,它独立于地址为2001的前一个变量num1。因此,这复制了原始变量num1的值到内存中的两个单独的位置(地址为2001和2002)。
let num1 = 70
let num2 = num1
console.log(num1) // 70
console.log(num2) // 70
num1 = 40
console.log(num1) // 40
console.log(num2) // 70
在这里,我们为 num1 分配了一个值70。这会在内存中创建一个名为num1的空间,地址为2001(假设)。当我们创建一个变量num2并为其分配num1的值时, = 运算符注意到我们正在处理一个原始值,因此它在内存中创建一个地址为2002的新空间并为其分配一个num1值的副本,即70。现在我们可以看到这两个变量在内存中都有不同的空间,并且都具有70的值。
现在,如果我们更改num1的值,那么num2将不起作用,因为它在内存中有自己的独立空间,现在它与num2的值无关,因为它们在内存中都有不同的空间(地址)。
让我们通过另一个例子更好地理解这一点:
function multiplication(tmp) {
tmp = tmp * 50;
return tmp;
}
var num = 30;
var result = multiplication(num);
console.log(num); // 30
console.log(result); // 1500
从上面的代码中,我们可以看到函数乘法接受一个参数并改变它的值。
然后我们声明了一个变量 num,其值为 30。
之后,我们将变量 num 传递给乘法函数。Javascript 自动将变量 num 的值复制到变量 tmp。所以,这里的 tmp 是一个新变量,它在内存中分配了一个新的空间,并且与 num 无关。
现在函数乘法所做的所有更改都直接对变量 tmp 进行;因此 num 的值不受影响。
这是因为在名为 tmp 的内存中创建了变量 num 的单独副本,初始值为 30,计算后变为 1500。
tmp和num彼此没有联系,即它们彼此独立。
在 JavaScript 中通过引用传递
与 JavaScript 中的值传递不同,JavaScript 中的引用传递不会在内存中创建新空间,而是传递实际参数的引用/地址,这意味着函数可以访问变量的原始值。因此,如果我们改变函数内部变量的值,那么原始值也会改变。
它不会创建副本,而是对原始变量起作用,因此函数内部所做的所有更改也会影响原始变量。
与 JavaScript 中的按值传递不同,这里当等号运算符识别出变量obj1
被设置为等于一个对象时,它会创建一个新的内存空间并将obj1
指向3005(地址假设)。现在,当我们创建一个新变量obj2
并将其分配给obj1
的值时,等号运算符识别出我们正在处理非原始数据类型,因此它指向obj1
指向的相同地址。因此我们可以看到没有创建新的内存空间,两个变量都指向obj1
指向的同一个地址。
let obj1 = {website: "webkaka.com"}
let obj2 = obj1;
console.log(obj1) // {website: "webkaka.com"}
console.log(obj2) // {website: "webkaka.com"}
obj1.website = "webkaka tutorial"
console.log(obj1) // {website: "webkaka tutorial"}
console.log(obj2) // {website: "webkaka tutorial"}
在上面的示例中,我们创建了一个变量obj1
并将其设置为一个对象,然后我们将另一个变量obj2
的值设置为obj1
。由于等号运算符表明我们正在处理非原始数据类型,因此它不会创建新的内存空间,而是将obj2
指向obj1
所指向的相同内存空间。因此,当我们改变obj1
的值时,obj2
的值也会改变,因为obj2
也指向与obj1
相同的内存空间。
对象中的引用传递(带函数)
let originalObj = {
name: "webkaka.com",
rating: 4.5,
topic: "JavaScript"
};
function demo(tmpObj) {
tmpObj.rating = 5;
console.log(tmpObj.rating);
}
console.log(originalObj.rating); // 4.5
demo(originalObj); // 5
console.log(originalObj.rating); //5
从上面的例子中,我们可以看到改变 tmpObj
的值时originalObj
的值也会改变。这样做的原因是,当我们调用demo
并传递对象时,originalObj
是通过其引用传递的,因此本地参数tempObj
将指向我们定义的同一个对象,即originalObj
。
因此,在这种情况下,我们不是在处理两个独立的副本,而是有指向同一个对象的变量,因此对该对象所做的任何更改都将对另一个变量可见。
在数组中通过引用传递(带函数)
let originalArr = ["卡卡网", "WebKaka","is", "the"];
function pushArray(tmpArr) {
tmpArr.push('best')
console.log(tmpArr);
}
console.log(originalArr); // ["卡卡网", "WebKaka", "is", "the"]
pushArray(originalArr); // ["卡卡网", "WebKaka", "is", "the", "best"]
console.log(originalArr); // ["卡卡网", "WebKaka", "is", "the", "best"]
在这里,当我们尝试向存储在tempArr
的数组中添加新项目时,它也会影响 originalArr
数组。发生这种情况是因为一个数组没有两个单独的副本,我们只处理一个数组。变量tempArr
引用了在变量originalArr
中初始化的同一个数组。
这个例子表明,像对象一样,在数组中改变tempArr
的值时originalArr
的值也会自动改变。
因此,我们可以得出结论,所有非原始数据类型都通过引用进行交互,因此当我们将它们的值设置为彼此相等或将它们传递给函数时,它们都指向相同的内存空间(地址),所以每当我们改变值之一,然后所有值都会更改。
何时使用按值传递或按指引传递?
在 JavaScript 中的按值传递,会创建变量的新副本,并且对新变量所做的任何更改都与原始变量无关,因此当我们想要跟踪初始变量而不想失去它的值时,就使用按值传递。
当我们传递大的参数时,在 JavaScript 中最好使用按引用传递,因为在被调用函数中没有单独的副本,因此不会浪费内存,因此程序效率更高。
总结
- 在 JavaScript 中,我们有值类型(也称为基元)和引用类型(非基元)。
- 基元是数字(number)、字符串(string)、布尔值(boolean)、未定义(undefined)和空值(null),而非基元是对象、函数和数组。
- 在 JavaScript 中按值传递时,会创建原始变量的副本,因此对复制的变量所做的任何更改都不会影响原始变量。
- 在 JavaScript 中按引用传递时,我们传递实际参数的引用,不会在内存中创建副本。
您可能对以下文章也感兴趣
- JS: forEach和for不同的功能表现【实例】
- [示例]详解JSON和XML的相似及区别之处
- 详细了解Javascript中var、let 和const的区别[示例]
- 实例详解何时使用JS中的let、var和const
- 实例详解区别JS三种声明方式:var,let,const
- 详解JSON.stringify()与JSON.parse()转换JSON对象和字符串
- 详细了解JavaScript中==和===的区别[示例]
- [示例]详解JS中=、==和===之间的区别
- 【实例】详解JS中==和===、!=和!==的使用区别
- 详解JS删除数组元素的三个方法:splice(),pop(),shift()
- 站长推荐