JavaScript-基本类型和引用类型

基本类型和引用类型的值

ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值。基本类型值是指简单的数据段,而引用类型值指那些可能由多个值构成的对象。

在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型。

基本数据类型:Undefined、Null、Boolean、Number和String。这5中基本数据类型是按值访问的,因为可以操作保存在变量中的实际的值。

引用类型的值是保存在内存中的对象。

在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型的。ECMAScript放弃了这一传统。

动态的属性

定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值。但是,当这个值保存到变量中以后,对不同类型值可以执行的操作则大相径庭。

对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法。

var person=new Object();
person.name="Nicholas";
alert(person.name);//Nicholas

创建了一个对象并将其保存在了变量person中。然后,我们为该对象添加了一个名为name的属性,并将字符串值“Nicholas”赋给了这个属性。紧接着,又通过alert()函数访问了这个新属性。如果对象不被销毁或者这个属性不被删除,则这个属性将一直存在。

但是,我们不能给基本类型的值添加属性,尽管这样做不会导致任何错误。

var name="Nicholas";
name.age=27;
alert(name.age); //undefined

这里,我们为字符串name定义了一个名为age的属性,并为该属性赋值27.但在下一行访问这个属性时,发现该属性不见了。这说明只能给引用类型动态地添加属性,以便将来使用。

复制变量值

在从一个变量向另一个变量复制进本类型值和引用类型值时,也存在不同。如果从一个变量向另一个变量复制基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上。

var num1=5;
var num2=num1;

在此,num1中保存的值是5。当使用num1的值来初始化num2时,num2中也保存了值5。但num2中的5与num1中的5是完全独立的,该值只是num1中5的一个副本。此后,这两个变量可以参与任何操作而不会互相影响。

当从一个变量向另一个变量复制引用类型的值时,同样也会将存储在变量对象中的值复制一份放到为新变量分配的空间中。不同的是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,改变其中一个变量,就会影响另一个变量。

var obj1=new Object();
var obj2=obj1;
obj1.name="Nicholas";
alert(obj2.name);//Nicholas

首先,变量obj1保存了一个对象的新实例。然后,这个值被复制到了obj2中;换句话说,obj1和obj2都指向同一个对象。这样,当为obj1添加name属性后,可以通过obj2来访问这个属性,因为这两个变量引用的是同一个对象。

传递参数

ECMAScript中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型值的传递,则如同引用类型变量的复制一样。有不少开发人员在这一点上可能会感到困惑,因为访问变量有按值和按引用两种方式,而参数只能按值传递。

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

function addTen(num){
    num+=10;
    return num;
}

var count=20;
var result=addTen(count);
alert(count);//20,没有变化
alert(result);//30

这里的函数addTen()有一个参数num,而参数实际上是函数的局部变量。在调用这个函数时,变量count作为参数被传递给函数,这个变量的值是20。于是,数值20被复制给参数num以便在addTen()中使用。在函数内部,参数num的值被加上了10,但这一变化不会影响函数外部的count变量。参数num与变量count互不相识,它们仅仅是具有相同的值。假如num是按引用传递的话,那么变量count的值也将变成30,从而反映函数内部的修改。

function setName(obj){
    obj.name="Nicholas";
}

var person=new Object();
setName(person);
alert(person.name);//Nicholas

创建了一个对象,并将其保存在了变量person中。然后,这个变量被传递到setName()函数中之后被复制给了obj。在这个函数内部,obj和person引用的是同一个对象。即使这个变量是按值传递的,obj也会按引用来访问同一个对象。于是,当在函数内部为obj添加name属性后,函数外部的person也将有所反映;因为person指向的对象在堆内存中只有一个,而且是全局对象。有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,看如下例子

function setName(obj){
    obj.name="Nicholas";
    obj=new Object();
    obj.name="Greg";
}

ver person=new Object();
setName(person);
alert(person.name);//Nicholas

如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为“Greg”的新对象。但是,当接下来再访问person.name时,显示的值任然是“Nicholas”。这说明即使在函数内部修改了参数的值,但原始的引用仍然保持未变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部变量了。而这个局部变量会在函数执行完毕后立即被销毁。

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

检测类型

想知道是什么类型的对象。为此,ECMAScript提供了instanceof操作符。

result=variable instanceof constructor

如果变量是给定引用类型的实例,那么instanceof操作符就会返回true

alert(person instanceof Object);//变量person是Object吗?
alert(colors instanceof Array);//变量colors是Array吗?

根据规定,所有引用类型的值都是Object的实例。因此,在检测一个引用类型值和Object构造函数时,instanceof操作符始终会返回true。当然,如果使用instanceof操作符检测基本类型的值,则该操作符始终会返回false,因为基本类型不是对象。

猜你喜欢

转载自blog.csdn.net/zsx157326/article/details/80530460