跳至主要內容

类型概述

Mr.LRH大约 15 分钟

类型概述

原始值(primitive value)

原始值(primitive value)是最简单的数据,也称为原始类型。

  • Undefined :一个声明未定义的变量的初始值,或没有实际参数的形式参数。
  • Null :表示一个空对象指针。
  • Boolean :布尔值,取值仅能为 true(真)和 false(假)的数据类型。
  • Number :64 位双精度浮点型的数字数据类型。
  • Bigint :表示大于 2^53 - 1 的整数,BigInt 可以表示任意大的整数。
  • String :字符串,用于表示文本的字符序列。
  • SymbolSymbol() 函数会返回 symbol 类型的值,symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符,这是该数据类型仅有的目的。

原始值特点:

  • 保存原始值的变量 :按值(by value)访问的。
  • 原始值的初始化 :可以只使用原始字面量形式。如果使用的是 new 关键字,则 JavaScript 会创建一个 Object 类型的实例,但其行为类似原始值。
  • 函数传递参数 :按值传递。在传递原始值参数时,值会被复制到一个局部变量(即一个命名参数)。
  • 原始值的比较 :值的比较。
  • 原始值的保存 :存储在栈内存中。占用空间小、大小固定,通过值来访问。

引用值(reference value)

引用值(reference value)(或者对象)是某个特定引用类型的实例,也称为引用类型,是把数据和功能组织在一起的结构。

  • Object :用于存储各种键值集合和更复杂的实体,所有引用类型都从它继承了基本的行为。
  • Array :表示一组有序的值,并提供了操作和转换值的能力。
  • Function :每个 JavaScript 函数实际上都是一个 Function 对象。
  • Map :键/值对的集合,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值,一个键只能出现一次。
  • Set :值的集合。存储任何类型的唯一值,无论是原始值或者是对象引用。可以按照插入的顺序迭代它的元素,Set 中的元素是唯一的。
  • WeakMap :键/值对的集合,其中的键是弱引用的。键必须是对象,而值可以是任意的。
  • WeakSet :对象值的集合。只能是对象的集合,在 WeakSet 的集合中,对象的引用为弱引用,所有对象都是唯一的。
  • 原始值包装类型 : 三种原始值包装类型 BooleanNumberString 可以被当做对象使用。具备以下特点:
    • 每种包装类型都映射到同名的原始类型。
    • 以读模式访问原型值时,后台会实例化一个原始值包装类型的对象,调用实例上特定的方法操作相应的数据。
    • 涉及原始值的语句执行完毕之后,包装对象会被销毁。
  • 类型化数组 :类似数组的对象,并提供了一种用于在内存缓冲区中访问原始二进制数据的机制。
    • ArrayBuffer :用来表示一个通用的、固定长度的二进制数据缓冲区。不能直接操作 ArrayBuffer 中的内容,需要使用 DataView
    • DataView :提供有可以操作缓冲区中任意数据的访问器(getter/setter)API。
  • Math :包含辅助完成计算的属性和方法。
  • Date :提供关于日期和时间的信息,包括当前日期、时间及相关计算。
  • RegExp :提供正则表达式功能。
  • ......

引用值特点:

  • 保存引用值的变量 :按引用(by reference)访问的。
  • 函数传递参数:按值传递。在传递引用值参数时,值在内存中的位置会被保存在一个局部变量,这意味着对本地变量的修改会反映到函数外部。
  • 引用值的比较 :引用的比较。
  • 引用值的保存 :存储在堆内存中。引用数据类型占据空间大、大小不固定,如果存储在栈中,将影响程序的运行性能。引用数据类型会在栈中存储一个指针,这个指针指向堆内存空间中该实体的起始地址。

类型判断

typeof

typeof operand 返回一个字符串,标识操作数的类型。适合用来判断一个变量是否为原始类型。

  • operand :表示要返回类型的对象或基本类型的表达式。
// 判断原始值(原始类型) - Number
typeof 3.14 // 'number'
typeof Infinity // 'number'
typeof NaN // 'number'
typeof Number(1) // 'number'
typeof Number('shoe') // 'number'

// 判断原始值(原始类型) - BigInt
typeof 42n // 'bigint'
typeof Object(1n) // 'object' :使用 Object 包装后, BigInt 被认为是一个普通 'object'

// 判断原始值(原始类型) - String
typeof 'string' // 'string'
typeof typeof 1 // 'string'
typeof String(1) // 'string'

// 判断原始值(原始类型) - Boolean
typeof true // 'boolean'
typeof Boolean(1) // 'boolean'
typeof !!1 // 'boolean'

// 判断原始值(原始类型) - Symbols
typeof Symbol() // 'symbol'
typeof Symbol('foo') // 'symbol'

// 判断原始值(原始类型) - Undefined
typeof undefined // 'undefined'
typeof declaredButUndefinedVariable // 'undefined' - 已声明但未定义变量
typeof undeclaredVariable // 'undefined' - 未声明变量

// 判断函数
typeof function () {} // 'function'
typeof class C {} // 'function'
typeof Math.sin // 'function'
typeof console.log // 'function'

// 判断引用类型
typeof null // 'object'
typeof { a: 100 } // 'object'
typeof ['a', 'b'] // 'object'
typeof new Date() // 'object'
typeof /regex/ // 'object'

// 避免使用原始值包装类型,可使用字面量方式
typeof new Boolean(true) // 'object'
typeof new Number(1) // 'object'
typeof new String('abc') // 'object'

instanceof

object instanceof constructor :用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

  • object :某个实例对象
  • constructor :某个构造函数
'str' instanceof String // true
new String('newStr') instanceof String // true
new String('newStr') instanceof Object // true

{} instanceof Object // true
Object.create(null) instanceof Object // false,一种创建非 Object 实例的对象的方法

new Date() instanceof Date // true
new Date() instanceof Object // true

// ========================================
// ========================================

function Foo() {}
function Bar() {}
var foo = new Foo()

foo instanceof Foo // true
foo instanceof Object // true,因为 Object.prototype.isPrototypeOf(foo) 返回 true
Foo.prototype instanceof Object // true

Bar.prototype = new Foo() // 继承
var bar = new Bar()
bar instanceof Bar // true
bar instanceof Foo // true,因为 Foo.prototype 在 bar 的原型链上

Object.prototype.toString.call

toString 返回一个表示该对象的字符串,默认情况下返回类型字符串。该方法旨在重写(自定义)派生类对象的类型转换的逻辑。

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(3.14) // '[object Number]'
Object.prototype.toString.call('str') // '[object String]'
Object.prototype.toString.call({ a: 100 }) // '[object Object]'
Object.prototype.toString.call([1, 2, 3]) // '[object Array]'
Object.prototype.toString.call(function () {}) // '[object Function]'
Object.prototype.toString.call(new Map()) // '[object Map]'
Object.prototype.toString.call(new Set()) // '[object Set]'
Object.prototype.toString.call(function* () {}) // '[object GeneratorFunction]'
Object.prototype.toString.call(Promise.resolve()) // '[object Promise]'
Object.prototype.toString.call(new Date()) // '[object Date]'
Object.prototype.toString.call(/regex/) // '[object RegExp]'

// ========================================
// ========================================

function typeOf(obj) {
  const toString = Object.prototype.toString
  const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object',
    '[object Map]': 'map',
    '[object Set]': 'set',
    '[object GeneratorFunction]': 'generatorFunction',
    '[object Promise]': 'promise'
  }
  return map[toString.call(obj)]
}

// ========================================
// ========================================

function typeOf(o) {
  var s = Object.prototype.toString.call(o)
  return s.match(/\[object (.*?)\]/)[1].toLowerCase()
}

typeOf({}) // "object"
typeOf([]) // "array"
typeOf(5) // "number"
typeOf(null) // "null"
typeOf() // "undefined"
typeOf(/abcd/) // "regex"
typeOf(new Date()) // "date"

// 在 typeOf 函数的基础上,添加专门判断某种类型数据的方法
[
  'Null',
  'Undefined',
  'Object',
  'Array',
  'String',
  'Number',
  'Boolean',
  'Function',
  'RegExp',
].forEach(function (t) {
  typeOf['is' + t] = function (o) {
    return typeOf(o) === t.toLowerCase()
  }
})
typeOf.isObject({}) // true
typeOf.isNumber(NaN) // true
typeOf.isRegExp(/abc/) // true

Array.isArray

Array.isArray(value) 用于确定传递的值是否是一个 Array。如果值是 Array,则为 true;否则为 false

// 下面的函数调用都返回 true
Array.isArray([])
Array.isArray([1])
Array.isArray(new Array())
Array.isArray(new Array('a', 'b', 'c', 'd'))
Array.isArray(new Array(3))
// Array.prototype 其实也是一个数组。
Array.isArray(Array.prototype)

// 下面的函数调用都返回 false
Array.isArray()
Array.isArray({})
Array.isArray(null)
Array.isArray(undefined)
Array.isArray(17)
Array.isArray('Array')
Array.isArray(true)
Array.isArray(false)
Array.isArray(new Uint8Array(32))
Array.isArray({ __proto__: Array.prototype })

当检测 Array 实例时,Array.isArray 优于 instanceof,因为 Array.isArray 能检测 iframes

const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
xArray = window.frames[window.frames.length - 1].Array
const arr = new xArray(1, 2, 3) // [1,2,3]

// 正确检查 Array
Array.isArray(arr) // true
arr instanceof Array // false

类型强制转换

转换为 Boolean

  • 转换为 Boolean 值为 falseundefinednull0-0falseNaN''(空字符串)
  • 转换为 Boolean 值为 true :所有其他值,包括任何对象,空数组([])或字符串 "false"
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(-0) // false
Boolean(false) // false
Boolean(NaN) // false
Boolean('') // false

Boolean(0n) // false , 将 BigInt 转为 Boolean
Boolean(1n) // true , 将 BigInt 转为 Boolean

Boolean('false') // true
Boolean(3.14) // true
Boolean(true) // true
Boolean({}) // true
Boolean([]) // true
Boolean(new Boolean(false)) // true
Boolean(Symbol('foo')) // true

转换为 String

  • 原始值类型
    • Number 类型 :转换为相应的字符串
    • String 类型 :转换后还是原来的值
    • Boolean 类型 :true 转换成字符串 'true'false 转换成字符串 'false'
    • Undefined 类型 : 转换为字符串 'undefined'
    • Null 类型 :转换为字符串 'null'
    • Symbol 类型 :转换为相应的字符串
  • Object 类型
    • 调用对象自身的 toString 方法。如果返回原始类型的值,则对该值使用 String 函数,不再进行以下步骤。
    • 如果 toString 方法返回的是对象,再调用原对象的 valueOf 方法。如果 valueOf 方法返回原始类型的值,则对该值使用 String 函数,不再进行以下步骤。
    • 如果 valueOf 方法返回的是对象,就报错。
  • Array 类型 :返回该数组的字符串形式
String(3.14) // "3.14"
String(1n)  // "1" , 将 BigInt 转为 String
String('foo') // "foo"
String(true) // "true"
String(undefined) // "undefined"
String(null) // "null"
String(Symbol('foo')) // "Symbol(foo)"

String({ a: 1 }) // "[object Object]"
String([1, 2, 3]) // "1,2,3"

String({
  valueOf: function () {
    return {}
  },
  toString: function () {
    return {}
  },
})
// toString 和 valueOf 方法,返回的都是对象,则会报错
// TypeError: Cannot convert object to primitive value

String({
  toString: function () {
    return 3
  },
})
// 返回 toString 的值
// "3"

String({
  valueOf: function () {
    return 2
  },
})
// 返回 valueOf 的值
// "[object Object]"

String({
  valueOf: function () {
    return 2
  },
  toString: function () {
    return 3
  },
})
// 调用 toString --> valueOf,返回的值
// "3"

转换为 Number

  • 原始值类型
    • Number 类型 :转换后还是原来的值
    • String 类型 :转换为原来的值
      • 如果可以被解析为数值,则转换为相应的数值
      • 如果不可以被解析为数值,返回 NaN
      • ''(空字符串)转换为 0
    • Boolean 类型 :true 转换成数值 1false 转换成数值 0
    • Undefined 类型 : 转换为 NaN
    • Null 类型 :转换为 NaN
    • Symbol 类型 :抛出 TypeError
  • Object 类型
    • 调用对象自身的 valueOf 方法。如果返回原始类型的值,则直接对该值使用 Number 函数,不再进行后续步骤。
    • 如果 valueOf 方法返回的还是对象,则改为调用对象自身的 toString 方法。如果 toString 方法返回原始类型的值,则对该值使用 Number 函数,不再进行后续步骤。
    • 如果 toString 方法返回的是对象,就报错。
  • Array 类型 :转换为 NaN
Number(3.14) // 3.14
Number(1n) // 1 , 将 BigInt 转为 Number
Number('3.14') // 3.14
Number('\t\v\r3.14\n') // 3.14,会自动过滤一个字符串前导和后缀的空格
Number('3.14abc') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number(undefined) // NaN
Number(null) // 0
Number(Symbol('foo')) // Uncaught TypeError: Cannot convert a Symbol value to a number

Number({ a: 1 }) // NaN
Number({}) // NaN
Number([1, 2, 3]) // NaN
Number([5]) // 5

Number({
  valueOf: function () {
    return {}
  },
  toString: function () {
    return {}
  },
})
// toString 和 valueOf 方法,返回的都是对象,则会报错
// TypeError: Cannot convert object to primitive value

Number({
  valueOf: function () {
    return 2
  },
})
// 返回 valueOf 的值
// 2

Number({
  toString: function () {
    return 3
  },
})
// 返回 toString 的值
// 3

Number({
  valueOf: function () {
    return 2
  },
  toString: function () {
    return 3
  },
})
// 调用 valueOf --> toString,返回的值
// 2

类型自动转换

相等操作符

  • 等于操作符用两个等于号(==)表示,如果操作数相等,则会返回 true
  • 不等于操作符用叹号和等于号(!=)表示,如果两个操作数不相等,则会返回 true

这两个操作符都会先进行类型转换(通常称为强制类型转换),再确定操作数是否相等。

在转换操作数的类型时,相等和不相等操作符遵循如下规则:

  • 如果任一操作数是 Boolean,则将其转换为 Number 再比较是否相等。false 转换为 0true 转换为 1
  • 如果一个操作数是 String,另一个操作数是 Number,则尝试将字符串转换为数值,再比较是否相等。
  • 如果一个操作数是 Object,另一个操作数不是,则调用对象的 valueOf() 方法取得其原始值,再根据前面的规则进行比较。

在进行比较时,这两个操作符会遵循如下规则:

  • NaN 不等于 NaN
  • nullundefined 相等。
  • nullundefined 不能转换为其他类型的值再进行比较。
  • 如果有任一操作数是 NaN,则相等操作符返回 false,不相等操作符返回 true
  • 如果两个操作数都是 Object,则比较它们是不是同一个 Object。如果两个操作数都指向同一个 Object,则相等操作符返回 true。否则,两者不相等。
  • 如果有任一操作数是 Symbol,则相等操作符返回 false,不相等操作符返回 true
null == undefined // true
null == 0 // false
undefined == 0 // false

0 == false // true
1 == true // true
2 == true // false
0 == !!null // true
0 == !!undefined // true

NaN == 'NaN' // false
NaN == 3.14 // false
NaN == NaN // false
NaN != NaN // true

'str' == String('str') // true
'str' == new String('str') // true
String('str') == new String('str') // true
new String('str') == new String('str') // false

'3.14' == 3.14 // true

new Number(3.14) == 3.14 // true
new Number(3.14) == new Number(3.14) // false
[] == ![] // true
// 根据运算符优先级, ! 的优先级是大于 == ,所以先会执行 ![]
// [] == ![] --> [] == false --> [] == 0 --> '' == 0 -> 0 == 0 --> true

{} == !{} // false
// {} == !{} --> {} == false --> {} == 0 --> NAN == 0 --> false

if 语句

  • truely 变量:!!a === true 的变量
  • falsely 变量:!!a === false 的变量
// 以下是 falsely 变量。除此之外都是 truely 变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined == false
!!false === false

关系操作符

关系操作符执行比较两个值的操作,包括小于(<)、大 于(>)、小 于 等 于( <=)和大于等于(>=

  • 如果操作数都是 Number,则执行数值比较。
  • 如果操作数都是 String,则逐个比较字符串中对应字符的编码(Unicode)。
  • 如果有任一操作数是 Number,则将另一个操作数转换为数值,执行数值比较。
  • 如果有任一操作数是 Object,则调用其 valueOf() 方法,取得结果后再根据前面的规则执行比较。如果没有 valueOf() 操作符,则调用 toString() 方法,取得结果后再根据前面的规则执行比较。
  • 如果有任一操作数是 Boolean,则将其转换为数值再执行比较。
  • 任何值(包括 NaN 本身)与 NaN 使用非相等运算符进行比较,返回的都是 false
2 > 1 // true

'cat' > 'dog' // false
'cat' > 'Cat' // true,“c”的 Unicode 码点是 99,“C”是 67
'大' > '小' // false,“大”的 Unicode 码点是 22823,“小”是 23567

5 > '4' // true,等同于 5 > Number('4'),即 5 > 4
true > false // true,等同于 Number(true) > Number(false),即 1 > 0
2 > true // true,等同于 2 > Number(true),即 2 > 1

1 > NaN // false
1 <= NaN // false
'1' > NaN // false
'1' <= NaN // false
NaN > NaN // false
NaN <= NaN // false

var x = [2]
x > '11' // true
// 等同于 [2].valueOf().toString() > '11',
// 即 '2' > '11'
x.valueOf = function () {
  return '1'
}
x > '11' // false
// 等同于 (function () { return '1' })() > '11'
// 即 '1' > '11'

// [2] > [1] // true
// 等同于 [2].valueOf().toString() > [1].valueOf().toString()
// 即 '2' > '1'

// [2] > [11] // true
// 等同于 [2].valueOf().toString() > [11].valueOf().toString()
// 即 '2' > '11'

// ({ x: 2 }) >= ({ x: 1 }) // true
// 等同于 ({ x: 2 }).valueOf().toString() >= ({ x: 1 }).valueOf().toString()
// 即 '[object Object]' >= '[object Object]'

四则运算符

加法运算符(+)

如果两个操作数都是数值,加法操作符执行加法运算并根据如下规则返回结果:

  • 任一操作数是 NaN,则返回 NaN
  • Infinity + Infinity,则返回 Infinity
  • (-Infinity) + (-Infinity),则返回 -Infinity
  • Infinity + (-Infinity),则返回 NaN
  • (+0) + (+0),则返回 +0
  • (-0) + (+0),则返回 +0
  • (-0) + (-0),则返回 -0

如果有一个操作数是字符串,则要应用如下规则:

  • 如果两个操作数都是 String,则将第二个字符串拼接到第一个字符串后面;
  • 如果只有一个操作数是 String,则将另一个操作数转换为 String,再将两个字符串拼接在一起。
  • 如果有任一操作数是 ObjectNumberBoolean,则调用 toString() 方法以获取字符串,然后再应用关于字符串的规则。对于 undefinednull,则调用 String() 函数,分别获取 "undefined""null"
'5' + 1 // '51'
'5' + true // "5true"
'5' + false // "5false"
'5' + {} // "5[object Object]"
'5' + [] // "5"
'5' + function () {} // "5function (){}"
'5' + undefined // "5undefined"
'5' + null // "5null"

其他运算符

其他运算符都会把运算值自动转成数值。

'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN

一元运算符也会把运算值转成数值。

// +'abc' // NaN
// -'abc' // NaN
// +true // 1
// -false // 0

Date()

如果 Date() 构造函数被调用时,有一个参数不是 Date 实例,它将被强制转换为原始值,然后检查它是否是一个字符串。

new Date() // Fri Apr 07 2023 01:01:23 GMT+0800 (中国标准时间),当前时间

new Date(undefined) // Invalid Date
// undefined 为原始类型值,但不是一个字符串
// 将被强制转换为一个数值,也就是 NaN,因此不是一个有效的时间戳

new Date(null) // Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间)
// null 为原始类型值,但不是一个字符串,将被强制转换为 0,
上次编辑于:
贡献者: lingronghai,lrh21g