JavaScript数据类型

241 阅读11分钟

JavaScript数据类型分为基本数据类型(原始类型)和复杂数据类型(引用类型):

不同点在于:

基本数据类型的变量和引用数据类型的地址存放在栈中,引用数据类型的值则存放在堆中;访问基本数据类型会直接在栈中读取,访问引用数据类型,会先在栈中读取引用类型的地址,然后根据地址去堆中拿取数据;栈里面的数据用完了会自动删除,数据调用快,堆里面的则不会自动删除,而如果里面的数据不断积累,就会造成内存泄漏,数据调用慢,而这一问题的解决需要提到垃圾回收机制

垃圾回收机制:

垃圾回收机制主要有标记清理引用计数两套垃圾回收算法,都是由系统自动完成,且为周期性运行,而在IE9之前的引用计数算法会导致循环引用的问题,这一缺陷的解决方法为:将变量设置为null,实际上为切断变量与其引用值之间的关系,当再次垃圾回收程序运行时这些值就会被删除,内存也会被回收,IE9将BOM和DOM对象都改成了JavaScript对象,同时避免了由于存在两套垃圾回收算法而导致的问题,还解决了常见的内存泄漏现象。需要注意:解除对一个值的引用并不会导致相关内存被回收,解除引用的关键在于确保相关的值已经不在上下文里了,因此它在下次垃圾回收时会被回收。

一、基本数据类型(原始类型):

Number:
    数值,分为整数(int)和浮点数(float)
Bigint:
    任意大的整数
String:
    字符串,通过单引号或双引号括起来的字符序列
Boolean:
    布尔值,truefalse
Null:
    空对象指针
Undefined:
    被定义但未初始化的变量初值为undefined
Symbol:
    符号,最大的用法是定义对象的唯一属性名

详解:

Number:

即数值类型,Number使用IEEE754格式表示整数和浮点数(也称双精度),由于js保存数值的方式,实际中可能存在正零(+0)和负零(-0),正零和负零在所有情况下都被认为是等同的

//整数:
let intNum = 20;  //=>20, 十进制整数直接书写即可
let intNum2 = 1_000_000_000;    //=>1000000000,可用下划线分割为容易看清的字段
let octalNum = 070;
console.log(octalNum)  //=>56, 此处为非严格模式下八进制形式,第一个数字必须是0,严格模式下前缀0会被拼抛出语法错误,应使用前缀0o
                       //无效的八进制值如070,会被忽略前缀0当作十进制70处理
let hexNum = 0xA;
console.log(hexNum);   //=>10,前缀0x(区分大小写)为十六进制表示
console.log(octalNum+0xA);   //=>66--->10+56,使用八进制和十六进制创建的数值在数学操作中都被视为十进制数值

//浮点数:
let floatNum = 0.1;  //必须包含小数点,且小数点后必须至少有一个数字,否则因为浮点值占用内存空间是整数的两倍,所以ES总会设法将值转为整数保存
                     //即1.0也会被转为整数1
let floatNum1 = .1;  //有效,不推荐
let floatNum2 = 3.125e7;   //=>31250000, js中的科学计数法表示
console.log(0.1+0.2==0.3)  //=>false, ES会将小数点后至少包含6个零的浮点值转换为科学计数法,如0.000 0003会被转为3e-7,精确度最高可达17位
                           //但在算数计算中远不如整数精确,例如0.1+0.2得到的为0.300 000 000 000 000 04
                           //所以,永远不要测试某个特定的浮点值,这种错误是由于使用IEEE754数值,并非JS独有

BigInt:

由于内存限制,ES并不支持表示所有数值,可表示的最小数值保存在Number.MIN_VALUE,最大数值保存在Number.MAX_VALUE,不同浏览器范围不同,若某个计算结果超出最大最小表示范围,这个数将会被转为Infinity表示正无穷大,-Infinity表示负无穷大,该值将不能进一步用于计算,要确定一个值是在最大值和最小值之间可使用isFinite(num)函数,返回结果为true或false,也正是因为这种最大范围限制,BigInt在ES10应运而生,表示大于2^53 - 1或任意大的整数;某些方面类似于Number,但不可使用Math对象中的方法,不可和Number实例混合运算,因其转为Number可能会丢失精度

let num = 10n;      //可通过在整数字面量后加一个n来定义BigInt,或通过函数BigInt(‘666’),但不加new关键字,参数引号可不加
console.log(typeof num);  //=>bigint

let num1 = BigInt(5);
let num2 = BigInt(2);
console.log(num1/num2);   //=>2n,  使用 `BigInt` 时,带小数的运算会被向下取整。
                          //还可使用+、-、*、%、**运算,除>>>右移外的位操作也支持
                          
console.log(1n===1);  //=>false
console.log(1n<2);    //=>true
console.log(1n==1);   //=>true,  BigInt与Number为宽松等,非严格等

String:

表示零或多个16位Unicode字符序列,通过单引号(‘)、双引号(")或反引号(`)定义,不同于某些语言使用不同的引号会改变对字符串的解释方式不同,不过需要注意以某种引号开头必须仍以某种引号结尾,码点超出16位的Unicode字符使用UTF-16规则编码为两个16位值的序列,这意味着一个长度为2的字符串可能表示的表示一个Unicode字符,同时字符串比较相等或是大小也是通过比较16位值完成的

let str = "❤️";
console.log(str.length);   //=>2

字符串类型包含一些字面量,用于表示非打印字符或其他用途的字符:

\n ---- 换行
\t ---- 制表
\b ---- 退格
\r ---- 回车
\f ---- 换页
\\ ---- 反斜杠
\' ---- 单引号
\" ---- 双引号
\xnn ---- 十六进制编码nn表示的字符(n是十六进制数字0~F),如:\x41表示“A”
\unnnn ---- 十六进制nnnn表示的Unicode字符(n同上)
let str2 = "Hello,\nWorld!";    //=>Hello,
                                //  World!
console.log(1+'hello');    //=>1hello,对数值使用+操作符,数值相加,对字符串使用+操作符,字符串会拼接起来
console.log(12==="12");

模版字面量: 使用反引号(`)定义,相较于其他两种定义方式,模版字面量可包含任意JS表达式,位于${}之间的内容都被当作JS表达式被求值并插入模版中

let a = 1;
let b = 2;
console.log(`a+b=${a+b}`);   //=>a+b=3
console.log(`h ello`.length);  //=>6,模版字面量会保持反引号内部的空格及换行

let c = 3;
console.log(typeof `${c}`); //=>string,任何插入的值都会使用toString()强制转型为字符串

Boolean:

Boolean只有两个字面值:true和false,表示真或假、开或关、是或否,在js中通常是比较操作的结果,这两个布尔值不等同于数值,因此true不等于1,false不等于0

null:

null值表示一个空对象指针类型,即表示某个值不存在,这也是typeof null会返回“object”的原因; 在定义将来要保存对象值的变量时,使用null来初始化,这样只需检查这个变量的值是不是null就可以知道这个变量是否在后来被重新赋值了一个对象的引用,同时保持null是空对象指针的语义,将其与undefined区分开来,因为undefined值是由null值派生而来因此它们在表面上相等,要区分它们必须使用全等(===)

console.log(null==undefined);  //=>true

Undefined:

undefined类型只有一个值,即undefined,当变量仅被声明但没有赋初始值时,就相当于给变量赋值了undefined,没有明确返回值的函数返回值为undefined,它表示某个值不存在,但undefined表示的是一种更深层次的不存在

let mes;
console.log(mes==undefined);   //=>true

let name;
console.log(name);  //=>undefined
console.log(age);   //=>报错,注意:包含undefined值的变量和未定义变量是有区别的

对未声明的变量,只能执行一个有用的操作,即typeof,返回结果为undefined,对未初始化的变量使用typeof时同样返回undefined

let a;
console.log(typeof a);   //=>undefined
console.log(typeof b);   //=>undefined

建议在声明的同时进行初始化,这样,当typeof返回“undefined”时就知道是因为给定的变量未声明,而非声明了未初始化

Symbol:

ES6引入了一种新的原始数据类型Symbol,表示独一无二的值,最大的用法是用来定义对象的唯一属性名。符号是原始值,且符号是唯一、不可变的,Symbol()函数不能用作构造函数,与new关键字一起用

let mySymbol = new Symbol(); //Uncaught TypeError: Symbol is not a constructor

正确的定义方式如下,只要创建Symbol()实例并将其用作对象的新属性,就可以保证它不会覆盖已有的对象属性,无论是符号属性还是字符串属性:

let s1 = Symbol("s");
let s2 = Symbol("s");
console.log(s1 === s2); //=>false

let obj = {};
obj[s1] = "aye";
obj[s2] = "18";
console.log(obj[s1]);    //=>aye
console.log(obj);    //{Symbol(s): 'aye', Symbol(s): '18'}

Object.getOwnpropertySymbols()会返回对象实例的符号属性数组,如果没有显式的保存对对象符号属性的引用,就必须遍历对象的所有符号属性才能找到对应的属性键:

let obj = {
    [Symbol("name")]: "aye",
    [Symbol("name")]: "Czp"
};

console.log(obj[Symbol("name")]); //=>undefined
console.log(Object.getOwnPropertySymbols(obj)); //=>[Symbol(name), Symbol(name)]
console.log(obj[Object.getOwnPropertySymbols(obj)[0]]); //=>aye
console.log(obj[Object.getOwnPropertySymbols(obj)[1]]); //=>Czp


大多数情况下,我们创建的对象Symbol属性字面量也不会如上相同,此时也可以:
let obj1 = {
    [Symbol("name")]: "aye",
    [Symbol("age")]: 18
};
console.log(obj1[Symbol("age")]); //=>undefined
let objKey = Object.getOwnPropertySymbols(obj1).find((s) => {
    return s.toString().match(/age/);
});
console.log(obj1[objKey]);   //=>18

如果需要共享和重用符号实例,可以用一个字符串作为键,在全局符号注册表中创建并重用符号,为此,需要使用Symbol.for()方法:

let str1 = Symbol.for("a");
let str2 = Symbol.for("a");
console.log(str1 === str2); //=>true

二、引用类型(复杂数据类型):

引用类型总体来讲只有Object,是一组数据和功能的集合,它在ES中作为所有对象的基类存在,具体又可细分为下列两部分:

基本引用类型:
    原始值包装类型:NumberStringBoolean
    日期:Date
    正则:RegExp
    单例内置对象:MathGlobal
集合引用类型:
    数组:Array
    定型数组:另一种形式的ArrayBuffer视图
    映射:Map
    弱映射:WeakMap
    集合:Set
    弱集合:WeakSet
    函数:Function

Object

一切对象都继承于Object,都是从Object.prototype继承方法和属性。 一切构造函数包括Object与Function,都继承于Function,最终继承于Object。可以说是先有的Object.prototype, Object.prototype构造出Function.prototype,然后Function.prototype构造出Object和Function。

// 构造函数对象
var a = function(){} // 构造函数对象

// 检查原型链
console.log(a.__proto__ === Function.prototype); //=> true
console.log(Object.__proto__ === Function.prototype); //=> true
console.log(Function.__proto__ === Function.prototype); //=> true
console.log(Function.prototype.__proto__ === Object.prototype); //=> true

所有的Object都有以下属性和方法:

constructor:

用于创建当前对象的函数

let o = new Object();    //对象o就是通过Object的constructor创建,它的值就是Object()函数

hasOwnProperty():

判断目标属性是否是当前对象实例的自身属性(不包括从原型链继承来的)

let obj = {name:'aye'};
console.log(obj.hasOwnProperty('name'));   //=>true

function Person(name) {
    this.name = name;
}
function Boy(age) {
    this.age = age;
}
Boy.prototype = new Person('aye');
let Aye = new Boy(18);
console.log(Aye.name,Aye.age); //=>aye,18
console.log('name' in Aye); //=>true,name存在于Aye的原型链上
console.log(Aye.hasOwnProperty('name')) //=>false
console.log(Aye.hasOwnProperty('age')) //>true

isPrototypeOf():

用于判断当前对象是否是另一个对象的原型

let fun = function () { };
console.log(Function.prototype.isPrototypeOf(fun));  //=>true,Function.prototype就是对象fun的原型
console.log(Object.prototype.isPrototypeOf(fun));   //=>true
console.log(Array.prototype.isPrototypeOf(fun))     //=>false

propertyIsEnumerable():

判断给定的属性是否可以使用,与hasOwnproperty()一样,属性名必须是字符串

let obj = { name: 'aye' };
console.log(obj.propertyIsEnumerable('name'));   //=>true
console.log(obj.propertyIsEnumerable('age'));   //=>false

function Person(name) {
   this.name = name;
}
function Boy(age) {
   this.age = age;
}
Boy.prototype = new Person('aye');
let Aye = new Boy(18);
console.log(Aye.propertyIsEnumerable('name')); //=>false,存在于原型,但不存在于自身
console.log(Aye.propertyIsEnumerable('age')); //=>true

toLocaleString():

返回对象在本地环境的字符串表示

let date = new Date();
console.log(date); //=>Sun Mar 27 2022 19:45:01 GMT+0800 (中国标准时间)
console.log(date.toLocaleString()); //=>2022/3/27 19:45:01

let arr = [1, 2, 3];
console.log(arr); //=>[1,2,3]
console.log(arr.toLocaleString()); //=>1,2,3

toString():

返回对象的字符串表示

let date = new Date();
console.log(date); //=>Sun Mar 27 2022 19:45:01 GMT+0800 (中国标准时间)
console.log(date.toString()); //=>Sun Mar 27 2022 19:47:26 GMT+0800 (中国标准时间)

let arr = [1, 2, 3];
console.log(arr); //=>[1,2,3]
console.log(arr.toString()); //=>1,2,3

valueOf():

返回对象对应的字符串、数值或布尔值表示

let date = new Date();
console.log(date); //=>Sun Mar 27 2022 19:45:01 GMT+0800 (中国标准时间)
console.log(date.valueOf()); //=>1648381781409