JavaScript引用数据类型(对象类型)和原始(基本)数据类型特点比较
为讲解JavaScript引用数据类型(对象类型)和原始(基本)数据类型特点比较,需要先回顾JavaScript数据类型有哪些?
一)原始(primitive:原始、基本)数据类型,也称为原始值(primitive value),包括:
1.布尔值(Boolean),其字面值只有两个,分别是true和false。
2.null,Null类型只有一个唯一的字面值null, null 值的特殊关键字。JavaScript 是大小写敏感的,因此 null 与 Null、NULL或变体完全不同。
3.undefined,Undefined类型只有一个唯一的字面值undefined,undefined 表示变量未赋值时的属性。而undefined是JavaScript中的一个全局变量,即挂载在window对象上的一个变量,并不是关键字。
4.数字(Number),整数或浮点数,例如: 42 或者 3.14159。
5.任意精度的整数(BigInt),可以安全地存储和操作大整数。 ECMAScript 2020 引入了一个新的原始数据类型BigInt,用于表示任意精度的整数。在之前的版本中,JavaScript只能表示有限范围的整数,超出范围的整数会被转换为特殊的Infinity值。
6.字符串(String),字符串是一串表示文本值的字符序列,例如:"Howdy"。
7.符号(Symbol,在 ECMAScript 2015 / ES6 中新添加的类型)。一种实例是唯一且不可改变的数据类型。
二)对象(object)类型,是一种非原始(non-primitive)数据类型,也称为引用值(reference value),用于更复杂的数据结构。JavaScript中的对象类型包括:
1、普通对象(Plain objects):是指用户自定义的对象类型。这些对象通常是通过使用构造函数或类来定义的。在JavaScript中,有多种方式可以创建对象。在ES6及以后的版本中,JavaScript引入了类(class),这是一种创建和定义对象的新方法。
2、数组(Arrays):例如let arr = [1, 2, 3]; 数组对象有一些特殊的属性和方法,如length、push、pop等。
3、函数(Functions):函数也是对象,它们可以有属性和方法。例如:
function sayHello() {
console.log('Hello');
}
sayHello.myProperty = 'This is a property of the function.';
4、日期(Dates):例如let date = new Date();
5、正则表达式(RegExp,Regular expressions):例如let regex = /ab+c/;
6、其他内置对象,如Math,Set,Map等。
JavaScript原始(基本)数据类型和引用数据类型(对象类型)特点差别:
☆原始(基本)数据类型只能表示一个简单的值,不能添加、修改和删除属性。引用数据类型可以包含多个键值对,形成复杂的数据结构,可以动态地添加、修改和删除属性。
原始(基本)数据类型只能表示一个简单的值,比如字符串、数字、布尔值等。它们不能添加、修改和删除属性。
例如:
let num = 5;
num = 10; // 修改变量的值
console.log(num); // 10
let str = "Hello";
str.length = 5; // 添加属性
console.log(str.length); // undefined
在这个例子中,我们可以修改原始数据类型的变量的值,但不能添加属性。即使我们尝试添加属性,也不会对原始数据类型的值产生影响。因为原始数据类型的值是不可变的,它们没有属性可以修改或删除。
引用数据类型可以包含多个键值对,形成复杂的数据结构,比如对象和数组。你可以动态地添加、修改和删除属性或元素。
例如:
let person = {
name: "John",
age: 30,
hobbies: ["reading", "coding"]
};
person.name = "Jane"; // 修改属性值
person.gender = "female"; // 添加新属性
delete person.age; // 删除属性
console.log(person); // { name: "Jane", hobbies: ["reading", "coding"], gender: "female" }
在这个例子中,我们有一个person对象,它包含了name、age和hobbies等属性。我们可以通过修改属性值、添加新属性或删除属性来动态地改变对象的结构。
☆ 原始(基本)数据类型的值是不可变的,引用数据类型的值是可变的。
原始(基本)数据类型的值是不可变的,意味着你不能直接修改它们的值。当你尝试修改原始数据类型的变量值时,实际上是创建了一个新的值,并将其赋给变量。
例如:
let num = 5;
num = 10;
console.log(num); // 10
在这个例子中,我们将num的值从5修改为10,但实际上是创建了一个新的值10,并将其赋给num变量。
引用数据类型的值是可变的,意味着你可以修改它们的属性或元素。这是因为引用数据类型的值实际上是存储在内存中的对象,变量只是指向这个对象的引用。当你修改引用数据类型的值时,实际上是在修改对象本身,而不是修改变量的值。
例如:
let obj = { name: "John" };
obj.name = "Jane";
console.log(obj); // { name: "Jane" }
在这个例子中,我们修改了obj对象的name属性的值,这是因为obj是一个引用数据类型,它指向的对象是可变的。
这种不可变性的特性使得原始数据类型在处理简单的数据时更加方便和可靠,而引用数据类型则更适合处理复杂的数据结构和对象。
☆ 变量赋值方面,原始(基本)数据类型的变量赋值是值的复制,而引用数据类型(对象类型)的变量赋值是引用的复制。
当执行到变量赋值语句时,JavaScript引擎会将赋值操作的右侧表达式计算出一个值,并将该值赋给变量。赋值操作可以使用赋值运算符 =,也可以使用其他赋值运算符(如 +=、-= 等)。
变量赋值是一个按值传递的过程。对于基本数据类型(如数字、字符串、布尔值等),赋操作会将值复制给变量。而对于引用数据类型(如对象、数组等),赋值操作会将引用(指向对象的内存地址)复制给变量,而不是复制对象本身。这意味着,当你修改一个引用类型的变量时,实际上是修改了引用所指向的对象。
对于原始类型,变量赋值是通过将一个值复制给另一个变量来完成的。这意味着当你将一个原始数据类型的变量赋值给另一个变量时,实际上是将原始值复制到了新的变量中。这两个变量是完全独立的,修改其中一个变量的值不会影响另一个变量。
例如:
let a = 5.1;
let b = a;
b = 10.2;
console.log(a); // 输出 5.1
console.log(b); // 输出 10.2
当把一个原始变量的值赋给另一个原始变量时,只是把栈中的内容复制给另一个原始变量,此时这两个变量互不影响——其实在内存中是两个地址,是互相独立的存在,当一个变量值改变时,另一个变量不会因此而发生任何变化。图解如下:
【图中的红色❌,表示5.1这个值不再被变量a引用,若一个值不再被任何变量引用,可以被垃圾回收器标记为可回收的,具体的垃圾回收时间是由JavaScript引擎决定的。】
对于引用数据类型,变量赋值是通过将引用复制给另一个变量来完成的。引用是指向存储在内存中的对象的地址。当你将一个引用数据类型的变量赋值给另一个变量时,实际上是将引用复制到了新的变量中。这两个变量指向同一个对象,修改其中一个变量的属性会影响另一个变量。
例如:
let obj1 = { name: 'Alice' };
let obj2 = obj1;
obj2.name = 'Bob';
console.log(obj1.name); // 输出 'Bob'
console.log(obj2.name); // 输出 'Bob'
需要注意的是,当你修改引用数据类型的属性时,实际上是修改了对象本身,而不是变量。因此,所有指向该对象的变量都会反映出这个修改。图解如下:
☆ 参数传递方面,在 JavaScript中,参数传递方式是按值传递——传递的是副本。具体说来:1)当将一个原始(基本)数据类型(如数字、字符串、布尔值等)作为参数传递给函数时,实际上是将该值的一个副本传递给函数——将实参值复制给形参,实参和形参相互独立互不干扰。函数内部对该副本的修改不会影响到原始的值。2)当将一个引用数据类型(对象类型)(如对象、数组等)作为参数传递给函数时,传递的是该对象的引用(地址)的副本——将实参引用的地址值复制给形参,形参和实参指向同一个对象的地址,改变形参所指向的对象的属性将影响实参所指向的对象。需要注意,在引用类型的参数传递中,并不会改变形参的值(即引用的地址),而是通过形参改变它所指向的对象的属性。
当传递原始数据类型时,实际上是将原始值的副本传递给了函数或其他变量。这意味着函数或其他变量操作的是原始值的副本,而不是原始值本身。
这种传递方式使得函数内部对原始值的修改不会影响到函数外部的变量,因为函数内部操作的是原始值的副本,而不是原始值本身。
例如:
function addTen(num) { // num 是一个局部变量
num += 10;
return num;
}
let count = 20;
let result = addTen(count);
console.log(count); // 20,没有变化
console.log(result); // 30
解析如下:
此例展示了在 JavaScript 中传递变量的值的方式。
例中定义了一个函数 addTen,它接受一个参数 num。
在此主要关注变量情况:
首先,定义了一个变量 count,并赋值为 20。
接下来,调用 addTen 函数,并将 count 作为参数传递进去。这里需要注意的是,虽然 count 的值是 20,但是在函数中传递的是 count 的值的副本,而不是 count 本身。
在函数内部,num 的值被增加了 10,变成了 30。然后,函数返回了这个新的值,并将其赋给了变量 result。
最后,通过 console.log 打印了 count 和 result 的值。由于在函数中传递的是 count 的值的副本,所以 count 的值没有发生变化,仍然是 20。而 result 的值是函数返回的新值,即 30。图解如下:
当传递引用数据类型时,实际上是将引用的地址传递给了函数。这意味着函数可以通过引用来访问和修改原始对象。引用是指向存储在内存中的对象的地址,所以传递引用时,传递的是指向对象的指针,而不是对象本身的实际值。
这种传递方式使得我们可以在函数内部修改原始对象,并且这些修改会在函数外部可见。因为函数和外部变量都指向同一个对象,所以对对象的修改会影响到所有引用该对象的变量。
例如:
function changeName(obj) {
obj.name = 'Bob';
}
let person = { name: 'Alice' };
console.log(person.name); // 输出 'Alice'
changeName(person);
console.log(person.name); // 输出 'Bob'
解析如下:
此例展示了在 JavaScript 中传递对象的引用的方式。
例中定义了一个函数 changeName,它接受一个参数 obj。
在此主要关注变量情况:
首先,定义了一个对象 person,并赋值为 { name: 'Alice' }。这个对象有一个 name 属性,其初始值为 ‘Alice’。
接下来,通过 console.log 打印了 person.name 的值,即 ‘Alice’。
然后,调用 changeName 函数,并将 person 对象作为参数传递进去。这里需要注意的是,虽然 person 是一个对象,但是在函数中传递的是 person 对象的引用,而不是对象本身的副本。
在函数内部,通过修改 obj 的 name 属性,将其值改为 ‘Bob’。由于 obj 是 person 对象的引用,所以这个修改也会影响到 person 对象本身。
最后,通过 console.log 打印了 person.name 的值,即 ‘Bob’。由于在函数中修改了 person 对象的 name 属性,所以 person.name 的值变成了 ‘Bob’。图解如下:
总结之,传递引用时,传递的是引用的地址,而不是实际的值。传递原始数据类型时,传递的是实际的值的副本。这种差异导致了在函数内部对引用数据类型的修改会影响到函数外部的变量,而对原始数据类型的修改不会影响到函数外部的变量。
☆ 数据比较方面,原始(基本)数据类型的比较是对值的比较,引用数据类型的比较是对内存地址的比较。
对于原始(基本)数据类型的比较,是对值的比较。例如:
let num1 = 5;
let num2 = 5;
console.log(num1 === num2); // true
因为num1和num2的值相同,所以比较结果为true。
引用数据类型的比较是对内存地址的比较,而不是对值的比较。这意味着当你比较两个引用数据类型的变量时,实际上是在比较它们是否指向同一个内存地址。例如:
let obj1 = { name: "John" };
let obj2 = { name: "John" };
console.log(obj1 === obj2); // false
尽管obj1和obj2的属性值相同,但它们指向的是不同的内存地址,所以比较结果为false。
补充、关于“JavaScript(JS)参数传递按值传递” MDN Web Docs说法
Arguments are always passed by value and never passed by reference. This means that if a function reassigns a parameter, the value won't change outside the function. More precisely, object arguments are passed by sharing, which means if the object's properties are mutated, the change will impact the outside of the function.
【选自https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions 】
译文:实参总是按值传递,而不是按引用传递。这意味着,如果函数重新赋值一个参数,该值在函数外部不会改变。更准确地说,对象参数是通过共享传递的,这意味着如果对象的属性发生了变化,那么这种变化将影响函数的外部。
解析一下:
JS调用函数时,参数本质上是按值传递给函数的。传递给函数的值被称为函数的实参(值传递),对应位置的函数参数名叫作形参。如果实参是一个包含原始值 (数字,字符串,布尔值) 的变量,则就算函数在内部改变了对应形参的值,返回后,该实参变量的值也不会改变。如果实参是一个对象引用,则对应形参会和该实参指向同一个对象。假如函数在内部改变了对应形参的值,返回后,实参指向的对象的值也会改变。
附录、
JavaScript中的可变(Mutable)、不可变(Immutable)和变量的赋值介绍 https://blog.csdn.net/cnds123/article/details/134036700
多种(C++、Java、JavaScript、Python)编程语言参数传递方式介绍 https://blog.csdn.net/cnds123/article/details/132981086文章来源:https://www.toymoban.com/news/detail-743099.html
ECMAScript 中变量的复制,以及函数的参数传递
https://juejin.cn/post/6953878356836384805文章来源地址https://www.toymoban.com/news/detail-743099.html
到了这里,关于JavaScript引用数据类型(对象类型)和原始(基本)数据类型特点比较的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!