JavaScript的变量:变量值的数据类型

JavaScript的每个变量都会有有一个变量值,变量值都有对应的数据类型。在JavaScript中变量有两种不同的数据类型:基本类型引用类型。在不同的地方对这两种数据类型的称呼也略有不同。比如,基本类型又称之为原始类型(拥有方法的类型或者可变类型),引用类型又称之为对象类型(不能拥有方法的类型或者不可变类型)。不管怎么称呼,都是依据数据类型的特点来命名的。

JavaScript基本类型和引用类型有哪些

  • 基本类型:指的是简单的数据段。在JavaScript中有五种基本数据类型:undefinednullbooleannumberstring。基本类型都是按值访问的,就是说可以操作保存在变量中的实际值
  • 引用类型:对象、数组、函数。对象是属性和方法的集合。引用类型可以拥有属性和方法,属性又可以包含基本类型和引用类型。引用类型的值保存在内存中的对象,JavaScript不能直接操作对象的内存空间,操作对象时,实际上是操作对象的引用而不是实际的对象。引用类型的值是按引用访问的。

如何理解基本类型和引用类型

如何能更好的理解JavaScript中的基本类型和引用类型,还是通过代码来阐述,或许能说得更清楚。

比如在代码中声明了一个变量:

var name = "大漠";

"大漠"是一个string类型,根据前面所说的,它属于JavaScript中的基本类型。这时值是这样储存的:

基本类型和引用类型

紧接着:

var name = "大漠";
var name2 = name;

这时name的值被复制了一份,并且赋值给了name2。在此,name保存的值是"大漠"。当使用name的值为初始化name2时,name2也保存了值"大漠"。但namename1中的值完全是独立的,该值只是name2"大漠"的一个副本。此后,这两个变量可以参与任何操作而不会相互影响。

var name = "大漠";
var name1 = name;
var name = "W3cplus";

console.log(name);  // => W3cplus
console.log(name1); // => 大漠

下图更能形象的展示复制基本类型值的过程:

基本类型和引用类型

上面通过代码阐述了:如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的另一个对象。

说这么多理论还真不如上代码来得实际,而又简单。来看下面一个示例:

var oPerson = {
    name: "大漠"
}

{name: "大漠"}是一个Object,它属于引用类型,赋值前后值是这样存储的:

基本类型和引用类型

接下来看一下引用类型复制:

var oPerson = {
    name: "大漠"
};
var oPerson2 = oPerson;
oPerson = {name:"W3cplus"};
console.log(oPerson.name);  // => W3cplus
console.log(oPerson2.name); // => 大漠

{name: "大漠"}是内存中的一个Object,而{name:"W3cplus"}是内存中的一个新Object了。所以oPerson变量存储的地址也是指向这个新的Object,所以和oPerson2又无关,因此oPerson2.name的值还是"大漠"。来看张保存在变量对象中的变量和保存堆中的对象之间的这种关系:

基本类型和引用类型

oPerson.name = "W3cplus";时,修改的是大家共同指向的内存中的Object的属性值,所以oPerson2.name的值也就跟着变了。

var oPerson = {name:"大漠"};
var oPerson2 = oPerson; //引用类型
oPerson.name = "W3cplus";
console.log(oPerson.name);  // => W3cplus
console.log(oPerson2.name); // => W3cplus

再来看另一个示例:

var oPerson = {name: "大漠"};
var oPerson2 = oPerson.name; // oPerson.name是String类型,所以值被复制给oPerson2
oPerson.name = "w3cplus"; // 修改oPerson.name的值为w3cplus
oPerson2;//其值还是String类型,值依旧是w3cplus

简单的总结一下:变量赋值时总是会复制一份的,如果是基本类型,复制的就是实际的值,如果是引用类型,复制的是指向对象的地址值,所以指向的还是同一个对象。

如果还不能理解基本类型和引用类型的话来看一个形象的示例。有人打了一个比喻,把基本类型比作连锁店,而把引用类型比作连锁店钥匙

基本类型的变量的交换等于把连锁店的模式复制到一个新地方重新开了一个分店(其中统一店面咱们可以理解为相同的变量内容),这样新开的店(新变量)与其它旧店(老变量)互不相关,而且这些连锁店各自经营(新、老变量各自管各自)。

var var1 = "大漠";
var var2 = var1;
var var1 = "w3cplus";
console.log(var1); // => w3cplus
console.log(var2); // => 大漠

把基本类型var2传递给另一个变量(赋值)时,其实是分配了一块新的内存空间,因此改变var1的值对var2没有任何影响,因为它不像引用类型,变量的交换其实是交换了指向同一个内容的地址。

而引用类型变量的交换等于把现有的一间店的钥匙(变量引用地址,因为变量是一个对象)复制一把给了另外一个老板(新变量)。这个时候两个老板同时在打理这同一间店,两个老板的行为都有可能对一间店的运营造成影响。

var obj = {name: "大漠"};
var obj2 = obj;
obj.name = "w3cplus";
console.log(obj.name);  // => w3cplus
console.log(obj2.name); // => w3cplus

在上面的代码中,obj2只进行了一次赋值,理论上它的值是已经确定下来了,但后面通过改写obj的值,发现obj2的值也发生了改变。这正是引用类型的特征,也是我们在使用当中需要特别注意的地方。

基本类型和引用类型之间对比

经过上面介绍,对于JavaScript中的基本类型和引用类型或多或少有些了解了。接下来继续看看两间之间的区别。

基本类型的值是不可变,引用类型的值是可变

在JavaScript中任何方法都无法改变一个基本类型的值,比如一个字符串:

var name = "w3cplus";
name.toUpperCase(); // => "W3CPLUS"
console.log(name);  // => w3cplus

原来的变量name并没有发生任何的变化,而是调用了toUpperCase()方法后返回的是一个新的字符串"W3CPLUS"

也就是说,不能给基本类型添加属性和方法,再次说明基本类型时值是不可变的

对于引用类型,可以为引用类型添加属性和方法,也可以删除其属性和方法。比如:

var person = {}; // 创建一个空的对象,它是一个引用类型
person.name = "w3cplus";
person.age = 6;
person.sayName =  function () {
    console.log(person.name);
}
person.sayName(); // => w3cplus
delete person.name; // 删除person对象的name属性
person.sayName(); // => undefined

上面代码说明引用类型可能拥有属性和方法,并且是可以动态改变的

基本类型和引用类型的比较不同

基本类型相等时值相等。基本类型只有在它们的值相等的时候它们才相等。但有时候会遇到这种情况:

var a = 1;
var b = true;
console.log(a == b); // => true

它们不是相等吗?其实这是类型转换和==运算符的知识,也就是说在用==比较两个不同类型的变量时会进行一些类型转换。像上面的比较先会把true转换为数字1,再和数字1进行比较,结果就是true了。这是当比较的两个值的类型不同的时候==运算符会进行类型转换,但是当两个值的类型相同的时候,即使是==也相当于是===

var a = "w3cplus";
var b = "w3cplus";
console.log(a === b); // => true

引用类型的比较是引用的比较:

var obj = "{}";
var obj2 = "{}";
console.log(obj == obj2); // => true

上面讲基本类型的比较的时候提到了,当两个比较值的类型相同的时候,相当于是用===,所以输出的是true。 上面的objobj2都是字符串。

var obj = {};
var obj2 = {};
console.log(obj == obj2); // =>false

objobj2是字符串。但此时的结果是false。因为引用类型是按引用访问的,换句话说就是比较两个对象的堆内存中的址是否相同,那很明显,objobj2在堆内存中地址是不同的。

基本类型和引用类型

基本类型和引用类型的存放地址不一样

基本类型的变量是存放在栈区的(栈区指内存里的栈内存):

var name = "w3cplus";
var city = "hangzhou";
var age = 6;

它的存储结构如下图:

基本类型和引用类型

栈区包括了 变量的标识符和变量的值。

引用类型的值是同时保存在栈内存和堆内存中的对象。JavaScript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作啥呢? 实际上,是操作对象的引用,所以引用类型的值是按引用访问的。

准确地说,引用类型的存储需要内存的栈区和堆区(堆区是指内存里的堆内存)共同完成,栈区内存保存变量标识符和指向堆内存中该对象的指针,也可以说是该对象在堆内存的地址。

var person1 = {name: 'jozo'};
var person2 = {name: 'xiaom'};
var person3 = {name: 'xiaoq'};

则这三个对象在内存中保存的情况如下图:

基本类型和引用类型

基本类型和引用类型的赋值

基本类型是简单赋值。在从一个变量向另一个变量赋值基本类型时,会在该变量上创建一个新值,然后再把该值复制到新变量分配的位置上:

var a = 10;
var b = a;
a++;
console.log(a); // => 11
console.log(b); // => 10

此时,a中保存的值是10,当使用a来初始化b时(a的值被复制给了b),但b中的10a是相互独立,互不关涉的,该值只是a中的值的一个副本,此后,这两个变量可以参加任何操作而相互不受影响。

基本类型和引用类型

也就是说,基本类型在赋值操作后,两个变量是相互不受影响的。前面有说过这个。

引用类型的赋值是对象引用。当从一个变量向另一个变量赋值引用类型的值时,同样也会将存储在变量中的对象的值复制一份放到为新变量分配的空间中。前面讲引用类型的时候提到,保存在变量中的是对象在堆内存中的地址,所以,与简单赋值不同,这个值的副本实际上是一个指针,而这个指针指向存储在堆内存的一个对象。那么赋值操作后,两个变量都保存了同一个对象地址,则这两个变量指向了同一个对象。因此,改变其中任何一个变量,都会相互影响:

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'

b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22

console.log(a == b);// true

它们的关系如下图:

基本类型和引用类型

因此,引用类型的赋值其实是对象保存在栈区地址指针的赋值,因此两个变量指向同一个对象,任何的操作都会相互影响。

特别声明:基本类型和引用类型之间对比这部分内容来自于《JavaScript数据类型–基本类型和引用类型 》一文。

上面扯到了,感觉好复杂。这里先提供几篇文章,后面再仔细学学这方面的知识:

总结

在JavaScript中变量值分为基本类型和引用类型。基本类型在内存中具有固定的大小,而引用类型则不同。例如,对象可以具有任意的长度,无固定大小。数组也是。基本类型变量存的是数据的具体值,而引用类型变量保存的是值的引用。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。中国Drupal社区核心成员之一。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:http://www.w3cplus.com/javascript/variable-value-data-types.html

返回顶部