【JS】JS数据类型的基本概念、内存、类型判断7种方法和类型转换

242 阅读9分钟

数据类型

最新的 ECMAScript 标准定义了 8 种数据类型:

  • undefined
  • number
  • null
  • string
  • boolean
  • symbol(es6新增)
  • bigInt(es6新增)
  • object JS变量可以保存两种类型(基本数据类型和引用数据类型)的值,对应为原始值和引用值。并且我们需要知道的一点是:JS的变量本身是没有类型的,只有值才有,变量可以随时持有任何类型的值。

基本数据类型

  • undefined
  • number
  • null
  • string
  • boolean
  • symbol(es6新增)
  • bigInt(es6新增) 以上七种为基本数据类型,又称原始数据类型、简单数据类型,我们称这些类型的值为原始值
原始值特性

当我们声明一个变量,将一个值赋给它的时候,JS引擎必须确定这个值是原始值还是引用值。

  • 原始值保存在栈内存中。
  • 保存原始值的变量是按值访问的,我们可以直接操作存储在变量中的值。
  • 原始值是没有属性的。
let person = 'kiko';
// 给一个原始值变量添加属性不会报错
kiko.gender = 'female';
// 没有了。
// 只有引用值才可以动态添加属性
console.log(kiko.gender);/ undefined
  • 通过变量把一个原始值赋值给另一个变量时,原始值会被复制到新变量的位置,两者是完全独立的。
let num1 = 7;
let num2 = num1 // num2也是数值7 num2是num1值的副本
  • 原始值可以通过typeof来确定数据类型。
  • 除 Object 以外的所有类型都是不可变的(值本身无法被改变)。如:JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变

引用数据类型

  • object

任何 constructed 对象实例的特殊非数据结构类型,也用做数据结构:new Object,new Array,new Map,new Set,new WeakMap,new WeakSet,new Date,和几乎所有通过 new keyword 创建的东西。 引用数据类型又称复杂数据类型,包括ObjectArrayMapSetDate等几乎所有可以用new操作符创建的数据类型。我们称这些类型的值为引用值

原始值包装类型(特殊引用类型)

上文提到,几乎用new操作符创建的数据类型都是引用类型,那有人可能会想到StringNumber、和Boolean也可以new出来,但它们是不是引用类型呀?
原来,es为了方便原始值的操作,提供了三种特殊的引用类型(也就是提供了封装对象,也称为原生函数)即:

  • String
  • Number
  • Boolean 这些类型具有引用类型的特点,也具有原始类型的特点,每当用到这三种原始类型的方法或者属性的时候,后台都会创建一个原始包装类型的对象,这使得这三类原始类型也具备了各种方法,如string.substring()
    引用类型和原始值包装类型的主要区别在于对象的生命周期,当我们通过new创建引用类型实例后,引用类型的实例会在离开作用域的时候销毁,而自动创建的原始值包装对象则只存在于访问它的那行代码的执行期间。
    这就是为什么第一个例子给一个字符串添加了属性后,下一行控制台返回的是undefined,因为在执行到下一行的时候,它已经被销毁了。
    下面的代码使用了 instanceof 来证明:String 和 Date 对象同时也属于Object 类型(他们是由 Object 类派生出来的)。这个例子来自mdn。
var simpleStr = "This is a simple string";
var myString  = new String();
var newStr    = new String("String created with constructor");

simpleStr instanceof String; // 返回 false, 非对象实例,因此返回 false
myString  instanceof String; // 返回 true
newStr    instanceof String; // 返回 true
myString  instanceof Object; // 返回 true
引用值特性
  • 引用值是对象,保存在堆内存上,变量保存的是引用值的指针(指向堆内存引用值的地址)。
  • 所以,保存引用值的变量是按引用访问的,在操作对象时,我们实际上操作的是对这个对象的引用。
  • 通过变量把一个原始值赋值给另一个变量时,只会复制引用值的指针,结果就是两者都指向了同一个对象。
  • instanceof可以确定引用值类型。
    需要注意:所有的引用值都是object的实例,所以如果是使用instanceof检测任何引用值和object,都会返回true。
let newArr = [1, 2, 3, 4, 5]
let newObj = {
  name: "zoe",
  age: "18"
}

console.log(newArr instanceof Array) //true
console.log(newObj instanceof Object) //true

扩展:const 定义的值一定是不能改变的吗?

const创建一个值的只读引用。但这并不意味着它所持有的值是不可变的,只是变量标识符不能重新分配。例如,在引用内容是对象的情况下,这意味着可以改变对象的内容(例如,其参数)。

const arr = []
// 可以改变对象的内容
arr.push(1)
console.log(arr) // [ 1 ]

// 无法改变其指针
arr = [1, 2, 3]
console.log(arr) // TypeError: Assignment to constant variable.

数据类型判断

前文提到,原始值可以使用typeof来确定数据类型,引用instanceof可以确定类型,除了这两种之外,本文还总结了五种真实世界中可能会用到的方法:

  • Object.prototype.toString.call()
  • Object.prototype.constructor()
  • Object.prototype.isPrototypeOf()
  • isNaN()
  • isArray()
typeof

此处可直接看MDN,已经讲得很全了。要注意的一点是,typeof对于除 Function 外的所有构造函数的类型都是 'object',也就是说使用new操作符的 new String()new Number()都会判断为'object'截屏2022-01-10 上午10.27.50.png

// 特殊情况
typeof(NaN) // 返回 number
typeof(null) // 返回 object (底层二进制存储问题)
instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。搬一个mdn上的例子:

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
}
var mycar = new Car("Honda", "Accord", 1998);
// Car在mycar的原型链上
var a = mycar instanceof Car;    // 返回 true
// Object在
var b = mycar instanceof Object; // 返回 true
Object.prototype.toString.call()

默认情况下,toString() 方法被每个 Object 对象继承,toString() 方法返回一个表示该对象的字符串。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中 type 是对象的类型。
我们可以用这个方法来检测对象类型,区分Array和Object。

let toString = Object.prototype.toString;

let arr = [1,2,3]
let obj = {}

toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]

// 区分数组和对象
toString.call(arr); // [object Array]
toString.call(obj); // [object object]
Object.prototype.constructor()

返回创建实例对象的构造函数的引用。

var o = {};
o.constructor === Object; // true

var o = new Object;
o.constructor === Object; // true

var a = [];
a.constructor === Array; // true

var a = new Array;
a.constructor === Array // true

var n = new Number(3);
n.constructor === Number; // true
Object.prototype.isPrototypeOf()

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。isPrototypeOf() 与 instanceof 运算符不同。在表达式 "object instanceof AFunction"中,object 的原型链是针对 AFunction.prototype 进行检查的,而不是针对 AFunction 本身。

isNaN()和isArray()

我们可以用这两种方法来便捷判断NaN和Array类型。

let nan = NaN
let arr = [1,2,3]

console.log(Number.isNaN(nan)) // true
console.log(Array.isArray(arr)) // true
扩展:Array和null的判断?

这里列出了Arraynull两种相对典型的类型,搞懂这两个,基本别的也没问题了。
由于objectarray的原型链上,所以用instanceof无法区分这两个,同理其他一切引用类型。下列判断objectarray的方法也适用于区分其他可以new出来的类型和对象。
null会被typeof判断为object,我们也可以用其他方法对null实现确定。

// 判断数组
let arr = [1,2,3]

console.log(Array.isArray(arr)) // true
console.log(Object.prototype.toString.call(arr)) // [object Array]
console.log(arr instanceof Array) // true
console.log(arr instanceof Object) // true (object在array的原型链上,所以instanceof无法区分数组和对象)
console.log(arr.constructor === Array) // true
console.log(Array.prototype.isPrototypeOf(arr)) //true

// 判断null
let a = null;

console.log(Object.prototype.toString.call(a)); //[object Null]
console.log(!a && typeof a === "object"); // true 复合判断
console.log(typeof null) // object 所以typeof无法判断null

类型转换

较为常见的是数字和字符串、布尔值和非布尔值的转换,也覆盖了大部分场景。

数字和字符串的转换

ES5中定义ToNumber,其中:

  • true -- 1
  • false -- 0
  • undefined -- NaN
  • null -- 0 对于对象(包括数组),会执行这样的操作:
  1. 转换为相应的基本类型值。(见下)
  2. 如果是非数字类型的基本类型值,强制转换为数字,若处理失败返回NaN。 转换为相应的基本类型值的操作通过ToPrimitive完成:
  3. 检查这个对象有没有valueOf()方法,有的话就返回基本类型值,并且使用这个值进行强制类型转换。
  4. 如果没有就执行toString()并用其返回值来进行强制类型转换。(除非自行定义,返回[[class]]里面的值)
  5. 如果valueOf()toString()都不返回基本类型值,就TypeError了。 重要-,/,*是数字运算符,这三个运算符只适用于数字。+既可以执行字符串拼接操作也可以执行数字加法。
// 字符串
let a = "2"
let b = a - 0

b // 2 

// 布尔值
let a = false;
console.log(a + '1'); // false1
console.log(a + 1); // 1

// 对象
let a = [2]
let b = [1]
a - b // 1
// 对象先转换为字符串"2" - "1",再转换为数字。

// + 的使用 重要
let a = [1,2]
let b = [3,4] 
console.log(a + b) // "1,23,4"
// 如上所述,对于对象,会先转换为基本类型,本例中执行toString()
// 于是就是"1,2" + "3,4",而在 + 运算中,一个操作符只要有一端为字符串的话就是字符串拼接,否则就是数字加法。
布尔值的转换

如果以Boolean为分类标准,那么JS中的所有值可以分为两种:

  • 假值:可以被强制类型转换为false的值。
  • 真值:其他。(true) 也就是说,除了假值之外,都是真值。我们只需要记住假值就行,以下五类为假值:
  • undefined
  • null
  • false
  • +0,-0,NaN
  • "" 也就是说,除了这五种,其他的值再像“假值”,也是真值。如:
let a = "false"
let b = []
let c = {}
let d = " "

console.log(Boolean(a && b && c && d)) // true 全是真值

常见的布尔值转换见于条件判断式和逻辑运算符中,如if()、for(),while(),三元判断式,逻辑运算符或、和逻辑运算符与,在这种情况中非布尔值会转换为布尔值,遵循上述的假值和真值转换。

结语

后续可能还会对常见的类型转换和类型判断题进行添加。欢迎各位提出意见,共同进步!希望我的文章能帮助大家~~~