数据类型 
温馨提示
阅读《JavaScript 高级程序设计(第 4 版)》和各个大佬的文章所归纳的总结,如有异议按你的理解为主
JavaScript 中的数据类型分为基本数据类型和引用数据类型
基本类型 
注: 基本数据类型也可以叫原始数据类型
在 ES2020 标准下的 JavaScript 一共有以下 7 种基本类型
基本类型总结
- 基本类型仅保存原始值,不存在属性和方法
 - 基本类型存储在 栈内存 中
 - 保存基本类型的变量是 按值 (by value) 访问 的,操作的就是存储在变量中的实际值
 - 复制基本类型时会创建该值的第二个副本 (独立使用,互不干扰)
 
为什么原始值不存在属性和方法,但 'hello world'.toString() 可以正确执行
为了方便操作原始值 ECMAScript 提供了 3 种特殊的引用类型:Boolean Number String,每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象,在执行完后再销毁这个包装对象
// 举个 🌰
const str = 'hello world'
str.toString()
str.length
/**
 * 在执行上面的代码时 `JavaScript` 都会执行以下 3 步
 * 1. 创建一个 String 类型的实例
 * 2. 调用实例上的特定方法或属性
 * 3. 销毁刚刚创建的实例
 */
const str = 'hello world'
new String(str).toString()
new String(str).length
字符串 String 
字符串是 JavaScript 中,最基本的值类型之一。详细的介绍请参考 W3C 的String 对象。
String 对象用于处理文本(字符串)。
初始化 
初始化字符串,可以分为两种,字面量和构造函数。
// 1. 字面量方式 双引号或单引号都代表的是字符串
var str = 'hello world !'
var str1 = 'welcome to my blog !'
typeof str // String
// 2. 构造函数
var str2 = new String('hello world !')
typeof str2 //object
在 JavaScript 中 String 类型的的字符串也可以使用 Object 类型中包含的属性和方法,js 会默认的将 String 类型转换成 Object。
length 和获取字符 
对字符串而言,是由一个一个的字符拼接而成,可以说,字符是字符串的基本单位。但是 JavaScript 中,没有字符串和字符的区别。对比 Java 中,就明显很多,字符只能用单引号,字符串只能用双引号。
var str = 'abcdefg'
// length 计算字符串的长度
console.log(str.length) // 7
// charAt() 返回特定索引位置的字符 默认为0
console.log(str.charAt()) // a
// charCodeAt() 返回特定位置字符在Unicode码中的十进制数 默认为0
console.log(str.charCodeAt()) // 97
// Unicode码转字符
// A-Z对应65-90  a-z对应97-122
var c = String.fromCharCode(65)
console.log(c) // A
详细的 Unicode 码比较多,这里展示其中一部分,如 ASCII 码对应的十进制数,可以参考下图。

字符串的常用方法 
// indexOf() 从前到后检索子串,返回子串开始位置的索引,无此子串返回-1
var str = 'hello world hello world hello world'
var position = str.indexOf('hello')
console.log(position) // 0
// 如果传入第二个参数(正整数),第二个参数为开始查找的位置,查找方向不变
var position2 = str.indexOf('hello', 35) // -1
console.log(position2)
// lastIndexOf() 从后到前检索子串,返回子串开始位置的索引,无此子串返回-1
var position3 = str.lastIndexOf('hello')
console.log(position3) // 24
// toUpperCase() 原串中所有字母变成大写,新串返回
var str2 = 'hello WORLD'
var str3 = str2.toUpperCase()
console.log(str2) // hello WORLD
console.log(str3) // HELLO WORLD
// toLowerCase() 原串中所有字母变成小写,新串返回
var str4 = str2.toLowerCase()
console.log(str4) // hello world
// concat() 拼接字符串,和 + 一致
var str5 = 'hello'
var str6 = ' world'
var str7 = str5.concat(str6) // hello world
console.log(str7 + '-----' + str5) // hello world-----hello
// localeCompare() 用本地特定的顺序(0123456789aAbB.....zZ)来比较两个字符串 小 -1;等 0;大 1
var str8 = 'A'
var str9 = 'a'
console.log(str8.localeCompare(str9)) // 1
// slice() 提取字符串的片断,并在新的字符串中返回被提取的部分。
var str10 = 'abcdefg'
//正数时  参数1 起始位置 参数2 结束位置后一位
console.log(str10.slice(3, 5)) // de
//负数时  参数1 加长度(转正)参数2 加长度(转正)
console.log(str10.slice(-2, -1)) // f
// substring() 提取字符串中两个指定的索引号之间的字符。
//正数时  参数1 起始位置 参数2 结束位置后一位
console.log(str10.substring(3, 5)) // de
//负数时  参数1 置0 参数2 置0 相当于str10.substring(0, 0)
console.log(str10.substring(-1, -5)) // ""
// substr() 从起始索引号提取字符串中指定数目的字符。
//正数时  参数1 起始位置 参数2 返回新串的长度
console.log(str10.substr(3, 2)) // de
//负数时  参数1 加长度 参数2 置0
console.log(str10.substr(-3, 2)) // ef
String 对象的方法 slice()、substring() 和 substr() (不建议使用)都可返回字符串的指定部分。slice() 比 substring() 要灵活一些,因为它允许使用负数作为参数。slice() 与 substr() 有所不同,因为它用两个字符的位置来指定子串,而 substr() 则用字符位置和长度来指定子串。
字符串与数组的转换 
实际运用中,会经常涉及数组与字符串之间的互相转换问题。
var str = 'hello world welcome to my blog'
// 字符串 --> 数组
// 参数1 分隔符 参数2 个数限制
var arr = str.split(' ', 2)
console.log(arr) // ["hello", "world"]
// 数组 --> 字符串
// 参数1 连接符
var str2 = arr6.join('*')
console.log(str2) // "hello*world"
var str3 = arr6.toString()
console.log(str3) // "hello,world"
字符串与数值的转换 
数值类型 Number 和 字符串 String 之间的转换,在实际应用中十分广泛。在学习的时候,严格按照类型来处理是很重要的。
//  字符串 --> 数值
// 【方法一】Number() 构造函数法
// 纯数值字符串,直接输出对应整型或浮点型;数值字符串前有0,忽略0
Number('-999.99') // -999.99
// 布尔值,真转成1,假转成0
Number(true) // 1
// undefined,转成NaN(Not a Number 非数值), NaN自身不等(NaN!==NaN)
Number(undefined) // NaN
// 非纯数值字符串,空串转化成0;其他,转NaN
Number('1234abc') // NaN
// 【方法二】parseInt()/parseFloat() 官方提供函数用于将字符串转化为数值
// 如果第一个字符不是数值字符,返回NaN
parseInt('a123456') // NaN
// 如果第一个字符是数值字符串,检测第二个……,直到检测到非数值字符,然后将前面数值部分返回
parseInt('1234abc') // 1234
parseInt('12.34') // 12
// 对于含小数点的字符串,可以用parseFloat转换为浮点型
parseFloat('12.34') // 12.34
// 小数点数量超过一个时,只保留第一个小数点后和第二个小数点前的部分
parseFloat('12.34.56') // 12.34
// 【方法三】隐式转换 只能是纯数值字符串或者空字符串
var str = '123'
console.log(+str) // 123
//  数值 --> 字符串
// 【方法一】toString() 官方提供的方法
var num = 123
num.toString() // "123"
// 数值toString方法 也可以接受一个参数,转换为对应进制的字符串 默认为十进制
var num1 = 3
num.toString() // "3" 十进制
num.toString(2) // "11" 二进制
// 【方法二】隐式转换
var num = 100
console.log('' + num) // "100"
字符串的应用是 JavaScript 中最基本的部分之一,需要对基本的属性、方法有一定认识,才能为以后更深入的学习 JavaScript 做铺垫。
引用类型 
在 JavaScript 中除了基本类型,其他的都是引用类型,常见的引用类型如下
Object对象Array数组Function函数Date日期与时间RegExp正则表达式Set类似于数组但成员的值都是唯一的 (ES6 引入)WeakSet(ES6 引入)Map类似于对象也是键值对的集合 (ES6 引入)WeakMap(ES6 引入)
引用类型总结
- 因为 
JavaScript不允许直接访问内存位置(不能直接操作对象所在的内存空间),所以引用类型在 栈内存 中存储的是地址(内存指针),而引用类型中的数据(方法或属性)是存储在 堆内存 中 - 保存引用类型的变量是 按引用 (by reference) 访问 ,实际上操作的是对该对象的引用而非实际的对象本身
 - 复制引用类型时只会复制内存指针
 
栈内存和堆内存
- 栈内存
- 存储基本数据类型和堆内存地址
 - 是连续的内存空间
 
 - 堆内存
- 存储引用数据类型和闭包中的变量
 - 不是连续的内存空间
 
 - 了解更多请点击 JS 中的栈内存和堆内存
 
数组 Array 
数组 Array 官方定义的一个类型,存若干数据(有序),数组相关的基础知识整理。详细的可以查看W3C 的相关介绍。
Array 对象用于在单个的变量中存储多个值。
初始化 
初始化一个数组的方式有两种,字面量和构造函数。
// 1. 字面量创建
var arr = [] // 创建了一个空数组
var arr1 = [1, , 'abc', true, 3.14]
console.log(arr1) // 第二个元素是 undefined
// 2. 构造函数创建
var arr2 = new Array() // [] 创建了一个空数组
arr2 = new Array(1, 2, 3) // [1,2,3]
//看一下变量的类型
console.log(typeof arr1) // object
console.log(typeof arr2) // object
//当只传一个参数的时候,若果是一个数字,创建一个长度为这个数字的数组
var arr3 = new Array(20) // 不是[20],是创建一个长度为20的数组
console.log(arr3.length, arr3) // 20  []
//如果是负值,会报错
// arr3 = new Array(-2);
// Array.html:30 Uncaught RangeError: Invalid array length
console.log(arr3)
length 
length 是数组的可读可写的属性。
var arr4 = [1, 2, 3, 4]
// js中数组不存在数组越界
// 可以通过小标添加新的元素
arr4[4] = 5
console.log(arr4, arr4.length) // [1,2,3,4,5] 5
// length可读可写的属性
arr4.length = 10
console.log(arr4, arr4.length) // [1,2,3,4,5,empty × 5] 10
// 如果把长度设置成比原有数组长度小,会删除超出长度的元素
arr4.length = 3
console.log(arr4, arr4.length) // [1,2,3] 3
访问和遍历数组元素 
访问数组中的元素,通过索引(下标)获取元素。
var arr5 = [1, 2, 3, 4]
// 通过索引获取元素
console.log(arr5[0], arr5[5]) // 1 undefined
arr5[2] = 'abc' // 直接修改某个元素
console.log(arr5)
遍历一个数组的方式有很多,有官方提供的,自己实现的等。
var arr5 = [1, 2, 3, 4, 5]
// 方法一   for循环遍历 数组得到遍历
for (var i = 0; i < arr5.length; i++) {
  console.log(arr5[i])
}
// 方法二   快速遍历for in遍历  据说性能上要比其他的便利快
for (var index in arr5) {
  // 这里的变量index是数组的索引
  console.log(index)
}
// 方法三   forEach()遍历,官方给我们提供的方法,需要传一个函数
// 这个函数有一个隐形的参数,代表数组的元素
// 函数内是遍历数组时对每个数组做什么操作
// 并不支持 break
arr5.forEach(function (num) {
  console.log(num)
})
数组的常用方法 
数组Array对象本身提供了许多方法,这里列举下常用的一些方法。比如 push和pop、shift和unshift、slice、sort等。
var arr7 = [1, 2]
// push() 向数组的末尾添加一个或更多元素,并返回新的长度。
console.log(arr7.push(3, 4, 5)) // 5
// pop() 删除并返回数组的最后一个元素
console.log(arr7.pop()) // 5
// shift() 删除并返回数组的第一个元素
console.log(arr7.shift()) // 1
// unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
console.log(arr7.unshift(1, 2)) // 5
// join() 把数组元素拼接成一个字符串(以某个字符串为连接符)不传参就是直接连接""
console.log(arr7.join('/')) // "1/2/2/3/4"
var arr8 = ['a', 'b', 'c']
// reverse() 数组倒序
console.log(arr8.reverse()) // ["c", "b", "a"]
// concat() 拼接两个或更多的数组,返回值是一个新的数组,不会改变原数组
console.log(arr8.concat(['d', 'e'])) // ["c", "b", "a", "d", "e"]
// slice() 截取数组的一部分,返回值是一个新数组,不会改变原数组
// 第一个参数:起始位置  第二个参数:结束为止的前一个索引
console.log(arr8.slice(0, 3)) // ["c", "b", "a"]
// splice() 删除数组元素并向数组添加数组元素 返回删除的元素的数组
// 第一个参数:必须,起始位置(起始索引)
// 第二个参数:必须,如果为0,表示不删除元素,如果为2,删除元素的长度
// 第三、四..参数可以省略,替换的数组元素
arr8.splice(1, 0, 'z') // []
console.log(arr8) // ["c", "z", "b", "a"]
arr8.splice(2, 1, 'w') // ["b"]
console.log(arr8) // ["c", "z", "w", "a"]
// toString() 把数组转成字符串
console.log(arr8.toString()) // "c,z,w,a"
// indexOf() 从头部查找某个元素的索引(第一次出现的位置)
console.log(arr8.indexOf('a')) // 3
// lastIndexOf()  从尾部开始查找(第一次出现的位置)
console.log(arr8.lastIndexOf('a')) // 3
var arr9 = [1, 6, 3, 8, 5]
// sort() 排序  参数也是函数function,function有两个隐藏参数,函数内是以何种方式去排序
arr9.sort(function (num1, num2) {
  // num1和num2是数组里相邻的两个元素
  // 这个函数必须要有返回值,是 true 或 false
  return num1 - num2 // true 是从大到小  false从小到大
}) // 原始的排序需要双层for循环
console.log(arr9) // [1, 3, 5, 6, 8]
数组和字符串转化 
实际运用中,会经常涉及数组与字符串之间的互相转换问题。
var str = 'hello world welcome to my blog'
// 字符串-->数组
// 参数1 分隔符 参数2 个数限制
var arr6 = str.split(' ', 2)
console.log(arr6) // ["hello", "world"]
// 数组-->字符串
// 参数1 连接符
var str2 = arr6.join('*')
console.log(str2) // "hello*world"
var str3 = arr6.toString()
console.log(str3) // "hello,world"
// 对于toString方法,Number变量调用,传入Number(整数),返回对应的进制形式
var value = 3 // 下面将十进制的数字3,转化为二进制的字符串11
console.log(value.toString(2)) // "11"
数组的迭代方式 
var arr = [1, 2, 3, 4, 5, 6, 3, 2]
// every() 参数函数全部返回真值,every()返回真值
function cb(obj, index, arr) {
  return obj > 3
}
var re = arr.every(cb)
console.log(re) // false
// some() 参数函数只要有一个返回真值,some()返回真值
var re2 = arr.some(cb)
console.log(re2)
var re3 = arr.some(function (obj, index, arr) {
  return obj > 3
})
console.log(re3) // true
// filter() 过滤掉不符合条件的元素,剩下的元素以新数组的形式返回
var arr2 = arr.filter(function (obj, index, arr) {
  return obj >= 3
})
console.log(arr2) // [3, 4, 5, 6, 3]
// forEach() 对每个元素,执行特定操作
arr.forEach(function (obj, index, arr) {
  arr[index] = ++obj
})
console.log(arr) // [2, 3, 4, 5, 6, 7, 4, 3]
数组相关拓展 
- 快速排序
 
function quickSort(arr) {
  var left = [],
    right = []
  var flag = arr.length / 2
  if (!flag) {
    return arr
  }
  var mid = arr.splice(flag, 1)
  arr.forEach(function (num) {
    num <= mid ? left.push(num) : right.push(num)
  })
  return quickSort(left).concat(mid, quickSort(right))
}
数组的运用在 JavaScript 中的运用十分普遍广泛,这部分的基础尤为重要。在实际的工作中,可能运用到第三方的类库,比如JQuery、Lodash等,但是掌握最基础的,才能明白其中的原理。
引用类型的拷贝 
我们在开发过程中对引用类型进行拷贝并修改时,便需要根据场景需求注意对原本数据的影响。
TIP:以引用类型中的对象为代表举 🌰
浅拷贝 

图片来源于如何写出一个惊艳面试官的深拷贝?
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝:基本类型拷贝的是值,引用类型拷贝的就是内存地址;所以当我们操作新对象中的引用类型时会影响源对象
Object.assign()
const obj1 = {
  name: 'tongren',
  props: { a: 1 }
}
const obj2 = Object.assign({}, obj1)
obj2.name = '桐人'
obj2.props.a++
obj1 // { name: 'tongren', props: { a: 2 } }
obj2 // { name: '桐人', props: { a: 2 } }
Array.prototype.concat()
const arr1 = [1, 2, 3, [4, 5]]
const arr2 = arr1.concat()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'
arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
Array.prototype.slice()
const arr1 = [1, 2, 3, [4, 5]]
const arr2 = arr1.slice()
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'
arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
ES6 扩展运算符
/* 对象 */
const obj1 = {
  name: 'tongren',
  props: { a: 1 }
}
const obj2 = { ...obj1 }
obj2.name = '桐人'
obj2.props.a++
obj1 // { name: 'tongren', props: { a: 2 } }
obj2 // { name: '桐人', props: { a: 2 } }
/* 数组 */
const arr1 = [1, 2, 3, [4, 5]]
const arr2 = [...arr1]
arr2[0] = 'arr2'
arr2[3][0] = 'arr2'
arr1 // [1, 2, 3, ['arr2', 5]];
arr2 // ['arr2', 2, 3, ['arr2', 5]];
深拷贝 

深拷贝是将一个对象从内存中完整的拷贝一份出来,即从堆内存中开辟一个新的区域存放新对象,所以修改新对象不会影响原对象
JSON.parse(JSON.stringify())
const obj1 = {
  name: 'tongren',
  props: { a: 1 }
}
const obj2 = JSON.parse(JSON.stringify(obj1))
obj2.name = '桐人'
obj2.props.a++
obj1 // { name: 'tongren', props: { a: 1 } }
obj2 // { name: '桐人', props: { a: 2 } }
JSON.parse(JSON.stringify()) 存在明显的弊端:
- 只能序列化对象的可枚举的自有属性
 undefined、Symbol、任意函数将被忽略NaN、Infinity、-Infinity将被当成null处理RegExp、Error、Set、Map等特殊对象,仅会序列化可枚举的属性(一般情况下即为空对象)Date类型,转换后会调用toJSON转为字符串类型- 循环引用的对象将报错
 
const map = new Map()
map.set(1, 2) // Map: 0: {1 => 2}
const obj1 = {
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: map,
  h: new Date(),
  i: () => {}
}
Object.defineProperty(obj1, 'j', {
  value: 'string'
})
const obj2 = JSON.parse(JSON.stringify(obj1))
/** 源对象 obj1
{
  a: undefined,
  b: null,
  c: Symbol(),
  d: NaN,
  e: Infinity,
  f: -Infinity,
  g: Map(1) {1 => 2}
  h: Fri Mar 10 2023 22:41:08 GMT+0800 (中国标准时间) {},
  i: () => {},
  j: 'string'
}
**/
/** 新对象 obj2
{
  b: null,
  d: null,
  e: null,
  f: null,
  g: {},
  h: '2023-03-10T14:41:08.110Z'
}
**/
structuredClone
HTML规范标准的 Web API
const original = { name: 'MDN' }
original.itself = original
const clone = structuredClone(original)
console.assert(clone !== original) // the objects are not the same (not same identity)
console.assert(clone.name === 'MDN') // they do have the same values
console.assert(clone.itself === clone) // and the circular reference is preserved
HTML 规范的标准提案,使用结构化克隆算法将给定的值进行深拷贝,支持循环引用。还可以使用 structuredClone(value, { transfer }) 调用方式使可转移对象仅被传递,不被克隆(直接移动源数据)
注意点
尽管作为规范标准实现的 Web API,但目前兼容性还是个巨大的问题,同时仍有其他不足:
- 无法拷贝对象的原型链
 - 无法拷贝函数
 - 不支持 
Error数据类型 
MessageChannel
vue.nextTick源码曾使用的Web API,在了解这个API时发现可以用于深拷贝
function cloneUsingChannel(obj) {
  return new Promise(resolve => {
    const channel = new MessageChannel()
    channel.port1.onmessage = e => resolve(e.data)
    channel.port2.postMessage(obj)
  })
}
但该方法存在一个缺陷,当拷贝对象带有函数属性时,将抛出错误:
const obj1 = {
  fn: function () {}
}
const obj2 = cloneUsingChannel(obj1)
// Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'MessagePort': function () {} could not be cloned.
JQuery.extend()
import $ from 'jquery'
const obj2 = $.extend(true, {}, obj1)
lodash.cloneDeep
import { cloneDeep } from 'lodash-es'
const obj2 = cloneDeep(obj1)
类型判断 
常见的五种判断方式
typeofinstanceofconstructorArray.isArray()Object.prototype.toString
typeof 
- 除 
null外的基本类型都能准确判断 
typeof undefined        // 'undefined'
typeof null             // 'object'
typeof true             // 'boolean'
typeof 'tongren'         // 'string'
typeof 2021             // 'number'
typeof Symbol()         // 'symbol'
typeof BigInt(2021)     // 'bigint'为什么 typeof null === 'object'
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此null 的类型标签是 0,typeof null 也因此返回 "object" —— MDN
- 除 
function外的引用类型均返回object 
typeof {}               // 'object'
typeof []               // 'object'
typeof console.log      // 'function'
typeof new Date()       // 'object'
typeof new RegExp()     // 'object'
typeof new Set()        // 'object'
typeof new WeakSet()    // 'object'
typeof new Map()        // 'object'
typeof new WeakMap()    // 'object'instanceof 
instanceof 用于检测构造函数的 prototype 属性是否存在于实例对象的原型链上
/** 基本类型 */
true instanceof Boolean       // false
'tongren' instanceof String    // false
1 instanceof Number           // false
/** 引用类型 */
function Person(name) {
  this.name = name
}
const p1 = new Person('tongren')
p1 instanceof Person          // true
p1 instanceof Object          // true
// 修改原型,使 p1 不再是 Person 的实例
Reflect.setPrototypeOf(p1, Array.prototype)
// OR p1.__proto__ = Array.prototype
p1 instanceof Person          // false
p1 instanceof Array           // true
instanceof 总结
instanceof不能判断基本类型,对于引用类型只能判断原型链上的从属关系instanceof并不完全可靠,因为构造函数的prototype属性可能会被修改- 修改原型的方法 
- 使用 
ES6提供的Reflect.setPrototypeOf()方法 - 借助于非标准的 
__proto__伪属性 
 - 使用 
 
- 修改原型的方法 
 
constructor 
实例对象可以通过 constructor 属性去访问它的构造函数
/** 基本类型 */
(true).constructor === Boolean            // true
'tongren'.constructor === String           // true
(2021).constructor === Number             // true
Symbol().constructor === Symbol           // true
BigInt(2021).constructor === BigInt       // true
/** 引用类型 */
({}).constructor === Object               // true
([]).constructor === Array                // true
function Person(name) {
  this.name = name
}
Person.prototype.constructor === Person   // true
// 修改原型造成 constructor 丢失
Person.prototype = {}
Person.prototype.constructor === Object   // true
constructor 总结
constructor可以判断除undefined和null外的所有基本类型和引用类型(undefined和null不存在构造函数)constructor并不完全可靠,因为构造函数的prototype属性可能会被修改,从而造成constructor属性指向不准确
Array.isArray() 
Array.isArray() 用于判断一个值是否是数组 (Array)
Array.isArray([])   // true
Array.isArray({})   // false
Object.prototype.toString 
- 每个对象都有一个 
toString()方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用,默认情况下toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖toString()返回"[object type]"其中type是对象的类型 - 为了每个对象都能通过 
Object.prototype.toString()来检测,需要以Function.prototype.call()或者Function.prototype.apply()的形式来调用 
const toString = Object.prototype.toString
toString.call(undefined)        // '[object Undefined]'
toString.call(null)             // '[object Null]'
toString.call(true)             // '[object Boolean]'
toString.call('tongren')         // '[object String]'
toString.call(2021)             // '[object Number]'
toString.call(Symbol())         // '[object Symbol]'
toString.call(BigInt(2021))     // '[object BigInt]'
toString.call({})               // '[object Object]'
toString.call([])               // '[object Array]'
toString.call(console.log)      // '[object Function]'
toString.call(new Date())       // '[object Date]'
toString.call(new RegExp())     // '[object RegExp]'
toString.call(new Set())        // '[object Set]'
toString.call(new WeakSet())    // '[object WeakSet]'
toString.call(new Map())        // '[object Map]'
toString.call(new WeakMap())    // '[object WeakMap]'
toString 方法的在 ECMAScript 5 下的大致执行过程
- 如果 
this是undefined返回[object Undefined] - 如果 
this是null返回[object Null] - 让 
O成为ToObject(this)的结果 - 让 
class成为O的内部属性[[Class]]的值 - 返回由 
"[object "class"]"三个部分组成的字符串 
类型转换 
温馨提示
阅读《你不知道的 JavaScript(中卷)》和各个大佬的文章所归纳的总结,如有异议按你的理解为主
将值从一种类型转换为另一种类型称为类型转换 
 在 JavaScript 中进行类型转换时,根据调用形式的不同可以分为以下两种:
- 显式类型转换
 - 隐式类型转换
 
抽象操作 (内部的类型转换规则) 
在了解类型转换前我们需要知道 JavaScript 的 抽象操作 (类型转换规则)
抽象操作 是指仅供内部使用的操作
ToPrimitive将引用类型转换成相应的基本类型值ToString将非字符串值转换成字符串ToBoolean将非布尔值转换成布尔值ToNumber将非数字值转换成数字值
ToPrimitive 
ToPrimitive 用来处理引用类型到基本类型的类型转换
ToPrimitive 转换规则
- 检查是否存在 
Symbol.toPrimitive()- 基本类型直接返回
 - 引用类型抛出 
TypeError错误 
 - 检查是否存在 
valueOf()- 基本类型直接返回
 - 引用类型则继续调用 
toString() 
 - 调用 
toString()- 基本类型直接返回
 - 引用类型抛出 
TypeError错误 
 
注意点
- 使用 
Object.create(null)创建的对象没有原型,即不存在valueOf()和toString(),当对其进行类型转换时会抛出TypeError错误 - 在做显式类型转换时 
valueOf()和toString()的调用顺序会根据转换目标不同去做相应调整- 默认情况下都是先调用 
valueOf()再调用toString() - 当需要转换的目标为字符串时,会先调用 
toString()再调用valueOf() 
 - 默认情况下都是先调用 
 
const obj1 = {
  toString() {
    console.log('toString')
    return []
  },
  valueOf() {
    console.log('valueOf')
    return 2021
  }
}
const obj2 = {
  toString() {
    console.log('toString')
    return 'tongren'
  },
  valueOf() {
    console.log('valueOf')
    return []
  }
}
/** 显式类型转换 */
Number(obj1)    // valueOf              => 2021
Number(obj2)    // valueOf   toString   => NaN
String(obj1)    // toString  valueOf    => '2021'
String(obj2)    // toString             => 'tongren'
/** 隐式类型转换 */
1 + obj1        // valueOf              => 2022
1 + obj2        // valueOf   toString   => '1tongren'
'str: ' + obj1  // valueOf              => 'str: 2021'
'str: ' + obj2  // valueOf   toString   => 'str: tongren'
ToString 
ToString 用来处理非字符串到字符串的类型转换
ToString 转换规则
- 基本类型 
undefined=>'undefined'null=>'null'true=>'true'false=>'false'number- 普通数值直接加引号
 - 极小和极大的数字将转换成指数形式的字符串
 +0 0 -0=>'0'Infinity=>'Infinity'
 - 引用类型会先调用 
ToPrimitive逻辑将其转换成基本类型,如果返回的基本类型不是字符串,再遵循以上规则进行转换 
ToBoolean 
ToBoolean 用来处理非布尔值到布尔值的类型转换,在 JavaScript 中,布尔类型分为真值(true)和假值(false)
- 假值:可以被强制类型转换为 
false的值 - 真值:除假值之外的值
 
ToBoolean 转换规则
- 以下值会被转换成假值(
false)undefinednullfalse+0 0 -0 NaN''
 - 除假值之外的值都会被转换成真值(
true) 
ToNumber 
ToNumber 用来处理非数字值到数字值的类型转换
ToNumber 转换规则
- 基本类型 
undefined=>NaNnull=>0true=>1false=>0string- 空字符串(
'') =>0 - 非数字字符串 => 
NaN 
- 空字符串(
 
 - 引用类型会先调用 
ToPrimitive逻辑将其转换成基本类型,如果返回的基本类型不是数值,再遵循以上规则进行转换 
显式类型转换 
显式类型转换是指显式的去调用类型转换方法
- 转换成布尔值 
Boolean()
 - 转换成数值 
Number()parseInt()parseFloat()
 - 转换成字符串 
String()
 
注意点
Number()转换的是整个值parseInt()和parseFloat()转换的是部分值,是对字符串逐个进行解析和转换,如果传入的参数不是字符串,会先对其进行字符串的转换
隐式类型转换 
隐式类型转换是指在执行过程中,当实际操作的值与 JavaScript 内部期望得到的值不同时,就会对其做隐式类型转换(即不易察觉的类型转换)
 在 JavaScript 中有以下场景会发生隐式类型转换
- 相等运算符 (
==) - 四则运算符 (
+ - * /) - 关系运算符 (
> < >= <=) - 逻辑操作符 (
&& ||) - 条件判断语句 
if()while()- 三元运算符
 
 
相等运算符运算规则(重点) 
为什么 0 == null 是 false ?
0 == null // false
ECMA-262 规范 7.2.12 小节对相等运算符的描述
- 如果 
x不是正常值(比如抛出一个错误),中断执行; - 如果 
y不是正常值,中断执行; - 如果 
Type(x)与Type(y)相同,执行严格相等运算x === y; - 如果 
x是null,y是undefined,返回true; - 如果 
x是undefined,y是null,返回true; - 如果 
Type(x)是数值,Type(y)是字符串,返回x == ToNumber(y)的结果; - 如果 
Type(x)是字符串,Type(y)是数值,返回ToNumber(x) == y的结果; - 如果 
Type(x)是布尔值,返回ToNumber(x) == y的结果; - 如果 
Type(y)是布尔值,返回x == ToNumber(y)的结果; - 如果 
Type(x)是字符串或数值或Symbol值,Type(y)是对象,返回x == ToPrimitive(y)的结果; - 如果 
Type(x)是对象,Type(y)是字符串或数值或Symbol值,返回ToPrimitive(x) == y的结果; - 返回 
false。 
Type(x) 是
the type of x的简写,其中的type是 ECMA-262 规范中定义的 ECMAScript 语言和规范类型
所以在计算 0 == null 时,由于 0 的类型是数值,null 的类型是 Null(这是规格 4.3.13 小节的规定,是内部 Type 运算的结果,跟 typeof 运算符无关);
 因此上面的前 11 步都得不到结果,要到第 12 步才能得到 false。
相等运算符运算规则总结
- 同类型比较时,执行严格相等运算 
x === y undefined与null比较时返回truestring与number进行比较时,先将string做ToNumber处理,再进行比较boolean与其它类型进行比较时,先将boolean做ToNumber处理,再进行比较引用类型与基本类型进行比较时,将引用类型做ToPrimitive处理,再进行比较undefinednull与其它类型的比较时都返回false
四则运算符运算规则 
四则运算符运算规则
-(减)*(乘)/(除) 运算符: 先对操作数做ToNumber处理再执行运算+(加) 运算符- 做一元运算时,对操作数做 
ToNumber处理 - 做二元运算时 
- 当其中一个操作数为 
string时,将另一个操作数做ToString处理再执行字符串拼接 - 当一个操作数为 
number另一个操作数为基本类型时,将基本类型做ToNumber处理再执行运算 - 当一个操作数为 
number另一个操作数为引用类型时,都会先做ToString处理再执行字符串拼接 
 - 当其中一个操作数为 
 
- 做一元运算时,对操作数做 
 
关系、逻辑、条件运算符运算规则 
关系运算符运算规则
- 将引用类型做 
ToPrimitive处理 - 如果两个参数都是 
string类型时进行Unicode 编码大小比较 - 否则将两个参数做 
ToNumber处理,再进行数值大小比较 
逻辑操作符与条件判断语句
在逻辑操作符与条件判断语句中都是做 ToBoolean 处理