1. 数据类型定义
/*数据类型定义
+ 原始值类型 [值类型/基本数据类型]
+ Number 数字
+ number类型
+ 正数 负数 0 小数
+ NaN (不是一个数, 但它属于 number 类型的)
+ NaN === NaN ==> false (NaN与自身都不相等)
+ 判断是否是 NaN(或者说判断是不是有效数字)
+ Object.is([value], NaN) ---> Object.is(NaN, NaN) ==> true
+ isNaN([value]) --> 检测一个值是否为有效数字(不是有效数字返回 true),
如果值不是 number 类型, 会先隐式转换为数字类型再去检测
+ Infinity(无限值) -Infinity
+ String 字符串
+ ''
+ ""
+ ``
+ Boolean 布尔
+ true
+ false
+ Null 空对象指针
+ Undefined 未定义
+ Symbol 唯一值
+ Bigint 大数
+ 对象类型 [引用数据类型]
+ 标准普通对象 object
+ 如: var obj = {}
+ 标准特殊对象 Array RegExp Date Math Error ...
+ 如: var arr = []
+ 非标转特殊对象 Number String Boolean ...
+ 如: let a = new Number(1)
a = {1}
+ 可调用/执行对象[函数] function
+ function add(){}
*/
// bigint 数据类型
// Number.MAX_SAFE_INTEGER 最大安全数字 9007199254740991(2**53 - 1), 超过这个数字进行运算, 运算结果不一定准确
// 需求: 服务器端数据存储值有 longInt(大数类型), 如果服务器返回这样的值(一般会以字符串形式返回), 而且需要客户端在这个
// 值基础上再次运算, 我们该如何处理??? 注意: 超出最大安全数字的数,必须以字符串的形式直接通过 BigInt('超出最大安全数字字符串')
// 1. 把服务器取的值[value]先转换为bigint类型, BigInt([value]) --> BV 数字后加n 如: BigInt('9007199254740991234')
// 2. 基于 BV 进行运算(运算的另外一个值也是 bigint 类型)
// 3. 把运算的结果转换为字符串(去掉n)再传给服务器 (9007199254740991234n).toString()
// 1 // 1
// new Number(1) // {1}
// NaN === NaN // false
// Object.is(NaN, NaN) // true
// isNaN(NaN) // true
// let num = '1dklfhhjl'
// if (Object.is(num, NaN)) {
// // num就是NaN
// alert('Object.is(num, NaN)')
// }
// if (isNaN(num)) {
// // num不是有效数字
// alert('isNaN(num)')
// }
// 隐式转换(浏览器私底下就转换了)
// isNaN([value]) 如果[value]不是number类型, 浏览器会将 [value] 先变为 number 类型, 再去检测
// Number([value]) 隐式转换
// 显式转换 Number([value]) parseInt([value]) parseFloat([value])
// Number([value]) 强制转换
// parseInt([value]) parseFloat([value])弱转换
// Symbol
// console.log(Symbol() === Symbol()) // false
// console.log(Symbol('AA') === Symbol('AA')) // false
// console.log(new Symbol()) // Symbol is not a constructor
// Symbol 并不是一个构造函数, 所以不能通过new来实例化对象; Symbol()只是一个函数
// Symbol的作用
// 作用1: 可以给对象设置唯一值类型的属性[对象"属性名"的类型: string symbol]
let a = [10, 20]
let sym = Symbol('BB')
let obj = {
name: '张三',
age: 13,
0: 100,
[{ xxx: 'xxxx' }]: 200, // "[object Object]": 200,
[a]: 300,
[Symbol('AA')]: 400,
[sym]: 500
}
// console.log(obj['name']) // "张三"
// console.log(obj[0]) // 100
// console.log(obj['0']) // 100
// console.log(obj[Symbol('AA')]) // undefined (Symbol('AA') 与 Symbol('AA') 是两个不同的唯一值)
// console.log(obj[sym]) // 500 (sym 保存的同一个 Symbol('BB') 值)
// --------
let arr = []
for (var key in obj) {
// for in 循环是无法迭代 Symbol 类型的私有属性
arr.push(key)
}
console.log(arr)
// 获取非 Symbol类型的私有属性, 不论是啥类型
let keys = Object.keys(obj)
console.log(keys)
// 获取所有私有属性, 不论是啥类型
let keyss = Reflect.ownKeys(obj)
console.log(keyss)
// 作用2: 把Symbol作为对象, 提供的很多静态属性的方法, 是JS很多知识的底层实现原理
// Symbol.toPrimitive / hasInstance / toStringTag / iterator / asyncIterator ...
// 作用3: vuex / redux 中我们要派发很多行为标识统一管理, 为了保证行为标识的唯一性, 所以可以基于 Symbol 处理
2. 其他数据类型转换成Number数字类型规则
/* Number([val])
+ 一般用于浏览器的隐式转换中
+ @1 数学运算
+ @2 isNaN 检测
+ @3 == 比较
+ ...
+ 规则:
+ @1 字符串转为数字: 空字符变为 0, 如果出现任何非有效数字字符, 结果都是 NaN
+ @2 把布尔转换为数字: true ---> 1, false ---> 0
+ @3 null ---> 0 undefined ----> NaN
+ @4 Symbol 无法转换成数字, 会报错: Uncaught TypeError: Cannot conver a Symbol value to a number
+ @5 BigInt 去除"n"(超过安全数字的,会按照科学计数法处理)
+ @6 把对象转为数字
+ 先调用对象的 Symbol.toPrimitive 这个方法, 如果不存在这个方法
+ 再调用对象的 valueOf 获取原始值, 如果获取的值不是原始值
+ @1 如果存在任意原始值, 它就默认将对象转化为表示它的原始值
+ @2 对象是复合值, 而且大多数对象无法真正表示为一个原始值, 因此默认的 valueOf() 方法简单地返回对象本身, 而不是返回一个原始值
+ @3 数组 函数 和 正则表达式 简单地继承了这个默认的方法, 调用这些类型的实例的valueOf()方法只是简单返回 对象本身
+ @4 日期类定义的 valueOf() 方法会返回它的一个内部表示: 1970年1月1日以来的毫秒数
如: var times = new Date() ---> times.valueOf() ==> 126687838838
+ 再调用对象的 toString 把其变为字符串
+ 最后再把字符串基于 Number 方法转化为数字
+ parseInt([val], [radix]) parseFloat([val])
+ 一般用于手动转换
+ 如果第一个非空格字符是非法的数字直接量, 最终返回 NaN
+ 规则: [val]值必须是一个字符串, 如果不是则先转化为字符串; 然后从字符串左侧第一个字符开始找, 把找到的有效数字字符,
最后转换为数字(一个都没找到就是 NaN; 遇到一个非有效数字字符, 不论后面是否还有有效数字字符, 都不再查找了;
parseFloat 可以多识别一个小数点)
+ parseInt([val {string}], [radix])
+ 在[val]字符串中, 从左到右找到符合[radix]进制的内容, 然后把这些内容当做 [radix] 进制, 转换为 10 进制的数字
+ [radix]的取值范围: 2~36 , 不在这个范围内, 最后结果都是 NaN
+ [radix]不写或者写0: 默认值是10(特殊: 如果[val]字符串是以0x开始的,默认值是16)
+ 在浏览器中有一个特殊情况: 无论是 Number([val]) 还是 parseInt([val]); [val] 是数字类型并且是以0开始的,
浏览器会认为这应该是8进制的值, 它会把8进制默认转化为10进制再处理
Number(012) ---> 10 parseInt(012) ---> 10
*/
// console.log(10 - '5') // 5
// console.log(10 - true) // 9
// console.log(10 + '5') // '105'
// console.log(10 + true) // 11
// console.log(NaN === NaN) // false
// console.log(isNaN(1)) // false
// console.log(isNaN('AA')) // true
// console.log(isNaN('1A')) // true
// // isNaN 对字符串进行判断时, 字符串首先经过Number进行转化, isNaN再对转化过的值进行判断
// console.log(isNaN(NaN)) // true
// console.log(isNaN(1)) // false
// console.log(isNaN(0)) // false
// console.log(isNaN('0')) // false
// console.log(isNaN(true)) // false isNaN(true) ---> isNaN(Number(true)) ---> isNaN(1) ---> false
// console.log(isNaN(false)) // false
// console.log(parseInt('1030px', 2))
// 在字符串中, 找到所有符合二进制的内容 --> '10'
// 把 '10' 看做二进制, 转换为十进制? 如何把其他机制的值转化为十进制 "按权展开求和"
// 个位数权重 0 , 十位数权重 1, 百位数权重 2 ...
// 1*2^1 + 0*2^0 = 2 + 0 = 2
// 练习题
// let arr = [27.2, 0, '0013', '14px', 123]
// let ary = arr.map(parseInt)
// console.log(ary)// [27, NaN, 1, 1, 27]
// let obj = {
// name: '张三',
// age: 12,
// [Symbol.toPrimitive](hint) {
// console.log(hint)
// let result;
// switch (hint) {
// case 'number':
// result = 0;
// break;
// case 'string':
// result = JSON.stringify(obj);
// break;
// case 'default':
// result = '';
// }
// return result
// }
// }
// obj[Symbol.toPrimitive] = function (hint) {
// // hint: 'number' / 'string' / default
// // + number: 获取当前对象的数字类型的原始值
// // + string: 获取当前对象的字符串类型的原始值
// // + default: 根据操作获取数字或者字符串类型的原始值
// }
// Number(obj); // hint: "number"
// String(obj); // hint: "string"
// 10 + obj; // hint: "default"
// 10 - obj; // hint: "number"
/*
+ javaScript 中 对象到字符串的转换
+ @1 如果对象具有toString()方法,则调用这个方法;
如果它返回一个原始值,javaScript将这个值转化为字符串(如果本身不是字符串), 并返回这个字符串结果
+ @2 如果对象没有toString()方法, 或者这个方法并不返回一个原始值, 那么javaScript会调用valueOf()方法,
如果存在这个方法,则javaScript调用它; 如果返回值是原始值, javaScript将这个值转化为字符串(如果本身不是字符串),
并返回这个字符串结果
+ @3 否则, javaScript无法从 toString() 或 valueOf()获得一个原始值, 会抛出一个类型错误异常
+ javaScript对象到数字的转化过程
+ @1 如果具有 valueOf()方法, 后者返回一个原始值, 则javaScript将这个值转换为数字(如果需要的话)并返回这个数字
+ @2 否则, 如果对象具有 toString()方法, 后者返回一个原始值, 则javaScript将其转换并返回
+ @3 否则, javaScript会抛出一个类型错误异常
+ @4 数组继承了默认的 valueOf()方法, 这个方法返回一个对象而不是一个原始值, 因此数组到数字的转换则调用toString()方法,
空数组转换成空字符串, 空字符串转换为数字 0, 含有一个元素的数组转换为字符串的结果和这个元素转换字符串的结果一样,
如果数组只包含一个数字元素, 这个数字转换成字符串, 再转回数字
*/
console.log([10]) // 10
/*
+ @1 [10][Symbol.toPrimitive] --> undefined
+ @2 [10].valueOf() --> [10] 不是原始值
+ @3 [10].toString() --> "10"
+ @4 Number("10") --> 10
*/
console.log([10, 20]) // NaN
/*
+ @1 [10,20][Symbol.toPrimitive] --> undefined
+ @2 [10,20].valueOf() --> [10,20] 不是原始值
+ @3 [10,20].toString() --> "10,20"
+ @4 Number("10,20") --> NaN
*/
console.log(Number(new Number(10)))
/*
+ @1 (new Number(10))[Symbol.toPrimitive] --> undefined
+ @2 (new Number(10)).valueOf() --> 10
*/
let time = new Date()
console.log(Number(time)) // time[Symbol.toPrimitive]('number') ==> 1637216018732
console.log(String(time)) // time[Symbol.toPrimitive]('string') ==> Thu Nov 18 2021 14:13:38 GMT+0800 (中国标准时间)
/*
time[Symbol.toPrimitive] ---> 函数
Number(time) --> time[Symbol.toPrimitive]('number') ---> 1637215829280
time[Symbol.toPrimitive]([str]) 执行这个方法可以传递3个参数 'number' / 'string' / 'default'
Number(对象) 按照 time[Symbol.toPrimitive]('number') 规则处理,
除了 Number(对象) 外传递的参数都是 'string'
*/
parseInt('') // NaN
// 规则: parseInt([val])的[val]值必须是一个字符串, 如果不是则先转化为字符串; 然后从字符串左侧第一个字符开始找, 把找到的有效数字字符,
// 最后转换为数字(一个都没找到就是 NaN; 遇到一个非有效数字字符, 不论后面是否还有有效数字字符, 都不再查找了;parseFloat 可以多识别一个小数点)
Number('') // 0
parseInt(null) // NaN 先将 null 转换为字符串 "null" ----> 然后处理 parseInt("null") ==> NaN
Number(null) // 0
3. 其他数据类型转化为String字符串类型规则
/* 其他数据类型转化为 String类型
+ 转化规则
+ @1 拿字符串包起来
+ @2 特殊: Object.prototype.toString
+ 出现情况:
+ @1 String([val]) (默认隐式转换) 或者 [val].toString() (手动转换)
+ @2 "+" 除数学运算, 还可能代表字符串拼接
"+" 有两边, 一边是字符串, 一定是字符串拼接
"+" 有两边, 一边是对象, 会把对象String([val])
+ @1 调用obj[Symbol.toPrimitive]('default')
+ @2 没有这个属性, 则再次调用 valueOf
+ @3 valueOf 获取的不是原始值, 则继续toString, 此时获取到的结果是字符串, "+" 就变为字符串拼接了
"+" 有两边, 剩下的情况一般都是数学运算了
"+" 只有一边, 例如: +xxx
+ 一般都是把xxx转换为数字(基于Number(xxx))
"+" ...
+ 把对象obj转换为字符串
+ String(obj): Symbol.toPrimitive ==> valueOf ==> toString 浏览器默认隐式转换用的是 String(obj)
+ obj.toString(): 直接调用这个方法转字符串, 不会在执行以上规则
*/
// console.log(10 + [10])
// // 没有Symbol.toPrimitive --> valueOf获取的也不是原始值 --> 调用toString "10" ==> "1010"
// console.log(10 + {})
// // 没有Symbol.toPrimitive --> valueOf获取的也不是原始值 --> 调用toString "[object Object]" ==> "10[object Object]"
// console.log(10 + new Date())
// // 调用日期的 Symbol.toPrimitive('default') ==> "10Wed Nov 17 2021 10:40:49 GMT+0800 (中国标准时间)"
// console.log(10 + new Number(10))
// // 没有Symbol.toPrimitive --> new Number(10).valueOf() 10 数学运算 ==> 20
// console.log(10 + new String(10))
// // 没有Symbol.toPrimitive --> valueOf "10" ==> "1010"
// console.log(String(10)) // "10"
// console.log(String(NaN)) // "NaN"
// console.log(String(true)) // "true"
// console.log(String(null)) // "null"
// console.log(String(undefined)) // "undefined"
// console.log(String(Symbol())) // "Symbol()"
// console.log(String(10n)) // "10" (bigInt类型)
// console.log(String({})) // "[object Object]"
// console.log(String([10, 20])) // "10,20"
// console.log(String(/\\d+/)) // "/\\d+/"
// console.log(String(function () { })) // "function () { }"
// console.log(String(new Date())) // "Wed Nov 17 2021 09:57:26 GMT+0800 (中国标准时间)"
// console.log(Object.prototype.toString.call(({}))) // "[object Object]"
// console.log(+ undefined) // NaN
// // console.log(+ Symbol()) // 报错
// // console.log(+ 10n) // 报错
// console.log(Number(10n)) // 10
console.log({} + 10) // "[object Object]10"
console.log(({} + 10)) // "[object Object]10"
console.log(10 + {}) // "10[object Object]"
// 练习题
let result = 100 + true + 21.2 + null + undefined + 'Tencent' + [] + null + 9 + false
// 100 + true + 21.2 + null + undefined + 'Tencent' + [] + null + 9 + false =>122.2 + null + undefined + 'Tencent' + [] + null + 9 + false
// => 122.2 + undefined + 'Tencent' + [] + null + 9 + false => NaN + 'Tencent' + [] + null + 9 + false
// => "NaNTencent" + [] + null + 9 + false => "NaNTencent" + null + 9 + false
// => "NaNTencentnull9false"
console.log(result) // "NaNTencentnull9false"
</script>
<script>
/*
+ 其他类型值转换为字符串 String([value])常用于 隐式 或者 [value].toString()常用于显示
+ 重点: 对象转换为字符串基于这两种方式还是有区别的
+ 对象.toString() 直接调取所属类原型上的toString方法进行处理
+ String(对象) 首先获取 对象[Symbol.toPrimitive] 这个属性, 如果存在这个属性则 对象[Symbol.toPrimitive]('string')
如果不存在, 则继续调用 对象.valueOf(), 如果获取的是原始值则结束即可
如果不是, 才会调用 toString()方法进行处理...
+ 标准普通对象调用toString 是 Object.prototype.toString(), 是用来检测数据类型的, 而不是转换为字符串的
+ 常见的隐式转换为字符串的情况
+ 字符串拼接["+" 在JS中除了数学运算也有字符串拼接的意思]
+ 有两边, 一边是字符串, 一定是字符串拼接 代码: [value] + '' ==> 把 [value] 转换为字符串
+ 有两边, 一边是对象: 可能回事字符串拼接, 因为其要把对象转换为数字, 转换过程中如果遇到转换为字符串, 则直接变为字符串拼接;
如果遇不到还是数学运算
+ 只有一边 + [value]: 一定是把[value]转换为数字的
+ parseInt([value]) 如果[value]不是字符串也要先变为字符串再处理
+ alert / confirm / prompt / document.writhe 都会把内容变为字符串, 然后再输出
*/
console.log(12 + [13])
// [13][Symbol.toPrimitive] --> [13].valueOf() --> [13].toString() "13" 直接变为字符串拼接 ==> "1213"
// let i = '10'
// i++; 一定是数学运算, 转换为数字累加1
// i = i + 1; i += 1; 有字符串拼接的情况
// console.log(i)
let i = '10'
console.log(10 + (++i)); //++i 先把i累加1 --> 11 再拿累加的结果和10运算 => 21 i=11
console.log(10 + (i++)); //i++ 先拿i的值和10运算, 再让i累加1 => 20 i=11
4. 其他类型转换为Boolean 布尔值类型
/* 把其他类型数据转换为布尔值
+ 规则:
+ 把其他类型转换为布尔值 只有 0(字符串'0'是true) / NaN / 空字符串(必须没有空格) / null / undefined / false 六个值转化为布尔值是 false, 其余都是 true
哪怕是把对象转换为布尔, 也不再调用 Symbol.toPrimitive 那套机制了, 直接看是不是
六个值(0 / NaN / 空字符串 / null / undefined / false)当中的一个即可
+ 出现情况:
+ @1 Boolean([val]) 或者 ! 或者 !!
+ @2 条件判断
*/
// 特别注意:
Boolean('') // false (空字符串中间无空格)
Boolean(' ') // true (空字符串中间有空格)
Boolean(0) // false
Boolean('0') // true
/*
非标准特殊对象「原始值对应的对象类型实例」的 valueOf 获取的才是原始值
Date具备[Symbol.toPrimitive]
Date的valueOf 获取的也是原始值 「距离1970-1-1午夜间的毫秒差」
*/
5. 基本数据类型之间的转换
/*基本数据类型之间相互转换
值 转换为字符串 转换为数字 转换为布尔值
undefined ------------ "undefined" ------------- 0 ------------------------ false
null ------------------ "null" -------------------- 0 ------------------------ false
true ------------------ "true" -------------------- 1
false ----------------- "false" ------------------ 0
"" ---------------------------------------------- 0 ------------------------- false
0 ----------------------- "0" ------------------------------------------------- false
NaN -------------------- "NaN" ------------------------------------------------ false
1 ----------------------- "1" ------------------------------------------------- true
*/
6. “==” 判断规则
/* "=="判断规则
+ "=="相等, 两边数据类型不同, 需要先转为相同类型, 然后再进行比较
+ @1 对象 == 字符串 --> 对象转字符串 [Symbol.toPrimitive] ---> valueOf ---> toString 机制
+ @2 null == undefined --> true ; null / undefined 和其他任何值都不相等
null === undefined --> false
+ @3 对象 == 对象 比较的是堆内存地址, 地址相同则相同
+ @4 NaN != NaN ==> true
+ @5 除了以上情况, 只要两边类型不一致, 剩下的都是转换为数字, 然后再进行比较的
+ "==="绝对相等, 如果两边类型不同, 则直接是 false, 不会转换数据类型[推荐]
*/
// console.log([] == false)
// 都转为数字 Number([]) --> String([]) = '' --> Number('') = 0 ==> Number([]) == 0
// Number(false) ==> 0
// 0 == 0 ==> true
// console.log(![] == false)
// 需要先算 ![] ==> false
// false == false ==> true
// 练习题
// var a = ?
// if (a == 1 && a == 2 && a == 3) {
// console.log('ok')
// }
// 解决方案一: 利用 == 比较的时候, 会把对象转换为数字 Number(a)
/*
+ Symbol.toPrimitive
+ valueOf
+ toString
+ 把字符串变为数字
*/
// // @1
// var a = {
// i: 0
// }
// a[Symbol.toPrimitive] = function toPrimitive() {
// return ++this.i
// }
// if (a == 1 && a == 2 && a == 3) {
// console.log('ok')
// console.log(a)
// }
// // @2
// var a = [1, 2, 3]
// a.toString = a.shift
// if (a == 1 && a == 2 && a == 3) {
// console.log('ok')
// console.log(a)
// }
// 解决方案二: 在全局上下文中, 基于 var / function 声明的变量, 并不是给 VO(G)设置的全局变量[基于 let / const 声明的变量才是]
// 而是给GO(window)全局设置的属性 var a = ? ==> window.a = ?
var i = 0
Object.defineProperty(window, 'a', {
get () {
return ++i
}
})
if (a == 1 && a == 2 && a == 3) {
console.log('ok')
}
// 特别注意
Symbol === Symbol // true
Symbol() === Symbol // false
Symbol() === Symbol() // false
7. 数据类型转换练习题
isNaN(parseInt(new Date())) + Number([1]) + typeof undefined; // "2undefined"
!(!"Number(undefined)") // true
parseFloat("1.6px") + parseInt("1.2px") + typeof parseInt(null) // "2.6number"
Boolean(Number("")) + !isNaN(Number(null)) + Boolean(parseInt([])) + typeof !(null) // "1boolean"
isNaN(Number(!!Number(parseInt(0.8)))) // false
!typeof parseFloat('0') // false
Number("") // 0
16 + {} // "16[object Object]"
{ } + 16 // 16
var n = 6;
console.log((n++) + n-- + 5 + --n + --n) // 27
// 7 + 5 + 5 + 6 + 4 = 27
console.log(n) // 4
</script>
<script>
var str = 'abc123'
var num = parseInt(str)
if (num == NaN) {
alert(NaN)
} else if (num == 123) {
alert(123)
} else if (typeof num == 'number') {
alert('number') // 走这里
} else {
alert(str)
}
if (isNaN(NaN)) {
console.log('哈哈')
} else {
console.log('哈哈哈哈')
}