都2022年了你不会还没搞懂JS数据类型吧

简介: 众所周知,JavaScript 是一门弱类型语言,不对变量进行类型强制,变量可以随时变成任何类型的值。这样既带来的灵活性也带来了不可控性,往往容易导致一些奇怪的bug。今天这篇文章主要从JS数据类型、JS数据类型的判断方法、JS数据类型的相互转换三个方面介绍,让你彻底弄懂JS数据类型。如果看完这篇文章还搞不懂数据类型你来打我。好了,下面咱们开始吧!
文章里的每个案例都是我亲自编写并验证的,建议阅读文章时,可以在浏览器执行案例,会更有利于理解。

image.png

JS数据类型

在JS中数据类型总共分为基本数据类型和引用数据类型,

七种基本数据类型

NumberStringBooleanNullUndefinedSymbolBigInt

引用数据类型

主要有ObjectArrayDateErrorFunctionRegExp

我相信JS数据类型各位都已经烂熟于心,笔者就不再详细举例了,在这里笔者只强调一点就是内存空间的存储。

基本数据类型保存在栈内存中,因为基本数据类型占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。

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

下面我用一个简单的例子给大家讲解清楚。

let a = 1;
let b = a; // 在栈中开辟了一块新的内存空间并赋值为1。
b = 2; // b变为2并不会影响到a的值。
console.log(a, b);  // 输出1 2

let c = {num: 1};
let d = c; // 这里d其实是指向了堆中同一个地址。
d.num = 2; // 所以这里d改变了,c也会跟着变。
console.log(c.num, d.num); // 输出 2 2

这里有小伙伴们就要问啦,我们怎么样才能使引用数据类型像基本数据类型一样赋值呢?这里就涉及到深拷贝和浅拷贝了,后面笔者会出专门的文章来细说。

JS数据类型的判断方法

说完JS的数据类型,接下来就是JS数据类型的判断方法啦。判断JS数据类型有很多种方式,常见的有typeofinstanceofObject.prototype.toString.call()constructor.nameconstructor.toString().indexOf()虽然都是判断数据类型但各自又有各自的特点。

typeof

  1. typeof获取的是小写的字符串类型。
  2. typeof 判断基本数据类型的时候除了 null 会判断成 object 其他的都准确。
  3. typeof 判断引用数据类型除了 function 会判断成 function 其他的引用数据类型都会被判断成 object

下面我用例子给大家说明

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

// typeof获取的是数据类型的小写
const typeOfTest = () => {
  console.log("typeof 1:", typeof 1); //number
  console.log("typeof a:", typeof "a"); //string
  console.log("typeof false:", typeof false); //boolean
  console.log("typeof undefined:", typeof undefined); //undefined
  console.log("typeof Symbol():", typeof Symbol()); //symbol
  console.log("typeof 9007199254740991n:", typeof 9007199254740991n); //bigint
  console.log("typeof BigInt('9007199254740991'):",typeof BigInt("9007199254740991")); //bigint

  // typeof NaN是number
  console.log("typeof NaN:", typeof NaN); //number
  
  // typeof 判断基本数据类型的时候除了 null 会判断成 object 其他的都准确。
  console.log("typeof null:", typeof null); //object

  // typeof 判断引用数据类型除了 function 会判断成 function 其他都会被判断成 object。
  console.log("typeof new Number(1):", typeof new Number(1)); //object
  console.log("typeof new String('a'):", typeof new String("a")); //object
  console.log("typeof new Boolean(false):", typeof new Boolean(false)); //object
  console.log("typeof new Date():", typeof new Date()); //object
  console.log("typeof []:", typeof []); //object
  console.log("typeof new Array(10):", typeof new Array(10)); //object
  console.log("typeof new Error('自定义错误'):",typeof new Error("自定义错误")); //object
  console.log("typeof user:", typeof user); //object
  console.log("typeof User:", typeof User); //function
  console.log("typeof new RegExp('[0-9]?'):", typeof new RegExp('[0-9]?')); //object
  console.log("typeof /[0-9]?/:", typeof /[0-9]?/); //object
};

typeOfTest();

instanceof

  1. instanceof获取的是true或者false。
  2. instanceof 只能判断出引用数据类型和自定义引用数据类型不能判断基本数据类型

下面我用例子给大家说明

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

const instanceofTest = () => {
  console.log("1 instanceof Number:", 1 instanceof Number); //false
  console.log("a instanceof String:", "a" instanceof String); //false
  console.log("true instanceof Boolean:", true instanceof Boolean); //false
  console.log("null instanceof Object:", null instanceof Object); //false
  console.log("undefined instanceof Object:",undefined instanceof Object); //false
  console.log("Symbol() instanceof Symbol:", Symbol() instanceof Symbol); //false
  console.log("9007199254740991n instanceof BigInt:",9007199254740991n instanceof BigInt); //false
  console.log("BigInt('9007199254740991') instanceof BigInt:",BigInt("9007199254740991") instanceof BigInt); //false

  // 引用数据类型和自定义引用数据类型都可以判断正确
  console.log("user instanceof User:", user instanceof User); //true
  console.log("new Number(1) instanceof Number:",new Number(1) instanceof Number); //true
  console.log("new String('a') instanceof String:",new String("a") instanceof String); //true
  console.log("new Boolean(false) instanceof Boolean:",new Boolean(false) instanceof Boolean); //true
  console.log("new Date() instanceof Date:", new Date() instanceof Date); //true
  console.log("[] instanceof Array:", [] instanceof Array); //true
  console.log("new Array(10) instanceof Array:",new Array(10) instanceof Array); //true
  console.log("User instanceof Function:", User instanceof Function); //true
  console.log("new Error('自定义错误') instanceof Error:",new Error("自定义错误") instanceof Error); //true
  console.log("new RegExp('[0-9]?') instanceof RegExp:",new RegExp("[0-9]?") instanceof RegExp); //true
  console.log("/[0-9]?/ instanceof RegExp:", /[0-9]?/ instanceof RegExp); //true
};

instanceofTest();

instanceof 其内部运行机制是判断在其原型链中能否找到该类型的原型。大概原理如下。这里会涉及到原型和原型链相关知识,不懂的话可以看看笔者的原型和原型链文章。

const instance_of_test = (leftVaule, rightVaule) => {
  let leftVaule = leftVaule.__proto__; // 取左表达式的__proto__值
  let rightProto = rightVaule.prototype; // 取右表达式的 prototype 值
  while (true) {
    if (leftVaule === null) {
      return false;
    }
    if (leftVaule === rightProto) {
      return true;
    }
    leftVaule = leftVaule.__proto__;
  }
};

因为instanceof的原理是通过原型链来查找,所以下面的例子也是正确的。

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

const instanceofTest2 = () => {
  console.log("user instanceof Object:", user instanceof Object); //true
  console.log("new Number(1) instanceof Object:",new Number(1) instanceof Object); //true
  console.log("new String('a') instanceof Object:",new String("a") instanceof Object); //true
  console.log("new Boolean(false) instanceof Object:",new Boolean(false) instanceof Object); //true
  console.log("new Date() instanceof Object:",new Date() instanceof Object); //true
  console.log("[] instanceof Object:", [] instanceof Object); //true
  console.log("new Array(10) instanceof Object:",new Array(10) instanceof Object); //true
  console.log("User instanceof Object:", User instanceof Object); //true
  console.log("new Error('自定义错误') instanceof Object:",new Error("自定义错误") instanceof Object); //true
  console.log("new RegExp('[0-9]?') instanceof Object:",new RegExp("[0-9]?") instanceof Object); //true
  console.log("/[0-9]?/ instanceof Object:", /[0-9]?/ instanceof Object); //true
};

instanceofTest2();

Object.prototype.toString.call()

  1. Object.prototype.toString.call()获取的是[object xxx]字符串,我们可以通过slice(8, -1)获取xxx来的带真实数据类型(大写)。
  2. Object.prototype.toString.call()能判断 js 内置的基本数据类型和引用数据类型。但是不能判断出自定义对象的数据类型。

下面我用例子给大家说明

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

const toStringCallTest = () => {
  // 基本数据类型都能正确判断
  console.log(
    "Object.prototype.toString.call(1):",
    Object.prototype.toString.call(1)
  ); //[object Number]
  console.log(
    "Object.prototype.toString.call('a'):",
    Object.prototype.toString.call("a")
  ); //[object String]
  console.log(
    "Object.prototype.toString.call(false):",
    Object.prototype.toString.call(false)
  ); //[object Boolean]
  console.log(
    "Object.prototype.toString.call(null):",
    Object.prototype.toString.call(null)
  ); //[object Null]
  console.log(
    "Object.prototype.toString.call(undefined):",
    Object.prototype.toString.call(undefined)
  ); //[object Undefined]
  console.log(
    "Object.prototype.toString.call(Symbol()):",
    Object.prototype.toString.call(Symbol())
  ); // [object Symbol]
  console.log(
    "Object.prototype.toString.call(12312321324234234234234n):",
    Object.prototype.toString.call(12312321324234234234234n)
  ); // [object BigInt]
  console.log(
    "Object.prototype.toString.call(BigInt('12312321324234234234234')):",
    Object.prototype.toString.call(BigInt("12312321324234234234234"))
  ); // [object BigInt]

  // 自定义引用数据类型判断不出来
  console.log(
    "Object.prototype.toString.call(user):",
    Object.prototype.toString.call(user)
  ); //[object Object] 得到的是Object而不是User

  // 引用数据类型都能正确判断
  console.log(
    "Object.prototype.toString.call(new Number(1)):",
    Object.prototype.toString.call(new Number(1))
  ); //[object Number]
  console.log(
    "Object.prototype.toString.call(new String('a')):",
    Object.prototype.toString.call(new String("a"))
  ); //[object String]
  console.log(
    "Object.prototype.toString.call(new Boolean(false)):",
    Object.prototype.toString.call(new Boolean(false))
  ); //[object Boolean]
  console.log(
    "Object.prototype.toString.call(new Date()):",
    Object.prototype.toString.call(new Date())
  ); //[object Date]
  console.log(
    "Object.prototype.toString.call([]):",
    Object.prototype.toString.call([])
  ); //[object Array]
  console.log(
    "Object.prototype.toString.call(new Array(10)):",
    Object.prototype.toString.call(new Array(10))
  ); //[object Array]
  console.log(
    "Object.prototype.toString.call(User):",
    Object.prototype.toString.call(User)
  ); //[object Function]
  console.log(
    "Object.prototype.toString.call(new Error('自定义错误')):",
    Object.prototype.toString.call(new Error("自定义错误"))
  ); //[object Error]
  console.log(
    "Object.prototype.toString.call(new RegExp('[0-9]?'))",
    Object.prototype.toString.call(new RegExp("[0-9]?"))
  ); //[object RegExp]
  console.log(
    "Object.prototype.toString.call(/[0-9]?/)",
    Object.prototype.toString.call(/[0-9]?/)
  ); //[object RegExp]
};

toStringCallTest();

我们可以使用slice(8, -1)方法来一次性获取数据类型,比如

Object.prototype.toString.call(new Error("自定义错误")).slice(8, -1); //输出Error

constructor.name

  1. 获取的是大写的字符串类型。
  2. 对象.constructor.name 既能获取基本数据类型又能获取引用数据类型还能获取自定义引用数据类型。
  3. 因为nullundefined没有constructor 所以不能进行判断。
  4. 由于对象的constructor可以改变,所以判断不一定准确,需要特别注意。后面我们举例讲解。

下面我用例子给大家说明

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

const constructorNameTest = () => {
  // 基本数据类型
  console.log("(1).constructor.name:", (1).constructor.name); //Number
  console.log("(a).constructor.name:", "a".constructor.name); //String
  console.log("(false).constructor.name:", false.constructor.name); //Boolean
  // console.log("null.constructor.name:", null.constructor.name); //报错
  // console.log("undefined.constructor.name:", undefined.constructor.name); //报错
  console.log("Symbol().constructor.name:", Symbol().constructor.name); //Symbol
  console.log(
    "(12312321324234234234234n).constructor.name:",
    12312321324234234234234n.constructor.name
  ); //BigInt
  console.log(
    "BigInt('12312321324234234234234').constructor.name:",
    BigInt("12312321324234234234234").constructor.name
  ); //BigInt

  // 自定义引用数据类型
  console.log("user.constructor.name:", user.constructor.name); //User

  // 引用数据类型
  console.log(
    "new Number(1).constructor.name:",
    new Number(1).constructor.name
  ); //Number
  console.log(
    "new String('a').constructor.name:",
    new String("a").constructor.name
  ); //String
  console.log(
    "new Boolean(false).constructor.name:",
    new Boolean(false).constructor.name
  ); //Boolean
  console.log(
    "new Date().constructor.name:",
    new Date().constructor.name
  ); //Date
  console.log("[].constructor.name:", [].constructor.name); //Array
  console.log(
    "new Array().constructor.name:",
    new Array().constructor.name
  ); //Array
  console.log("User.constructor.name:", User.constructor.name); //Function
  console.log(
    "new Error('自定义错误').constructor.name:",
    new Error("自定义错误").constructor.name
  ); //Error
  console.log("/[0-9]?/.constructor.name", /[0-9]?/.constructor.name); //RegExp
  console.log(
    "new RegExp('[0-9]?').constructor.name",
    new RegExp("[0-9]?").constructor.name
  ); //RegExp
};

constructorNameTest();

由于对象的constructor可以改变所以我们使用constructor.name判断数据类型的时候要注意是否改变了constructor。下面我用例子说明。

const num = new Number(1);
console.log("num.constructor.name:", num.constructor.name); // 输出 Number

function Person() {}
// 改变constructor
num.constructor = Person;
console.log("num.constructor.name:", num.constructor.name); //输出 Person 所以如果constructor被改变了就不准确了。

封装一个 javascript 的类型判断函数

通过上面的学习,现在我们自己动手来封装一个类型判断函数。

function getType(value) {
  // 首先判断数据是 null 的情况,如果是拼上空字符串返回
  if (value === null) {
    return value + "";
  }

  // 判断数据是引用类型的情况,不是引用数据类型就直接用typeof
  if (typeof value === "object") {
    // 内置引用数据类型都可以使用Object.prototype.toString.call
    let valueClass = Object.prototype.toString.call(value);

    // 如果判断是Object,再使用constructor.name进一步判断
    if (valueClass.includes("Object")) {
      return value.constructor.name.toLowerCase();
    } else {
      // 转成数组
      let type = valueClass.split(" ")[1].split("");

      // 去除末尾的 ]
      type.pop();

      // 转成字符串并转成小写
      return type.join("").toLowerCase();
    }
  } else {
    // 判断数据是基本数据类型的情况和函数的情况
    return typeof value;
  }
}

JS数据类型转换

JS数据类型的转换分为 其他值到布尔类型的转换、其他值到字符串类型的转换、其他值到数值类型的转换。

其他值到布尔类型的值的转换

全局方法Boolean()new Boolean() 可以将其他值转换为布尔类型。

  1. 除了这些假值:undefinednullfalse0+0-0NaN"" 布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。
  2. Symbol 可以被强制类型转换为布尔值(显式和隐式结果都是 true )。
const convertBooleanTest = () => {
  console.log("Boolean(1):", Boolean(1)); //true
  console.log("Boolean('a'):", Boolean('a')); //true
  console.log("Boolean(Symbol()):", Boolean(Symbol())); //true
  console.log("Boolean(' '):", Boolean(" ")); //true

  console.log("Boolean(false):", Boolean(false)); //false
  console.log("Boolean(undefined):", Boolean(undefined)); //false
  console.log("Boolean(null):", Boolean(null)); //false
  console.log("Boolean(0):", Boolean(0)); //false
  console.log("Boolean(-0):", Boolean(-0)); //false
  console.log("Boolean(+0):", Boolean(+0)); //false
  console.log("Boolean(NaN):", Boolean(NaN)); //false
  console.log("Boolean(''):", Boolean("")); //false
};

convertBooleanTest();

其他值到字符串类型的转换

全局方法 String() 或者 new String() 可以将其他值转换为字符串。或者使用隐式转换通过 + ""

  1. Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined"。
  2. Boolean 类型,true 转换为 "true",false 转换为 "false"。
  3. Number 类型的值直接转换字符串值。
  4. Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  5. 对于对象来说,会调用 toString()来返回内部属性 [[Class]] 的值,如[object Object]。如果对象有自定义的 toString() 方法,并且返回的不是基本数据类型则会继续调用valueOf()方法。如果valueOf()方法返回的还不是基本数据类型则报错Uncaught TypeError: Cannot convert object to primitive value,如果是基本数据类型则将该值转换为String类型并输出。
const convertStringTest = () => {
  console.log("String(null):", String(null)); // null
  console.log("String(undefined):", String(undefined)); // undefined
  console.log("String(1):", String(1)); // 1
  console.log("String(false):", String(false)); // false
  console.log("String(1112323212424234n):", String(1112323212142423114n)); //1112323212142423114
  console.log("String(Symbol()):", String(Symbol())); //Symbol()
  // console.log("Symbol() + '':", Symbol() + ""); //会报错
  console.log("String({}):", String({})); //[object Object]
  
  // 从这里可以看出Error构造函数已经重写了toString()方法
  console.log(
    "String(new Error('自定义错误')):",
    String(new Error("自定义错误"))
  ); //Error: 自定义错误
};

convertStringTest();

下面我们详细介绍下对象类型转字符串的过程。感兴趣的小伙伴可以分别注释返回值来进行测试。

const converStringTest2 = () => {
  function Animal(name) {
    this.name = name;
  }
  // 重写 toString 方法
  Animal.prototype.toString = function () {
    console.log("toString 先调用");
    return { name: "toString" }; //返回的不是基本数据类型则继续调用valueOf方法
    // return "toString 方法的返回值"; // 返回的是基本类型则直接返回不再调用valueOf方法
  };
  // 重写 valueOf 方法
  Animal.prototype.valueOf = function () {
    console.log("valueOf 后调用");
    // return { name: "valueOf" }; // 返回的还不是基本数据类型,所以报错
    return "valueOf 方法的返回值"; //返回的是基本数据类型则将该值直接返回
  };
  
  const dog = new Animal("dog");
  console.log("String(dog):", String(dog));
  
  // 依次输出 toString 先调用、valueOf 后调用、valueOf 方法的返回值
}

converStringTest2()

其他值到数值类型的转换

全局方法 Number()new Number() 可以将字符串转换为数字。

  1. undefined 类型的值转换为 NaN。
  2. Null 类型的值转换为 0。
  3. Boolean 类型的值,true 转换为 1,false 转换为 0。
  4. String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
  5. BingInt类型转换为Number的时候会精度丢失。
  6. Symbol 值不能够被转换为数字(显式和隐式都会产生错误)。
  7. 对于对象类型会首先调用 valueOf() 方法。如果没有返回基本类型值,就继续调用 toString()。如果 valueOf()toString() 均不返回基本类型值,则会报错Uncaught TypeError: Cannot convert object to primitive value。如果返回了基本数据类型的值,就将该值转换成Number类型并输出。
const convertNumberTest = () => {
  console.log("Number(undefined):", Number(undefined)); // NaN
  console.log("Number(null):", Number(null)); // 0
  console.log("Number(true):", Number(true)); // 1
  console.log("Number(false):", Number(false)); // 0
  console.log("Number(123):", Number("123")); // 123
  console.log("Number('123a'):", Number("123a")); // NaN
  console.log("Number(''):", Number("")); // 0
  console.log("Number(1112323212424234n):", Number(1112323212142423114n)); //1112323212142423200
  // console.log("Number(Symbol()):", Number(Symbol())); //会报错
};

下面我们详细介绍下对象类型转字符串的过程。感兴趣的小伙伴可以分别注释返回值来进行测试。

function User(name, age) {
  this.name = name;
  this.age = age;
}
const user = new User("randy", 24);

const convertNumberTest2 = () => {
  // 例子1
  console.log("Number(user):", Number(user)); //NaN
  // 先调用valueOf()返回的是User { name: 'randy', age: 24 }而不是基本数据类型就再调用toString()
  console.log("user.valueOf():", user.valueOf()); //User { name: 'randy', age: 24 }
  // 调用toString返回的是[object Object],但是转字符串变成NaN,所以返回值是NaN。
  // 如果调用toString()不是基本数据类型就报错
  console.log("user.toString():", user.toString()); //[object Object]

  // 例子2
  function Animal(name) {
    this.name = name;
  }
  Animal.prototype.toString = function () {
    console.log("toString 后调用");
    // return { name: "toString" }; // 返回引用数据类型会报错
    // return "toString 方法的返回值"; //是基本数据类型 但是转成Number类型得到NaN
    return 123; //是基本数据类型 直接返回123
  };
  Animal.prototype.valueOf = function () {
    console.log("valueOf 先调用");
    return { name: "valueOf" }; //不是基本数据类型继续调用toString方法
    // return "valueOf 方法的返回值"; //是基本数据类型 但是转成Number类型得到NaN
  };
  const dog = new Animal("dog");
  console.log("Number(dog):", Number(dog)); //123

  // 依次输出 valueOf 先调用、toString 后调用、123
};

convertNumberTest2();

讲到这好奇宝宝们是不是又要问一会先调用toString方法一会儿先调用valueOf方法,这里到底是什么逻辑呢?别急,接下来笔者就会说到。

toString、valueOf、toPrimitive的执行原理

  1. 对象输出的时候会调用 valueOf() 方法(除了 new Date 对象会调用 toString()方法),返回对象的原始值。
  2. 对象在转换基本类型时,会调用 valueOf()toString(),先调用哪个方法,主要是要看这个对象倾向于转换为什么。
  • 如果倾向于转换为 Number 类型的,就优先调用 valueOf();如果倾向于转换为 String 类型,就先调用 toString()
  • 比如使用了 Number()转换就会先调用 valueOf()方法,如果 valueOf() 没有返回基本类型就会再调用 toString()方法,如果都没返回基本类型则报错。
  • 比如使用了 String()转换就会先调用 oString()方法,如果toString()没有返回基本类型就会再调用 valueOf()方法,如果都没返回基本类型则报错。
  • 如果有 Symbol.toPrimitive 属性的话,则只会调用 Symbol.toPrimitive 方法,toString()valueOf() 方法就不会调用了。并且该方法只能返回基本类型,否则会报错。

接下来我们就举例说明。感兴趣的小伙伴可以分别注释返回值来进行测试。

const toStringValueOfTest = () => {
  function Animal(name) {
    this.name = name;
  }
  
  Animal.prototype.toString = function () {
    console.log("toString 方法");
    return "toString";
  };
  Animal.prototype.valueOf = function () {
    console.log("valueOf 方法");
    return "valueOf";
  };

  // 有 Symbol.toPrimitive 就不会再调用 toString和valueOf方法啦。
  Animal.prototype[Symbol.toPrimitive] = function () {
    console.log("Symbol.toPrimitive 方法");
    return "Symbol.toPrimitive";
  };

  const dog = new Animal("cat");
  console.log(String(dog)); // Symbol.toPrimitive转字符串得到Symbol.toPrimitive
  console.log(Number(dog)); // Symbol.toPrimitive转数字得到NaN
  
  //依次输出 Symbol.toPrimitive 方法、Symbol.toPrimitive、Symbol.toPrimitive 方法、 NaN
};

toStringValueOfTest();

扩展

判断是不是数组

(1)instanceof

console.log([1, 2, 3] instanceof Array);

(2)通过原型判断

console.log(Array.prototype.isPrototypeOf([1, 2, 3]));

(3)Object.prototype.toString.call()

console.log(Object.prototype.toString.call([1, 2, 3]).slice(8, -1))

(4)constructor.name

console.log(([1, 2, 3]).constructor.name)

(5)Array.isArray()

其实判断是不是数组我们除了用上面介绍的方式,还可以用ES6的新特性Array.isArray()方法来进行快速判断,该方法返回true/false

console.log(Array.isArray([1, 2, 3]));

类数组

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。常见的类数组对象有 argumentsDOM 方法的返回结果。

对类数组的操作我们一般会将类数组转为数组。常见的类数组转换为数组的方法有这样几种:

(1)通过 call 调用数组的 slice 方法来实现转换

Array.prototype.slice.call(arrayLike);

(2)通过 call 调用数组的 splice 方法来实现转换

Array.prototype.splice.call(arrayLike, 0);

(3)通过 apply 调用数组的 concat 方法来实现转换

Array.prototype.concat.apply([], arrayLike);

(4)通过 Array.from 方法来实现转换

Array.from(arrayLike);

(5)通过展开运算符

[...arguments]

isNaN 和 Number.isNaN

函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。也就是说只有是NaN才会返回true

parseInt()、parseFloat()和Number()

我们都知道parseInt()parseFloat()Number()都是进行Number类型转换的,但是它们之间到底有什么区别呢?

  1. parseInt()parseFloat()解析字符串允许含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而Number()不允许出现非数字字符,否则会返回 NaN
  2. parseInt是取整,但不会四舍五入。而parseFloat()Number()不会取整。
const parseIntNumberTest = () => {
  console.log('parseInt("123a"):', parseInt("123a")); // 123
  console.log('parseFloat("123a"):', parseFloat("123a")); // 123
  console.log('parseInt("123.6"):', parseInt("123.6")); // 123
  console.log('parseFloat("123.6"):', parseFloat("123.6")); // 123.6
  console.log('Number("123a"):', Number("123a")); // NaN
  console.log('Number("123.3"):', Number("123.3")); // 123.3
};

parseIntNumberTest();

运算符优先级

  1. () []
  2. ++ -- !
  3. 算术运算符(+、-、*、/、%)
  4. 关系运算符(>、<、<=、>=、==、===)
  5. 逻辑运算符(&&、||)
  6. 三目运算符(? :)
  7. 赋值运算符(= += *= /= -= )

+ - * / % 操作符

  1. 如果是+操作,并且有字符串操作数并且都是基本数据类型则直接进行字符串的拼接。
  2. 如果有引用数据类型的操作数则先把引用数据类型转成Number类型再进行字符串的拼接。
  3. 如果没有字符串操作数则都转成Number类型进行计算。
  4. 那么对于除了加法的运算符来说,只要其中一方是数字,那么另一方就会被转为数字。
const OperatorTest = () => {
  // 如果没有字符串操作数则都转成Number类型进行计算。
  console.log("true + 2:", true + 2); // true转数字为1 所以得到3
  console.log("true - 2:", true - 2); // true转数字为1 所以得到-1
  console.log("true / 2:", true / 2); // true转数字为1 所以得到0.5
  console.log("null + 2:", null + 2); // null转数字为0 所以得到2
  console.log("undefined + 2:", undefined + 2); // undefined转数字为NaN,所以得到NaN
  
  // 如果是+操作,并且有字符串操作数并且都是基本数据类型则直接进行字符串的拼接。
  console.log('"a" + 2:', "a" + 2); // 有字符串就进行字符串拼接,所以得到 a2
  console.log('"a" + true', "a" + true); //有字符串就进行字符串拼接,所以得到 atrue

  // 如果有引用数据类型的操作数则先把引用数据类型转成Number类型再进行字符串的拼接。 
  function Animal(name) {
    this.name = name;
  }
  // 这里toString不会被调用,因为valueOf已经返回基本数据类型的值了。
  Animal.prototype.toString = function () {
    console.log("toString 方法");
    return "toString";
  };
  Animal.prototype.valueOf = function () {
    console.log("valueOf 方法");
    return 12;
  };
  const dog = new Animal("brid");
  console.log("dog + 2:", dog + 2); //输出valueOf 方法并返回12 计算得14
  console.log("dog - 2:", dog - 2); //输出valueOf 方法并返回12 计算得10
  console.log("dog * 2:", dog * 2); //输出valueOf 方法并返回12 计算得24
  console.log("dog / 2:", dog / 2); //输出valueOf 方法并返回12 计算得6
  console.log("dog % 2:", dog % 2); //输出valueOf 方法并返回12 计算得0
  console.log("'哈哈' + dog:", "哈哈" + dog); //输出valueOf 方法并返回12 计算得哈哈12
};

OperatorTest();

其实+运算符还有特殊用处,比如平时我们获取时间戳我们需要使用 new Date().getTime(),其实有更快的方法就是利用+进行隐式转换,+ new Date()就能直接获取到时间戳。

// 获取时间戳
console.log(new Date().getTime())

console.log(+ new Date())

console.log(Date.now())

== 操作符

  1. 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
  2. 其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,true 变 1,false 变 0,再应用其他规则进行比较。
  3. null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
  4. 如果一个操作值为 NaN ,则相等比较永远返回 false( NaN 本身也不等于 NaN )。
  5. 对象和非对象之间的相等比较,对象会调用 valueOf 或者 toString 抽象操作后变为基本类型,再进行比较。如果没有重写toString或者valueOf方法默认先调用toString方法,否则重写了啥先调用啥方法,都重写了先调用valueOf方法。
  6. 如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。
const dengyu1Test = () => {
  console.log("1 == '1':", 1 == "1"); // true
  console.log("true == '1':", true == "1"); // true先转为1,字符串'1'在转为1,然后再比较为true
  console.log("true == 1:", true == 1); // true
  console.log("false == '0':", false == "0"); // true
  console.log("false == 0:", false == 0); // true
  
  // 布尔先转数字 字符串转数字 1==NaN 返回false
  console.log("true == 'true':", true == "true"); //false
  console.log("false == 'false':", false == "false"); //false
  console.log("null == undefined:", null == undefined); //true
  console.log("NaN == NaN:", NaN == NaN); //false
};
dengyu1Test();

对象和非对象比较。

const dengyu2Test = () => {
  function Animal(name) {
    this.name = name;
  }
  Animal.prototype.toString = function () {
    console.log("toString 方法");
    return "toString";
  };
  Animal.prototype.valueOf = function () {
    console.log("valueOf 方法");
    return 1;
  };
  const dog = new Animal("brid");
  
  console.log("dog == '[object Object]':", dog == "[object Object]"); 
  console.log("dog == true:", dog == true);

  // 依次输出valueOf 方法、false、valueOf 方法、true
};
dengyu2Test();

对象和对象比较。

const dengyu3Test = () => {

  function Animal(name) {
    this.name = name;
  }

  const dog = new Animal("brid");
  
  // 不指向同一地址
  const dog2 = new Animal("brid");
  console.log("dog == dog2:", dog == dog2); //false
  
  // 指向同一地址
  const dog3 = dog;
  console.log("dog == dog3:", dog == dog3); //true
};
dengyu3Test();

具体比较顺序如下:

===操作符

===不会进行类型的转换,首先会比较类型,类型不同直接返回false,类型相同再比较值。

const dengyu4Test = () => {
  console.log("1 === 1:", 1 === 1); // true
  console.log("1 === '1':", 1 === "1"); // false
  
  function Animal(name) {
    this.name = name;
  }
  const dog = new Animal("brid");
  
  // 不指向同一地址
  const dog2 = new Animal("brid");
  
  // 指向同一地址
  const dog3 = dog;
  console.log("dog === dog2:", dog === dog2); //false
  console.log("dog === dog3:", dog === dog3); //true
};
dengyu4Test();

Object.is()

使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 让-0 和 +0 不再相等,让两个 NaN 相等。

Object.is(+0, -0); // 返回false
Object.is(NaN, NaN); // 返回true

&& ||

&&逻辑与也叫短路与,在其操作数中找到第一个虚值表达式并返回它。使用了短路来防止不必要的工作。如果没有找到任何虚值表达式,则返回最后一个表达式的值。

|| 逻辑或也叫短路或,在其操作数中找到第一个真值表达式并返回它。使用了短路来防止不必要的工作。如果没有找到任何真值表达式,则返回最后一个表达式的值。

所以说逻辑与逻辑或返回的不是true false而是某个表达式的值。

// 输出a,console.log("a")的返回值是undefined所以返回a undefined
console.log(1 && console.log("a") && null && console.log("b") && 2); //a undefined

// 没有虚值返回最后一项的值
console.log(" " && true && 5); // 5

// 返回第一个真值
console.log(null || console.log("a") || 1 || console.log("b")); // a 1

// 没有真值返回最后一项的值
console.log(undefined || null); //null

什么情况下会发生布尔值的隐式强制类型转换?

  1. if (..) 语句中的条件判断表达式。
  2. for ( .. ; .. ; .. ) 语句中的条件判断表达式(第二个)。
  3. while (..) 和 do..while(..) 循环中的条件判断表达式。
  4. ? : 中的条件判断表达式。
  5. 逻辑运算符 ||(逻辑或)和 &&(逻辑与)左边的操作数(作为条件判断表达式)。

好啦,关于JS数据类型,笔者已经讲完啦,感谢大家的耐心观看。

系列文章

都2022年了你不会还没搞懂JS数据类型吧

都2022年了你不会还没搞懂JS原型和继承吧

都2022年了你不会还没搞懂JS赋值拷贝、浅拷贝、深拷贝吧

都2022年了你不会还没搞懂对象数组的遍历吧

都2022年了你不会还没搞懂this吧

都2022年了你不会还没搞懂JS Object API吧

都2022年了你不会还没搞懂js垃圾回收和内存泄露吧

都2022年你不会还没搞懂js执行上下文和事件循环机制吧

都2022年了你不会还没搞懂js中的事件吧

都2020年了你不会还没搞懂js异步编程吧

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

相关文章
|
8天前
|
JavaScript 前端开发
js确定数据类型typeof与instanceof
js确定数据类型typeof与instanceof
15 0
|
8天前
|
JavaScript
js基础数据类型
js基础数据类型
19 1
|
4天前
|
缓存 JavaScript 安全
2022年最新最详细的安装Node.js以及cnpm(详细图解过程、绝对成功)
这篇文章提供了2022年最新最详细的Node.js和cnpm安装教程,包括步骤图解、全局配置路径、cnpm安装命令、nrm的安装与使用,以及如何管理npm源和测试速度。
2022年最新最详细的安装Node.js以及cnpm(详细图解过程、绝对成功)
|
5天前
|
JavaScript 前端开发
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
这篇文章是JavaScript基础与实战教程的第一部分,涵盖了JavaScript的基本语法、标识符、数据类型以及如何进行强制类型转换,通过代码示例介绍了JS的输出语句、编写位置和数据类型转换方法。
JavaScript基础&实战(1)js的基本语法、标识符、数据类型
|
5天前
|
JavaScript
js基础数据类型
js基础数据类型
|
17天前
|
存储 JavaScript 前端开发
JavaScript引用数据类型和构造函数的秘密
JavaScript引用数据类型和构造函数的秘密
|
1天前
|
JavaScript Linux
2022年超详细在CentOS 7上安装Node.js方法(源码安装)
这篇文章介绍了在CentOS 7系统上通过源码安装Node.js的详细步骤,包括从官网下载Node.js源码包、将安装包上传至虚拟机、解压安装包、删除压缩文件、编译安装Node.js、检查Node.js和npm版本,以及切换npm源到淘宝镜像以加速下载。此外,还提供了一个获取Linux下Node.js离线安装包的微信公众号搜索方式。
|
5天前
|
JavaScript 前端开发
JavaScript 数据类型
JavaScript 数据类型
12 0
|
5天前
|
JavaScript
js基础数据类型
js基础数据类型
|
2月前
|
JavaScript 前端开发 开发者
JavaScript数据类型概述及Undefined与Null详解
JavaScript数据类型概述及Undefined与Null详解
29 3