数据类型
基本数据类型
- String
- Number
- Boolean
- Null
- Undefined
- Symbol
引用值类型
- Object
- Array
- Function
- …
前五个是ES5之前的,第六个是ES6之后出现的。引用数据类型如果不太严谨的来说,几乎全是引用数据类型,也就是一切皆对象。
因为JavaScript语言,本就是一个趋向面向对象的脚本语言,可以看出,它受Java的影响很深,借鉴了Java语言的数据结构,将值分为原始值和对象两大类,虽然数据结构上分开了,但是Java中有包装类的概念,可以将原始值包装成对象,也就是引用数据类型。这样更加满足在Java中的一切皆对象的思想,面向对象的思想也就完整了,那么这个思想也被设计者(Brendan Eich-布兰登·艾奇)拿了过来,在JavaScript中同样适用,JavaScript中也有包装类。
特点
栗子
123.length; //报错
123.toString() //报错
var a = 123;
a.length; //undefined
a.toString(); //"123"
var a = "123";
a.length; //3
1、为什么直接使用纯原始值直接去获取属性调用方法会报错?
答:123.length
,123是一个值,而且是一个原始值类型,这句代码是想要在原始值上获取属性名叫 length 的属性,但是它报错了,说明如果是纯原始值我们绝对不能获取属性,那么方法呢?事实上也不行。
2、为什么var了一个变量后,获取属性就不报错了,返回undefined
,但是调用方法却正常?
答:当在控制台var了一个a,并赋值原始值123后,a. 的时候会出现 so many方法或属性的提示。
而纯原始值123.的时候什么东西都没有,那么关键是a. 这些方法和属性都是从哪里来的呢?从而引出了一个叫包装类的概念。
js是一种弱类型语言,var一个变量可以存储不同的数据类型,不需要类型声明,js引擎系统也是宽宏大度的,当你想要使用原始值类型获取属性或者方法的时候,js引擎系统会默认帮你自动包装成一个对象,也就是包装类对象,在构造此对象的构造函数的原型链上有着这些方法。
那么a.length
为什么不报错,还是undefined以及a.toString()
是正常的返回数据也就能解释的通了。当存储着原始值类型的a变量
获取属性和方法时,js引擎系统认为你有可能不是有意在原始值类型上获取属性或方法的,既然是无心之过,就大度原谅你一下吧,就使用包装类将其包装成包装类对象,要是原型链上有属性或者方法,你就用就可以了,如果没有也不会报错,只是返回一个undefined
。
3、为什么原始值string获取属性正常,调用方法也正常?
答:因为构造出string类型的包装类对象的构造函数的原型链上的方法多,有length
和toString()
。
栗子
var a = 123;
var b = a;
b = 456;
b.m = 456;
console.log(a); //123
console.log(b); //456
console.log(b.m) //undefined
console.log(a.m) //undefined
var c = {m: 123};
var d = c;
d.m = 456;
d.n = 789;
console.log(c.m); //456
console.log(d.m); //456
console.log(c.n); //789
console.log(d.n); //789
1、为什么明明给原始值类型新增了属性b.m,访问这个属性返回的是undefined?
答:这个其实是js引擎系统自动搞的事情。上面提到了,原始值类型是不能有属性和方法的,但是因为js是一个弱语言类型,js引擎系统它是很大度的,如果你想在原始值类型上去获取属性或者执行方法,js引擎系统会自动对其进行包装成包装类对象,我们在原始值上获取属性或者方法,实质上只在这个包装类对象上或者原型链上获取属性和方法,但这里需要注意的是,这个包装类对象不是一直存在的,而是在你进行操作之后就立即销毁了。
销毁了之后,再console.log(b.m)
;是又重新包装了一个对象,和增加属性时的根本不是同一个对象,当然返回的是undefined。
2、把存储原始值类型变量的复制到新变量中,新的变量中的值发生改变,原来的变量中的值不随之改变。
答:这种现象出现的本质是数据内存中存储机制造成的。
在内存中,原始值类型存储在栈(Stack)中,引用值类型则是将地址引用存储在栈里,内容值存储在堆(Heap)里。
在内存中的存储
原始值类型在内存中的存储
var a;在栈内存中开辟一个内存空间标识为a,存储值为123.
引用数据类型在内存的存储
var c;在栈内存中开辟一个内存空间标识为c,存储值为c对象的地址引用 m
在堆内存开辟一块内存空间,用来存储c对象中的键值对内容,这块堆内存引用的地址被栈内存中标识符为c的内存空间存储着。都是无序的键值对,类似Java的HashMap集合。
复制
当复制原始值时
只是将栈内容空间中存储的值拿了过去,空间上还是相互独立,互不干扰。所以新的变量中的值发生改变,原来的变量的值不随之改变。
当复制引用值类型时
也是将栈内存中存储的值拿了过去,只不过这次拿的是堆内存的地址引用,并不是值本身,所以地址的指向并没有变,指向的还是堆内存中的那个存储对象键值对内容的那个对象。所以新的变量中的值的属性发生变化,原来的变量中的值的属性也随之改变。
总结
-
从应用角度出发,原始值类型是绝对不能有属性和方法的,引用值类型却可以拥有属性和方法。
-
从底层角度出发,原始值类型存储在栈中,引用值类型则是将地址引用存储在栈里,内容值存储在堆里。
-
首先要注意的是,js的语言特性,它是一种弱类型语言,相比强类型语言,异常的灵活多变。
-
不要试图通过原始值类型去获取属性或者执行方法,因为有包装类的自动包装,但会立即销毁。你可能得不到你想要的结果,除非你对原型链上的属性和方法很熟悉。
-
尽量不要去使用
==
号去比较两个引用值类型是否相等,因为==
号对于原始值来说,比较的是值本身,但是对于引用值 比较的是地址,如果地址指向相同,那么系统就认为两者是同一对象,也就相等,反之则不相等。这就有可能就算 看上去对象长得一样,实际上是两个对象并不相等。所以在这一点要格外的注意。