一、JS的数据类型
基本数据类型
-
number
-
string
-
boolean
-
null 空对象指针
-
undefined 未定义
-
Symbol 创建一个唯一值,Symbol不能被new
let a1 = Symbol('AA'); let a2 = Symbol('AA'); let a3 = a1; console.log('A1 === A2'); //false应用场景:
-
没有ES6之前,属性名类型只能是字符串,ES6之后,现在属性名也可以是Symbol类型
let key = Symbol('BB'); let obj = { n: 10, 10: 100, [Symbol('AA')]: 300, [key]: 400 } console.log(obj[Symbol('AA')]); //undefined console.log(obj[key]); //4400 -
Symbol.asyncIterator / iterator / hasIntance / toPrimitive / toStringTag... 是某些JS知识底层实现的机制
-
vuex在派发行为标识统一进行管理的时候,可以基于Symbol类型的值, 保证行为标识的唯一性
-
-
BigInt大数类型
Number.MAX_SAFE_INTEGER:最大安全数9007199254740991「16位」 Number.MIN_SAFE_INTEGER:最小安全数-9007199254740991 超过安全数后再进行运算或者访问,结果会不准确!!需求:
服务器数据库存储值有logInt(大数类型),如果服务器返回这样的值,而且需要客户端在此基础上进行运算,但是由于客户端有最大安全数,超过这个数字进行运算, 运算结果不一定准确解决方案:
- 服务器返回给客户端的大数,按照“字符串”格式返回 - 客户端把其变为BigInt,然后按照BigInt进行运算 - 最后把运算后的BigInt转换为字符串,再传递给服务器就可以 console.log((BigInt('9007199254740991898') + BigInt('12345')).toString())
对象类型【引用数据类型】
-
标准对象Object
-
标准特殊对象:
- 数组Array
- 正则RegExp
- 日期Date 实例
- 数学Math
- Error
-
非标准特殊对象:Number、String、Boolean:基于构造函数或者Object创造出来的原始值对象类型的格式信息,类型属于对象类型(Symbol和BigInt不能被new)
-
可调用执行对象:函数function
二、JS中有关于小数(浮点数)的计算会出现精度丢失的问题:0.1+0.2 !== 0.3
原因: JS中所有值都是以二进制在计算机底层进行存储的,就会涉及到一个问题,它会默认把十进制值转为二进制值「而浮点数转为二进制会出现无限循环的情况」,但是我们在计算机底层存储的时候最多存储64位,那就说明舍弃了一些值,直接按64位存储,说明值本身就失去了精准度,所以如果拿失去精准度的值和另外一个失去精准度的值运算,运算的结果肯定也是失去精准度的。
解决方案:
- toFixed保留小数点后面N位,他自己会四舍五入
- 扩大系数法
十进制转二进制:
(10).toString(2); //'1010'
(0.1).toString(2) //'0.0001100110011001100110011001100110011001100110011001101'
附图:第一张图为10转为二进制,第二张图为0.1转二进制
三、数据类型检测
typeof
-
所有的数据类型值在计算机底层都是按照"644位"二进制进行存储
-
typeof是按照二进制进行检测类型的
-
二进制的前三位是零,认为是对象,然后再去看有没有实现call方法,如果实现了,返回'function',如果没有实现,返回'object'
局限性
-
typeof null :null的二进制是64个0,而typeof认为前三位是0的都是'object'
-
typeof 检测对象,除函数对象会返回"function",其余对象返回的都是"object",不能细分对象
-
检测未被声明的变量,不会报错,会返回'undefined' 应用
-
检测除null之外的原始值类型都可以使用typeof
-
检测是否为对象,if ( obj !== null && /^(object|function)$/i.test(typeof obj))
-
检测某个东西是否兼容:if (typeof Symbol !== 'undefined') {...}
-
支持更多的模块导入方案
(function(){ let utils = {}; if( typeof window !== 'undefined' ) window.utils = utils; if( typeof module === 'object' && typeof module.export === 'object' ) module.export = utils; })();
instanceof 检测对象是否为这个类的实例: [对象] instanceof [构造函数]
局限性:
-
对原始值类型无效,instanceof左边只要是原始值类型,结果就是false
-
因为可以通过setPrototypeOf修改原型链指向,所以检测结果不一定准确
-
无法检测是否为“标准普通对象(Object的实例,直接找object原型),因为所有对象都是Object的实例,检测结果都是true(当使用instance判断是否是该对象的实例时,会调用Symbol.hasInstance这个方法)
//当执行arr instance Array的时候 arr instance Array ; //true //相当于 Array[Symbol.hasInstance](arr); //true
原理:
-
传统版本:instanceof检测是按照实例的原型链进行查找的,只要构造函数的prototype出现在了对象的原型链上,那么检测结果都是true
-
新版本(es6):先检测构造函数是否拥有Symbol.hasInstance方法「ES6之后,Function.prototype设置了Symbol.hasInstance方法,所以函数都具备这个属性」,如果有这个方法,构造函数Symbol.hasInstance,返回的值就是我们要的值
let arr = []; arr instanceof Array; //true arr instanceof RegExp; //falsee arr instanceof Object; //true 1 instanceof Number;//false,不能检测基本数据类型 let obj = {} Object.setPrototypeOf(obj, RegExp.prototype) console.log(obj instance RegExp) //true
constructor 本意:获取构造函数
-
constructor可以随便改,所以也不准确
-
可以检测是否为标准普通对象(如下面例子数组.constructor 不等于object)
-
对原始值也有效(除null/undefined,因为他俩无法进行成员访问)
let arr = []; arr.constructor === Array ; //true arr.constructor === RegExp ; //false arr.constructor === Object ; //false let num = 1; num.constructor === Number //true
Object.prototype.toString.call( [value] )
除Object.prototype.toString之外,其余构造函数原型上的toString一般用来“转换字符串的”,只有它是用来检测数据类型的,检测结果为[object xxx]
原理:
首先会看这个被检测的value值有没有Symbol.toStringTag这个属性,有的话,属性值是什么,则结果中的xxx 就是谁,没有这个属性,一般当前实例所属的构造函数(以内置的为主)
- Array.isArray()
- isNaN
四、数据类型间相互转换
1.把其他类型转换为number
一般用于浏览器的隐式转换中
@1 数学运算
@2 isNaN检测
@3 ==比较
规则:
-
字符串转换为数字:空字符串变为0,如果出现任何非有效数字字符,结果都是NaN
-
把布尔转换为数字:true->1 false->0
-
null->0 undefined->NaN
-
Symbol无法转换为数字,会报错:Uncaught TypeError: Cannot convert a Symbol value to a number
-
BigInt去除“n”(超过安全数字的,会按照科学计数法处理)
-
把对象转换为数字:
+ 先调用对象的 Symbol.toPrimitive 这个方法,如果不存在这个方法 + 再调用对象的 valueOf 获取原始值,如果获取的值不是原始值 + 再调用对象的 toString 把其变为字符串 + 最后再把字符串基于Number方法转换为数字
2.parseInt([val],[radix]) parseFloat([val])
-
一般用于手动转换
-
规则:[val]值必须是一个字符串,如果不是则先转换为字符串;然后从字符串左侧第一个字符开始找,把找到的有效数字字符最后转换为数字「一个都没找到就是NaN」;遇到一个非有效数字字符,不论后面是否还有有效数字字符,都不再查找了;parseFloat可以多识别一个小数点;
练习题: let arr = [27.2, 0, '0013', '14px', 123]; arr = arr.map(parseInt);
3.把其他数据类型转换为String
转化规则:
-
拿字符串包起来
-
特殊:Object.prototype.toString
出现情况:
-
String([val]) 或者 [val].toString()
-
“+”除数学运算,还可能代表的字符串拼接
+ 有两边,一边是字符串 + 有两边,一边是对象 + 只出现在左边 + ... 练习题: let result = 100 + true + 21.2 + null + undefined + "Tencent" + [] + null + 9 + false; console.log(result);
4.把其他数据类型转换为Boolean
转换规则: 除了“0/NaN/空字符串/null/undefined”五个值是false,其余都是true
出现情况:
@1 Boolean([val]) 或者 !/!!
@2 条件判断
5.==”比较时候的相互转换规则
“==”相等,两边数据类型不同,需要先转为相同类型,然后再进行比较
-
对象==字符串 对象转字符串「Symbol.toPrimitive -> valueOf -> toString」
-
null==undefined -> true null/undefined和其他任何值都不相等
-
null===undefined -> false
-
对象==对象 比较的是堆内存地址,地址相同则相等
-
NaN!==NaN
-
除了以上情况,只要两边类型不一致,剩下的都是转换为数字,然后再进行比较的
“===”绝对相等,如果两边类型不同,则直接是false,不会转换数据类型「推荐」
练习题:
console.log([] == false);
console.log(![] == false);
五、什么情况下a == 1 && a ==2 && a==3
var a = ?;
if (a == 1 && a == 2 && a == 3) {
console.log('OK');
}
方案一、利用==把a转为数字,再根据对象转数字会经历一系列的操作,此时可以重写一些步骤来实现
var a = {
i: 0,
//重写Symbol.toPrimitive/valueOf/toString
[Symbol.toPrimitive](){
return ++this.i;
}
}
// a==1 ->Number(a) -> a[Symbol.toPrimitive]()
if(a==1 && a==2 && a==3) {
console.log('OK')
}
方案二、在全局上下文中基于var声明变量,是给window对象设置属性,再利用数据劫持
var i = 0;
Object.defineProperty(window, 'a', {
get() {
return ++i;
}
})
if(a==1 && a==2 && a==3) {
console.log('OK')
}
方案三
var a = [1,2,3]
a.toSting = a.shift;
if(a==1 && a==2 && a==3) {
console.log('OK')
}