【JavaScript】深度详细理解原始值类型和引用值类型

数据类型

基本数据类型

  • 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类型的包装类对象的构造函数的原型链上的方法多,有lengthtoString()
在这里插入图片描述

栗子

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集合。

复制

当复制原始值时

只是将栈内容空间中存储的值拿了过去,空间上还是相互独立,互不干扰。所以新的变量中的值发生改变,原来的变量的值不随之改变。

当复制引用值类型时

也是将栈内存中存储的值拿了过去,只不过这次拿的是堆内存的地址引用,并不是值本身,所以地址的指向并没有变,指向的还是堆内存中的那个存储对象键值对内容的那个对象。所以新的变量中的值的属性发生变化,原来的变量中的值的属性也随之改变。

总结

  1. 从应用角度出发,原始值类型是绝对不能有属性和方法的,引用值类型却可以拥有属性和方法。

  2. 从底层角度出发,原始值类型存储在栈中,引用值类型则是将地址引用存储在栈里,内容值存储在堆里。

  3. 首先要注意的是,js的语言特性,它是一种弱类型语言,相比强类型语言,异常的灵活多变。

  4. 不要试图通过原始值类型去获取属性或者执行方法,因为有包装类的自动包装,但会立即销毁。你可能得不到你想要的结果,除非你对原型链上的属性和方法很熟悉。

  5. 尽量不要去使用 ==号去比较两个引用值类型是否相等,因为 ==号对于原始值来说,比较的是值本身,但是对于引用值 比较的是地址,如果地址指向相同,那么系统就认为两者是同一对象,也就相等,反之则不相等。这就有可能就算 看上去对象长得一样,实际上是两个对象并不相等。所以在这一点要格外的注意。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值