JavaScript系列 -- 数据类型

242 阅读9分钟

JS原始数据类型有哪些?

在 JS 中,存在着 7 种原始值,分别是:

  • Number
  • String
  • Boolean
  • null
  • Undefined
  • Symbol
  • Bigint

变量声明 var、let、const的区别

  1. 作用域不同 let、const有块级作用域,而var没有

块级作用域就是if(){}和for(){}和{}(不包括function(){})都叫一个块,在这之中定义的所有变量在代码块外都是不可见的,我们称之为块级作用域

{
    let i = 1
    var j = 2
}
console.log(i) // undefined
console.log(j) // 2
  1. let不能被重新定义,const不能被重新定义和赋值,但是var是可以的
  2. let、const不能在定义之前访问该变量,但是var支持变量提升
console.log(i); // 输出 undefined,而不是报错
{
    var i = 5;
}
  1. 暂存性死区 暂存性死区是相对于某一个变量来说的,就是在定义该变量之前的区域就是暂存性死区
【死区里边拿不到`外边`的i,也拿不到`本代码块内`的i】
const i = 1
{
    ---------------------------------------------
    console.log(i) // 死区,报错:Uncaught ReferenceError: Cannot access 'i' before initialization
    ---------------------------------------------
    const i = 2 
    console.log(i) //直到这里才能正常使用 i 
}

变量初始化

一般我们会知道声明该变量将得到的值是什么类型:

  • 如果是数字就用0
  • 是数组用[]
  • 是object用{}或null
  • 是string用''
  • 是boolean值用false
  • 如果未知类型可以不赋值,或者统一用null来赋初值,表示这个变量是要来存放对象的(在JS中万物皆对象)

关于NaN

  • 含义:Not a Number ,例如Number('a')会得到NaN
  • 特性:NaN 不等于其本身
  • 检测:
    • Number.isNaN():判断是否为 NaN
    • isNaN():判断能否转换为数值
      • true:不可以转换为数值,如:NaN、含有非数字字符的string等。isNaN('84a') === true
      • false:可以转换为数值,如number、只含有数字字符的string等。isNaN('12') === false

为什么 0.1 + 0.2 != 0.3 ?怎么解决?

  • 原因:计算机在计算0.1+0.2时会把0.1和0.2分别转换为二进制数后再相加,而0.1和0.2在转换成二进制后会无限循环,此时由于位数限制会把多余的位数截掉,此时就已经出现了精度的损失,所以相加得到的二进制数转换为十进制后就会变成0.30000000000000004
  • 解决:
    • 乘上10的N次方,小数点后几位N就多大
// 先转换为字符串然后split取小数部分求length
function add(num1, num2) {      
    let len1 = (num1.toString().split('.')[1] || '').length; // 小数点的位数   
    let len2 = (num2.toString().split('.')[1] || '').length;     
    let max = Math.pow(10,Math.max(len1, len2));   // 10的小数点最长长度次幂 
    return (num1 * max + num2 * max) / max;
}
  • 另外并不是所以的浮点数运算都会这样,出现这个的本质原因是十进制转二进制时会出现循环导致的 image.png

string 有哪些常见函数

关于 String 的方法详解可以看 JavaScript系列 -- String 详解

正则表达式

null 和 undefined 的区别

  • 定义上:
    • undefined 表示未定义,是已声明但未赋值变量的默认值
    • null 表示不存在,表示尚未创建的对象,空值、空指针、空数组
  • 例子:
    • a[3]=[1,2,3],a[6]就是 undefined,也就是说数组/字符串越界区域就是 undefined
    • 链表尾结点的next为 null,表示空指针,叶子节点的left、right为null,表示没有左、右子节点

null 是对象吗?为什么?

结论: null 不是object

解释: 虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。 JS 为了性能考虑使用低位存储变量的类型信息000 开头代表是对象然而 null 表示为全零,所以将它错误的判断为 object

Symbol 数据类型

  • 创建symbol值:var sym = Symbol("id")
  • 生成的Symbol值都是唯一的: console.log(Symbol('foo') === Symbol('foo')); // false
  • 其值对于开发者来说是看不见的:console.log(sym) // Symbol()
  • 应用:利用其唯一性可以做哈希表的key

Bigint 数据类型

BigInt 是一种内置对象,它提供了一种方法来表示大于 2的53次方 - 1 的整数。这原本是 Javascript中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数。

如何判断两个值是否相等

== 、 === 、Object.is() 三种判断方式

  • " == " 运算符在判断相等前对两边的变量(如果它们不是同一类型) 进行强制类型转换
例如:
null == undefined // true

即使是同一类型,也会出现“错误”的时候:

[] == [] // false
{} == {} // false
NaN == NaN // false

出现该情况的原因是:[ ]、{ }是空对象,JS语言比较的是堆内存的地址,而两个空对象(数组)分配的地址不一样,故为 false,所以不能用于判断出空数组空对象

  • 判断空数组我们是采用arr.length === 0来判断是否为空数组
  • 判断空对象可以采用arr = Object.getOwnPropertyNames(obj)arr = Object.keys()获得 一个由属性名称组成的数组 再结合 arr.length === 0来判断是否为空对象
  • " === " 不会进行强制类型转换,但无法判断出 +0 和 -0 是不相同的,NaN 和 NaN 是相等的
+0 === -0 // true
NaN === NaN // false
  • Object.is()(" === " 的改进 ),可以判断出 +0 和 -0 是不相同的,NaN 和 NaN 是相等的
Object.is(+0,-0) // false
Object.is(NaN,NaN) // true

如何判定数据类型?

【typeof 数据类型】 无法判断 null 和 array (都会显示 "object")

对于原始类型来说,除了 null 都可以调用typeof显示正确的类型 —— 注意是返回字符串

typeof 1 // "number" 
typeof NaN // "number"  所以无法区分NaN和Number
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'

typeof null // "object"

对于引用数据类型,除了函数之外,都会显示"object"

typeof [] // 'object'
typeof {} // 'object'
typeof function(){} // 'function'

【变量 instanceof 数据类型】 能否判断基本数据类型?

instanceof 是基于原型链判断数据类型,本质是判断某个对象是否为另一个对象(构造方法)的实例 其中另一个对象是指 Object、Array、Function、String、Number、构造函数等等

1. instanceof 方法无法用于判断基本数据类型

var str = 'hello world'
str instanceof String // false

var num = 5
num instanceof Number // false

var b = false
b instanceof Boolean // false

但有特殊例子:null。借此方法可将 null{} 区分开,弥补 typeof 方法无法区分 null 和 对象 的缺陷

var n = null
n instanceof Object // false

2. 但 instanceof 方法能用于:

  • 判断 使用 new 方式创建的 基本数据类型
var str = new String('hello world')
str instanceof String // true

var num = new Number(5)
num instanceof Number // true

var b = new Boolean(false)
b instanceof Boolean // true

其实从控制台也可以看出,用 new 创建出来的基本数据类型的变量是不一样的:

image.png

  • 判断 引用类型
var obj = {}
obj instanceof Array // false
obj instanceof Object // true

var arr = []
arr instanceof Array // true
arr instanceof Object // true

通过 xxx instanceof Array ? (xxx 是数组) : (xxx 是对象) 来区分数组和对象,弥补 typeof 方法无法区分数组和对象的缺陷

但是 xxx instanceof Object 无论 xxx 是数组还是对象都会得到 true,这是为什么?我们在控制台打印出数组,展开查看其原型,发现原型链上存在一个Object,这个Object便是Object.prototype,而 instanceof 方法又是基于 原型链 的查询,所以 arr instanceof Object 会返回 true

微信截图_20210622212858.png

  • 判断一个 实例对象 是否为一个 构造函数 创建出来的
const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

实现一个 instanceof 方法

基于 instanceof 是查找一个对象 obj 的原型链而判断另一个对象 Obj 是否处于其原型链上的原理,我们可以自己实现一个 instanceof 方法:

function MyInstanceof(obj, OBJ){
    if(typeof OBJ != 'function') throw new Error('instance error');   // 构造函数不是函数报错
    if(!obj || (typeof obj != 'object' && typeof OBJ != 'object')) return false;  // 没有实例或实例不是引用类型返回 false
   
    while (obj.__proto__){  // 核心代码:采用迭代的方式沿着原型链往上爬,爬一次判断一次
        if(obj.__proto__ === OBJ.prototype) return true;
        obj = obj.__proto__;
    } // 最终 obj.__proto__ 是 null 时退出循环
    return false;
}

函数测试:

image.png

【Object.prototype.toString.call()】 —— 获取具体的数据类型

利用 Object.prototype.toString 返回实例所属类的信息,通过改变 toString 中的 this 指向来返回指定参数的类型

Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(Symbol('xxx')) // "[object Symbol]"
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call({}) // "[object Object]"
Object.prototype.toString.call(function(){console.log('xxx')}) // "[object Function]"
Object.prototype.toString.call(new Date) // "[object Date]"
Object.prototype.toString.call(Math) // "[object Math]"

对Object.prototype.toString.call()获取的结果进行字符串的截取,因而实现封装函数 getType()

// 【从第8个截取到倒数第二个】
function getType(element) {    
    return Object.prototype.toString.call(element).slice(8, -1);
}

数据类型之间的转换

JS中类型转换有哪几种?

JS中,类型转换只有三种:

  • 转换成数字 Number()
  • 转换成布尔值 Boolean()
  • 转换成字符串 String() 和 .toString()
String()可以将null和undefined转换为字符串,但是没法转进制字符串
.toString()不能将null和undefined转换为字符串
.toString() 括号中的可以写一个数字,代表进制,对应进制字符串:
二进制:.toString(2); 八进制:.toString(8); 十进制:.toString(10); 十六进制:.toString(16);

转换规则如下:

两个值比较之前的数据类型转换(易错点)

前面说到 == 运算符在判断两个值是否相等前如果两个值的数据类型不同,则会对两边的变量进行强制类型转换(可能是一个也可能是两个)。其实 != 、> 、< 、>= 、 <= 等运算符在判断之前也会这样做,那其结果又是如何,所以我们要清楚其中是怎么转换的

观察上面转换规则的那张图,除去三个类型之前的转换,我们可以得到这些结论:

  • undefined、null => false
  • 引用类型 => true
  • arr =>

看几个需要清楚的场景:

"1" === 1 // false
+"1" === 1 // true
-"1" === -1 // true
"-1" === -1 // false

1 == true0 == false // true
1 === true0 === false // false
5 == true // false
true > false // true
1 > false // true
true > 0 // true

"1" == true"0" == false // true
"1" === true"0" === false // false

"" == false // true
"" === false // false

"" == undefined // false
"" == null == null // false

undefined == false // false
null == false // false
undefined == null // true
undefined === null // false

附录

变量提升、声明提升

(这块内容来自 原文地址

JavaScript中变量声明与函数声明都会被提升到作用域顶部,优先级依次为:变量声明->函数声明->变量赋值

var的变量提升

console.log(a); // undefined
var a = 1;
console.log(a); // 1
// console.log(b); // ReferenceError: b is not defined

为了显示ab的区别,打印了一下未声明的变量b,其抛出了一个ReferenceError异常,而a并未抛出异常,其实对a的定义并赋值类似于以下的操作,将a的声明提升到作用域最顶端,然后再执行赋值操作。

var a;
console.log(a); // undefined
a = 1;
console.log(a); // 1

函数声明提升

函数声明会将声明与赋值都提前,也就是整个函数体都会被提升到作用域顶部。

s(); // 1
function s(){
    console.log(1);
}

函数表达式只会提升变量的声明,本质上是变量提升并将一个匿名函数对象赋值给变量。

console.log(s); // undefined
var s = function s(){
    console.log(1);
}
console.log(s);  // f s(){console.log(1);}

优先级

var s = function(){
    console.log(0);
}
function s(){
    console.log(1);
}
s(); // 0

其在JS引擎的执行的优先级是 变量声明->函数声明->变量赋值

var s; // 1.变量声明
function s(){ // 2.函数声明
    console.log(1);
}
s = function(){ // 3.变量赋值
    console.log(0);
}
s(); // 0

参考文章

(建议收藏)原生JS灵魂之问, 请问你能接得住几个?(上)