【ES6 & JS】数据类型及其存储方式

文章目录


一、JS的数据类型有哪些?

JavaScript中的数据类型可以分为原始数据类型(基本数据类型)和引用数据类型两种。

原始数据类型包含:

  • Number:数值类型
  • String:字符串类型
  • Boolean:布尔类型
  • Undefined:undefined,表示值未定义
  • Null:null,空值
  • Symbol(ES6新增):符号类型
  • Bingint(ES10新增):大整数类型

引用数据类型统称为Object,主要包含:

  • Object:对象
  • Array:数组
  • Function:函数
  • 除了上述三种之外,还包括Date、RegExp、Map、Set等......

二、数据类型介绍

1.Number 

Number类型使用IEEE 754格式表示整数和浮点值,不同的数值类型有不同的数值字面量格式。

最基本的数值字面量格式是十进制整数,也可以用八进制(以8为基数,以0o开头)或十六进制(以16为基数,以0x开头)字面量表示。

let intNum = 55;           //十进制整数55
let octalNum = 0o70;       //八进制整数56
let hexNum = 0x39;         //十六进制整数57

浮点值数值中必须包含小数点,且小数点后必须至少有一个数字。对于非常大或非常小的数值,浮点值可以用科学计数法来表示,ES中科学计数法的格式要求是一个数值后跟一个大写或小写的字母e,再加上一个要乘的10的多少次幂。浮点值精确度最高可达17位小数,但在算术计算中远不如整数准确。

let floatNum1 = 1.1;
let floatNum2 = 0.1;
let floatNum3 = .1;        // 有效,但不推荐
let floatNum4 = 3.125e7;   // 等于 31250000
let a = 0.1, b = 0.2;
console.log(a + b);        //0.30000000000000004 !== 0.3

有一个特殊的数值叫NaN,意思是不是数值Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。比如,用0除以任意数值在其他语言中通常会导致错误,但在ECMAScript中,0+0-0相除会返回NaN。如果分子是非0值,分母是有符号0或无符号0,则会返回Infinity(无穷大)或-Infinity(由于JavaScript保存数值的方式,实际中可能存在+0-0+0-0在所有情况下都被认为是相同的)。

NaN独有的属性:任何涉及NaN的操作始终返回NaNNaN不等于包含NaN在内的任何值。为此,ECMAScript提供了isNaN()函数,用来判断该函数接收到的参数是否不是数值

console.log(0/0);          //NaN
console.log(5/0);          //Infinity
console.log(5/-0);         //-Infinity
console.log(NaN == NaN);   //false
console.log(isNaN(NaN));   //true
console.log(isNaN(10));    //false,10是数值
console.log(isNaN("10"));  //false,"10"可以转换为数值10
console.log(isNaN("ES"));  //true,"ES"无法转换为数值
console.log(isNaN(true));  //false,true可以转换为数值1,false可以转换为数值0

数值转换3 个函数可以将非数值转换为数值:Number()parseInt()parseFloat()Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值。

Number()函数基于如下规则执行转换:

    布尔值,true 转换为1,false 转换为0。

    数值,直接返回。

    null,返回0。

    undefined,返回NaN。

    字符串,应用以下规则:

        如果字符串包含数值字符,包括数值字符前面带加、减号的情况,则转换为一个十进制数值。因此,Number("1")返回1,Number("123")返回123,Number("011")返回11(忽略前面的零)。

        如果字符串包含有效的浮点值格式如"1.1",则会转换为相应的浮点值(同样,忽略前面的零)。

        如果字符串包含有效的十六进制格式如"0xf",则会转换为与该十六进制值对应的十进制整数值。

        如果是空字符串(不包含字符),则返回0。

        如果字符串包含除上述情况之外的其他字符,则返回NaN。
    对象,调用valueOf()方法,并按照上述规则转换返回的值。如果转换结果是NaN,则调用toString()方法,再按照转换字符串的规则转换。

let num1 = Number("Hello world!");            // NaN
let num2 = Number("");                        // 0
let num3 = Number("000011");                  // 11
let num4 = Number(true);                      // 1

parseInt()函数更专注于字符串是否包含数值模式。字符串最前面的空格会被忽略,从第一个非空格字符开始转换。如果第一个字符不是数值字符、加号或减号,parseInt()立即返回NaN。这意味着空字符串也会返回NaN(这一点跟Number()不一样,它返回0)。如果第一个字符是数值字符、加号或减号,则继续依次检测每个字符,直到字符串末尾,或碰到非数值字符。

let num1 = parseInt("1234blue");              // 1234
let num2 = parseInt("");                      // NaN
let num3 = parseInt("0xA");                   // 10,解释为十六进制整数
let num4 = parseInt(22.5);                    // 22
let num5 = parseInt("70");                    // 70,解释为十进制值
let num6 = parseInt("0xf");                   // 15,解释为十六进制整数

不同的数值格式很容易混淆,因此parseInt()也接收第二个参数,用于指定底数(进制数)。如果知道要解析的值是十六进制,那么可以传入16 作为第二个参数。

let num1 = parseInt("AF", 16);                // 175
let num2 = parseInt("AF");                    // NaN
let num1 = parseInt("10", 2);                 // 2,按二进制解析
let num2 = parseInt("10", 8);                 // 8,按八进制解析
let num3 = parseInt("10", 10);                // 10,按十进制解析
let num4 = parseInt("10", 16);                // 16,按十六进制解析

parseFloat()函数也是从位置 0 开始检测每个字符,它始终忽略字符串开头的零,解析到字符串末尾或者解析到一个无效的浮点数值字符为止。这意味着第一次出现的小数点是有效的,但第二次出现的小数点就无效了,此时字符串的剩余字符都会被忽略。parseFloat()只解析十进制值,不能指定底数。
 

let num1 = parseFloat("1234blue");            // 1234,按整数解析
let num2 = parseFloat("0xA");                 // 0
let num3 = parseFloat("22.5");                // 22.5
let num4 = parseFloat("22.34.5");             // 22.34
let num5 = parseFloat("0908.5");              // 908.5
let num6 = parseFloat("3.125e7");             // 31250000

2.String

String(字符串)类型表示0或多个16Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示。

let name1 = "Jack";
let name2 = 'Mary';
let name3 = `ECMAScript`;

ECMAScript中的字符串一旦创建,其值就不可以改变,若要修改某个变量中的值,必须先销毁原始的字符串,然后将包含新值的另一个字符串保存到该变量。

let language = "Java";                       //Java
language = language + "Script";              //JavaScript

在这里,变量language 一开始包含字符串"Java"。紧接着,language 被重新定义为包含"Java""Script"的组合,也就是"JavaScript"。整个过程首先会分配一个足够容纳10 个字符的空间,然后填充上"Java""Script"。最后销毁原始的字符串"Java"和字符串"Script",因为这两个字符串都没有用了。

字符串转换:有两种方式把一个值转换为字符串。首先是使用几乎所有值都有的toString()方法。这个方法唯一的用途就是返回当前值的字符串等价物。null undefined 值没有toString()方法。

let age = 11;
let ageAsString = age.toString();           // 字符串"11"
let found = true;
let foundAsString = found.toString();       // 字符串"true"

如果不确定一个值是不是null undefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串。String()函数遵循如下规则:

  • 如果值有toString()方法,则调用该方法(不传参数)并返回结果。
  • 如果值是null,返回"null"。
  • 如果值是undefined,返回"undefined"。
let value1 = 10;
let value2 = true;
let value3 = null;
let value4;
console.log(String(value1));                // "10"
console.log(String(value2));                // "true"
console.log(String(value3));                // "null"
console.log(String(value4));                // "undefined"

3.Boolean

Boolean(布尔值)类型有两个字面值:true false。这两个布尔值不同于数值,因此true 不等于1false 不等于0

let found = true;
let lost = false;

虽然布尔值只有两个,但所有其他ECMAScript 类型的值都有相应布尔值的等价形式。要将一个其他类型的值转换为布尔值,可以调用特定的Boolean()转型函数:

let message = "Hello ECMAScript!";
console.log(typeof message);              //string
let messageAsBoolean = Boolean(message);
console.log(typeof messageAsBoolean);     //boolean
不同类型与布尔值之间的转换规则:
数据类型会转换成为true的值会转换成为false的值
Booleantruefalse
String非空字符串""(空字符串)
Number非零数值0、NaN
Object任意对象null
UndefinedN/A(不存在)undefined

4.Undefined

Undefined 类型只有一个值,就是特殊值undefined。当使用 var let 声明了变量但没有初始化时,就相当于给变量赋予了undefined 值,一般来说,永远不用显式地给某个变量设置undefined 值。

let message;
console.log(message,typeof message);   // undefined  'undefined'

包含undefined 值的变量跟未定义变量是有区别的,对于已声明但未初始化的变量,在执行console.log()方法时,会指出变量的值,即‘undefined’,但是对于未声明的变量,只能执行一个有用的操作,就是对他调用typeof。无论是声明还是未声明,typeof 返回的都是字符串"undefined"

let message;
console.log(message);          //undefined
console.log(age);              //报错
console.log(typeof message);   //undefined
console.log(typeof age);       //undefined

5.Null

Null 类型同样只有一个值,即特殊值null。逻辑上讲,null 值表示一个空对象指针,这也是给typeof 传一个 null 会返回"object"的原因。

undefined 值是由 null 值派生而来的,因此它们被定义为表面上相等,但是在全等比较操作时,他们是不同的。

let car = null;
console.log(typeof car);          // "object"
console.log(null == undefined);   //true
console.log(null === undefined);  //false

在定义将来要保存对象值的变量时,可以使用 null 来初始化。这样只要检查这个变量的值是不是 null 就可以知道这个变量是否在后来被重新赋予了一个对象的引用。

if(car != null){
            //car是一个对象的引用
}

即使 null undefined 有关系,它们的用途也是完全不一样的。如前所述,永远不必显式地将变量值设置为 undefined。但 null 不是这样的。任何时候,只要变量要保存对象,而当时又没有那个对象可保存,就要用 null 来填充该变量。这样就可以保持 null 是空对象指针的语义,并进一步将其与 undefined 区分开来。

6.Symbol

Symbol(符号)是ECMAScript 6 新增的数据类型。Symbol是原始值,且Symbol实例是唯一、不可变的。Symbol的用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险。

符号需要使用Symbol()函数初始化。因为符号本身是原始类型,所以typeof 操作符对符号返回symbol

调用Symbol()函数时,也可以传入一个字符串参数作为对符号的描述,将来可以通过这个字符串来调试代码。但是,这个字符串参数与符号定义或标识完全无关。

let sym1 = Symbol();
let sym2 = Symbol();
console.log(sym1,sym2);        //Symbol() Symbol()
console.log(sym1 == sym2);     //false

let sym3 = Symbol('foo');
let sym4 = Symbol('foo');
console.log(sym3 == sym4);     //false

Symbol()函数不能与 new 关键字一起作为构造函数使用。

let mySymbol = new Symbol();    //报错

可以使用Symbol.for()方法在全局符号注册表中创建并重用符号。Symbol.for()对每个字符串键都执行幂等操作。第一次使用某个字符串调用时,它会检查全局运行时注册表,发现不存在对应的符号,于是就会生成一个新符号实例并添加到注册表中。后续使用相同字符串的调用同样会检查注册表,发现存在与该字符串对应的符号,然后就会返回该符号实例。

即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同。

let sym5 = Symbol.for('foo');         //创建新符号
let sym6 = Symbol.for('foo');         //重用已有符号
console.log(typeof sym5);             //symbol
console.log(sym5 === sym6);           //true
let sym7 = Symbol('foo');
console.log(sym5 === sym7);           //false

7.BigInt

BigInt 是一种数字类型的数据,它可以表示大于 2^53-1 的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

可以用在一个整数字面量后面加 n 的方式定义一个 BigInt ,如:10n,或者调用函数 BigInt()(但不包含 new 运算符)并传递一个整数值或字符串值。

const theBiggestInt = 9007199254740991n;
const alsoHuge = BigInt(9007199254740991);          // 9007199254740991n
const hugeString = BigInt("9007199254740991");      // 9007199254740991n
const hugeHex = BigInt("0x1fffffffffffff");         // 9007199254740991n
const hugeBin = BigInt("0b11111111111111111111111111111111111111111111111111");
                                                    // 9007199254740991n

BigInt 不同于 Number 之处:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。

以下操作符可以和 BigInt 一起使用: +`*``-``**``%` 。除 >>> (无符号右移)之外的位操作也可以支持。因为 BigInt  都是有符号的, >>> (无符号右移)不能用于 BigInt

const MaxSafe = BigInt(Number.MAX_SAFE_INTEGER);    // ↪ 9007199254740991n
const maxPlusOne = MaxSafe + 1n;                    // ↪ 9007199254740992n
const multi = MaxSafe * 2n;                         // ↪ 18014398509481982n
const subtr = multi – 10n;                          // ↪ 18014398509481972n
const mod = multi % 10n;                            // ↪ 2n
const bigN = 2n ** 54n;                             // ↪ 18014398509481984n
bigN * -1n                                          // ↪ –18014398509481984n

8.Object

ECMA-262 将对象定义为一组属性的无序集合。严格来说,这意味着对象就是一组没有特定顺序的值。对象的每个属性或方法都由一个名称来标识,这个名称映射到一个值。

创建自定义对象的通常方式是创建Object 的一个新实例,然后再给它添加属性和方法。

let person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
person.sayName = function() {
    console.log(this.name);
};

现今创建 Object 的常用方式为对象字面量表示法。

let person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
    sayName() {
        console.log(this.name);
    }
};

9.Array

ECMAScript 数组也是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。这意味着可以创建一个数组,它的第一个元素是字符串,第二个元素是数值,第三个是对象。ECMAScript 数组也是动态大小的,会随着数据添加而自动增长。

使用Array构造函数创建数组:

let colors = new Array(1, "blue", {age: 22});

使用数组字面量表示法创建数组:

let colors = ["red","yellow","blue"];
let names = [];
let values = [1,2,];

Array 构造函数还有两个ES6 新增的用于创建数组的静态方法:from()of()from()用于将类数组结构转换为数组实例,而 of() 用于将一组参数转换为数组实例。

console.log(Array.from("Matt"));          // ["M", "a", "t", "t"]
console.log(Array.of(1, 2, 3, 4));        // [1, 2, 3, 4]

要取得或设置数组的值,需要使用中括号并提供相应值的数字索引:

let colors = ["red", "blue", "green"];    // 定义一个字符串数组
console.log(colors[0]);                   // 显示第一项   red
colors[2] = "black";                      // 修改第三项
colors[3] = "brown";                      // 添加第四项
console.log(colors);                      //['red', 'blue', 'black', 'brown']

10.Function

函数实际上是对象。每个函数都是Function类型的实例,而Function 也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是指向函数对象的指针,而且不一定与函数本身紧密绑定。函数通常以函数声明的方式定义,比如:

function sum (num1, num2) {
    return num1 + num2;
}

另一种定义函数的语法是函数表达式。函数表达式与函数声明几乎是等价的:

let sum = function(num1, num2) {
    return num1 + num2;
};

还有一种定义函数的方式与函数表达式很像,叫作箭头函数,如下所示:

let sum = (num1, num2) => {
    return num1 + num2;
};

最后一种定义函数的方式是使用 Function 构造函数。这个构造函数接收任意多个字符串参数,最后一个参数始终会被当成函数体,而之前的参数都是新函数的参数不推荐使用。

let sum = new Function("num1", "num2", "return num1 + num2"); // 不推荐

三、数据类型存储方式

原始数据类型和引用数据类型的存储位置不同,原始数据类型存储在栈中,引用数据类型存储在堆中。堆和栈的概念存在于数据结构中和操作系统内存中。

在数据结构中,栈中数据的存取方式为后进先出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

在操作系统中,内存被分为栈区和堆区。栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。

原始数据类型是直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据,所以放入栈中存储。

引用数据类型是存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

let a = 100;
let b = a;
b = 200;
console.log(a,b);           //100,200
let obj1 = {};
let obj2 = obj1;
obj2.name = "ES6";
console.log(obj1.name);     //ES6

JS数据类型及存储

总结

  • 声明变量时不同的内存地址分配:
    • 简单类型的值存放在栈中,在栈中存放的是对应的值
    • 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址
  • 不同的类型数据导致赋值变量时的不同:
    • 简单类型赋值,是生成相同的值,两个对象对应不同的地址
    • 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值