JavaScript数据类型

javaScript数据类型

JavaScript数据类型,包括七种基本值和一种对象类型。

七种基本值(primitive values、原始值)类型: null、undefined、number、string、boolean、Symbol、bigInt和 Object类型。

null:空、无。表示不存在,当为对象的属性赋值为null,表示删除该属性。在布尔运算中被认为是false。

undefined:未定义。当声明变量却没有赋值时会显示该值。可以为变量赋值为undefined。一个声明未定义的变量的初始值。

number:除了能够表示浮点数外,还有一些带符号的值:+Infinity,-Infinity 和 NaN (非数值,Not-a-Number)。

string:用于表示文本数据,在字符串中的每个元素占据了字符串的位置。第一个元素的索引为0,下一个是索引1,依此类推。字符串的长度是它的元素的数量。JavaScript 中字符串是不可变的(如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。

boolean:布尔表示一个逻辑实体,可以有两个值:true 和 false。

Symbol:表示独一无二的值。这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。ES6 引入了一种新的原始数据类型 symbol,表示独一无二的值。

bigInt:可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。BigInt是通过在整数末尾附加 n 或调用构造函数来创建的。ES10 又引入bigint。

 

基本类型的值是不可变的——值本身无法被改变。其特点:

基本类型的值是不可变的。

需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而原值不能像数组、对象以及函数那样被改变。详见https://developer.mozilla.org/zh-CN/docs/Glossary/Primitive

 

一种对象类型(Object type)。此外,还有Array(数组)类型,以及分别用于表示日期和正则表达式的 Date(日期)和 RegExp(正则表达式),这三种类型都是特殊的对象。严格意义上说,Function(函数)也是一种特殊的对象。详见 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/A_re-introduction_to_JavaScript

对象类型(Object type)细分包括:Object类型、Array类型、Date类型、RegExp类型、Function类型,也称为引用值(reference values)类型。

 

JavaScript中基本数据类型和引用数据类型的区别

参见“JS基本数据类型和引用数据类型的区别及深浅拷贝”一文,详见https://www.cnblogs.com/c2016c/articles/9328725.html

栈(stack)和堆(heap)

stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放

1、基本数据类型和引用数据类型

  ECMAScript包括两个不同类型的值:基本数据类型和引用数据类型。

  基本数据类型指的是简单的数据段(存放在栈中);引用数据类型指的是有多个值构成的对象(存放在内存中)。

  当我们把变量赋值给一个变量时,解析器首先要确认的就是这个值是基本类型值还是引用类型值。

2、常见的基本数据类型:

  基本数据类型是按值访问的,因为可以直接操作保存在变量中的实际值。示例:

  var a = 10;

  var b = a;

  b = 20;

  console.log(a); // 10值

  上面,b获取的是a值得一份拷贝,虽然,两个变量的值相等,但是两个变量保存了两个不同的基本数据类型值。

  b只是保存了a复制的一个副本。所以,b的改变,对a没有影响。

  下图演示了这种基本数据类型赋值的过程:

     JavaScript数据类型

3、引用类型数据:

  也就是对象类型Object type,比如:Object 、Array 、Function 、Data等。

  javascript的引用数据类型是保存在堆内存中的对象。

  与其他语言的不同是,你不可以直接访问堆内存空间中的位置和操作堆内存空间。只能操作对象在栈内存中的引用地址。

  所以,引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。

  var obj1 = new Object();

  var obj2 = obj1;

  obj2.name = "我有名字了";

  console.log(obj1.name); // 我有名字了

  说明这两个引用数据类型指向了同一个堆内存对象。obj1赋值给onj2,实际上这个堆内存对象在栈内存的引用地址复制了一份给了obj2,

  但是实际上他们共同指向了同一个堆内存对象。实际上改变的是堆内存对象。

  下面我们来演示这个引用数据类型赋值过程:

    JavaScript数据类型

4、总结区别

  a 声明变量时不同的内存分配: 

  1)原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置

  这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。

  2)引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是一个指针(point),指向存储对象的内存地址。

   这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。

   地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。

  b 不同的内存分配机制也带来了不同的访问机制

  1)在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,

  首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是传说中的按引用访问

  2)而原始类型的值则是可以直接访问到的。

  c 复制变量时的不同

  1)原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

  2)引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,

  也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。

  (这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量罢了)。多了一个指针。

   d 参数传递的不同(把实参复制给形参的过程)

  首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。

  但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?还不就是因为内存分配时的差别。  

  1)原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。

  2)引用值:对象变量它里面的值是这个对象在堆内存中的内存地址,这一点你要时刻铭记在心!

  因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因了,因为它们都指向同一个对象。

检测数据类型

typeof经常用来检测一个变量是不是最基本的数据类型。如

onsole.log(typeof 23); //输出:number

console.log(typeof 'abc123'); //string"

console.log(typeof true); //boolean

console.log(typeof x); //undefined

console.log(typeof undefined); //undefined

参见下图:

JavaScript数据类型

 

function stu(){} //定义一个函数

console.log(typeof stu); //输出:function

参见下图:

JavaScript数据类型

 

小结、typeof对基本类型(除null)和函数对象很方便,对其他类型就没办法了。

比较特殊的是typeof null返回“object”。

历史原因,规范尝试修改typeof null返回“null”修改完大量网站无法访问,为了兼容,或者说历史原因返回"object"。

console.log(typeof null); //输出:"object"

对数组

var arr = [1,2,3];

console.log(typeof arr); //输出:"object"

对数组等对象的判断常用instanceof。

instanceof 左操作数是一个类,右操作数是标识对象的类。如果左侧的对象是右侧类的实例,则返回true.

而js中对象的类是通过初始化它们的构造函数来定义的。即instanceof的右操作数应当是一个函数。所有的对象都是object的实例。如果左操作数不是对象,则返回false,如果右操作数不是函数,则抛出typeError。

instanceof 运算符是用来测试一个对象是否在其原型链原型构造函数的属性。其语法是object instanceof constructor

instanceof 操作符用来比较两个操作数的构造函数。只有在比较自定义的对象时才有意义。 如果用来比较内置类型,将会和 typeof 操作符 一样用处不大。

有一点需要注意,instanceof 用来比较属于不同 JavaScript 上下文的对象(比如,浏览器中不同的文档结构)时将会出错, 因为它们的构造函数不会是同一个对象。

结论:instanceof 操作符应该仅仅用来比较来自同一个 JavaScript 上下文的自定义对象。 正如 typeof 操作符一样,任何其它的用法都应该是避免的。

var arr = [1,2,3];

console.log(arr instanceof Array); //true

 

null和undefined 类型

JavaScript 中的 null 表示一个空值(non-value),必须使用 null 关键字才能访问,undefined 是一个“undefined(未定义)”类型的对象,表示一个未初始化的值,也就是还没有被分配的值。我们之后再具体讨论变量,但有一点可以先简单说明一下,JavaScript 允许声明变量但不对其赋值,一个未被赋值的变量就是 undefined 类型。还有一点需要说明的是,undefined 实际上是一个不允许修改的常量。

 

number类型

JavaScript 采用“遵循 IEEE 754 标准的双精度 64 位格式”("double-precision 64-bit format IEEE 754 values")表示数字。——在JavaScript(除了BigInt)当中,并不存在整数或整型(Integer)。

console.log(2 / 3);  // 输出:1.5

console.log(Math.floor(3 / 2)); // 1

 

console.log(0.1 + 0.2); //0.30000000000000004

 

JavaScript 支持标准的算术运算符,包括加法、减法、取模(或取余)等等。还有一个之前没有提及的内置对象 Math(数学对象),用以处理更多的高级数学函数和常数:

Math.PI ;//3.141592653589793

 

要小心NaN:如果把 NaN 作为参数进行任何数学运算,结果也会是 NaN:

NaN + 5; //NaN

可以使用内置函数 isNaN() 来判断一个变量是否为 NaN:

isNaN(NaN); // true

 

JavaScript 还有两个特殊值:Infinity(正无穷)和 -Infinity(负无穷):

1 / 0; //  Infinity

-1 / 0; // -Infinity

可以使用内置函数 isFinite() 来判断一个变量是否是一个有穷数, 如果类型为Infinity, -Infinity 或 NaN则返回false:

isFinite(1/0); // false

isFinite(Infinity); // false

isFinite(-Infinity); // false

isFinite(NaN); // false

isFinite(0); // true

isFinite(2e64); // true

isFinite("0"); // true

如果是纯数值类型的检测,则返回 false:

Number.isFinite("0"); // false

 

可以使用内置函数 parseInt() 将字符串转换为整型。该函数的第二个可选参数表示字符串所表示数字的基(进制):

parseInt("123", 10); // 123

parseInt("010", 10); // 10

 

 

一些老版本的浏览器会将首字符为“0”的字符串当做八进制数字,2013 年以前的 JavaScript 实现会返回一个意外的结果:

parseInt("010");  //  8

parseInt("0x10"); // 16

 

 

这是因为字符串以数字 0 开头,parseInt()函数会把这样的字符串视作八进制数字;同理,0x开头的字符串则视为十六进制数字。

如果想把一个二进制数字字符串转换成整数值,只要把第二个参数设置为 2 就可以了:

parseInt("11", 2); // 3

 

JavaScript 还有一个类似的内置函数 parseFloat(),用以解析浮点数字符串,与parseInt()不同的地方是,parseFloat() 只应用于解析十进制数字。

一元运算符 + 也可以把数字字符串转换成数值:

+ "42";   // 42

+ "010";  // 10

+ "0x10"; // 16

备注: parseInt() 和 parseFloat() 函数会尝试逐个解析字符串中的字符,直到遇上一个无法被解析成数字的字符,然后返回该字符前所有数字字符组成的数字。但是运算符 "+"对字符串的转换方式与之不同, 只要字符串含有无法被解析成数字的字符,该字符串就将被转换成 NaN。可分别使用这两种方法解析“10.2abc”这一字符串,并比较得到的结果,来理解这两种方法的区别。

 

String类型