有生之年系列
总结一下子数据类型,基础中的基础。
有哪些数据类型?
js中有两类数据类型,可分为
- 原始类型Primitive Value
 - 引用类型Reference Value
 

这是大类,在此基础上分出很多 面试题 不,知识点来。
数据类型举例
基础内容略。
注意这部分引用子《《JavaScript高级程序设计》第4版》
- 3.3 string部分,提到了转义,有个表格,略过没有整理。觉得不是很必要
 - 3.6 Symbol,我单独在下面整理
 - 3.9 类型转换这里,我觉得有了ts,这块知识是毒瘤,就没有整理。
 
// Numberconsole.log(Infinity,Number.MAX_VALUE, NaN)// 如果对数学运算比较严格,应该去使用 Math第三方库// BigInt 123n 是单独的类型typeof 1n === 'bigint'
Symbol
JS悟道作者直接说 Symbol是多余的东西,哈哈哈
Symbol 是原始值,符号是唯一的不可变的,目的是创建唯一记号,这让对象的key可以是非字符串,有字符串和数字的地方就可以有symbol。
这个和私有属性没有关系,也很容易拿到Symbol
// Symbollet n=Symbol('n')// 多次返回不同的值,这样就可以重写对象的属性,不用担心会覆盖,比较安全typeof n // 'symbol' 是简单值// 每次不等,证明唯一Symbol() === Symbol() // false// Symbol.for 可以全局重用,定义同一个符号var a=Symbol.for('aa');var b=Symbol.for('aa') // 查到了就复用a===b // true// 反解keySymbol.keyFor(a)//'aa'// Symbol 能遍历得到吗?var o={[a]:1}// object中很容易拿到符号keyObject.getOwnPropertySymbols(o) // [Symbol(aa)]// 万能的Reflect.ownKeys(o)`
还有一些其他属性,实在记不住。
对symbol来说,更重要的是 常用内置符号well-known symbol ,可以暴露js内部行为,这就可以让我们重写、覆盖一些行为。 我觉得目前只需要知道symbol能重写js行为 就够了,没啥应用场景。
es6 允许显示指定 toPrimitive Symbol覆盖原有行为,也就是可以改变把对象转成原始值。重写 valueof , toString 。(这一点《重学前端》也有提及。)
class Bar {constructor() {this[Symbol.toPrimitive]=function(hint){switch(hint){case 'number':return 3;case 'string':return 'stringbar';case 'default':default:return 'defaultbar';}}}}let bar = new Bar();console.log(3 + bar); // "3default bar"console.log(3- bar); // 0console.log(String(bar)); // "string bar"
Object
基本api略过,参考《JS对象》
Array
基本api略过。参考《JS之数组》
高程4讲 [].sort时候并没有提及多种排序算法,等到数据结构与算法时候再说吧。
Math
为什么 0.1+0.2 不等于0.3
有问题的举例如下:
0.1+0.20.28*1001-0.919.9*100#4.661*1855正常结果是8646.155,四舍五入就是8646.16#但因为精度缺失,变成了8646.154999999999,导致四舍五入是8646.15
一句话描述:浮点数误差,用IEEE 754 的都有这个问题,包括Python。
底层原理稍微复杂,我引用一篇文章 JavaScript精度丢失问题
要解决这个问题,建议使用第三方 math.js https://mathjs.org/docs/datatypes/numbers.html#roundoff-errors
A solution is to limit the precision just below the actual precision of 16 digits in the displayed output 解决方案是将精度限制在显示输出中16位的实际精度以下
比如
// math.format(math.evaluate(.1+.2),{precision:14})function calc(string){return math.format(math.evaluate(string),{precision:14})}calc('.1+.2')calc('.28*100')
发现计算 4.661*1855还是有问题~~ 或者使用原生 `parseFloat((0.1+0.2).toFixed(10));`  ~~ 既然如此,那就 math.js 一把梭
操作符
普通的不提,《也谈js里的位操作符、位运算》单独摘出去了。
Map
如果存储 key/value ,js中使用对象足够。两者大部分功能一致,略有差异。
// 传递可迭代内容var a = new Map([['a',1]]);// key 的部分可以是任意内容,比如对象var a=new Map([[{a:1},1]])
Map特点:
- Key约束不同。object的key智能是 number/string/symbol,而map可以是任意内容
 - 顺序和迭代不同。因为传入的是可迭代的内容,天生就有顺序、可迭代。
 
那怎么选?
- 用哪个都行,影响不大
 - 在乎内存和性能需要注意
 - 内存占用。但同等内存,map比object存得多,说是多50%
 - 插入性能。如果大量操作map性能更好
 - 查找速度。差不多
 - 删除性能。大量删除map性能好
 
WeakMap
弱映射。weak是指js垃圾回收对待key的方式。基础使用和 Map 没区别。
但 key是弱弱的拿着,不是正式引用,不会影响系统垃圾回收,这也意味着没有迭代、没有统计。
const a=new WeakMap()a.set({},'val')
这里的代码,key是空数组,没法再拿到它,代码执行完就GC了,a也就变成了空映射。
另一个显著区别,WeakMap 的 key 只能是对象。这样确保是通过引用来取值。
应用场景,vue3 里在对数据做响应式处理使用用到了 WeakMap,设定双向缓存。
应用场景,深拷贝解决循环应用问题。可以见下面的实战部分。
Set 和 WeakSet
和Map、WeakMap 很像。
Set 一个广泛使用的场景是数组去重。
实战
两者类型在存储上不同
- Primitvie 是在 Stack 内存中,被引用或者拷贝,会创建新的变量,彼此独立
 - Reference 是 Heap 内存中,存储的是引用地址,多个引用指向同一个地址,彼此共享
 
1 面试1
基础面试题,就是 a=b={} a.b=1,b.a会不会变?
会变,引用是引用值
2 面试题2
let a = {name: 'Julia',age: 20}function change(o) {debugger;o.age = 24; // 修改的依然是引用o = {name: 'Kath',age: 30}return o; // return 把o变成了另一个内存地址}let b = change(a);console.log(b.age); // 30 {name: "Kath", age: 30}console.log(a.age); // 24 {name: "Julia", age: 24}
实战:类型检测
如何判断这个变量的类型?
简单说还是 typeof 判断整体类型, instanceof 判断具体类型。
typeof
判断基础类型
typeof Symbol() // symboltypeof 1n // biginttypeof console.log // functiontypeof null // object
规则:
- 返回类型字符串
 - 不能是null,null有问题
 - 如果是可调用对象,有 
[[call]],比如 箭头函数、构造函数、生成器、普通函数会返回function - 其他都是 
object - 如果是未声明变量,返回 
'undefined' 
line5 为啥是object,,因为 null 的二进制表达式 000000,前三位数字是0说明是 object ,且无法被 [[call]] 因此不是 function 。这是一个bug,如果要判断是否为 null,直接三等即可。
规范指路 12.5.6.1
instanceof
new一个对象,得到的新对象就是 原型链 prototype chain 继承的对象,通过 instanceof 能判断是否是被new的构造函数,进而判断出新对象的数据类型
var Car=function(){}var bmw = new Car()let a=new String('2')let b='3'console.log(bmw instanceof Car) // truetypeof a // objecta instanceof String //truetypeof b // stringb instanceof String // false
这里没啥好说的,注意new出来的是 object,对照一下最上面的思维导图就懂了。
实现一个 instanceof 函数?
思路:
- 判断是否是基础类型
 - 如果是引用类型,顺着原型查找
 - 使用 Object.getProrotypOf
 
function instanceof(a,b){// 如果左边是基础类型,就算了if(typeof left !== 'object' || left === null) return false;// 左侧的原型对象let proto = Object.getPrototypeOf(left)while(true){if(proto === null) return false // 说明走到null上了if(proto === right.prototype) return true;proto = Object.getPrototyeOf(proto)}}
Object.prototype.toString
toString 是object的原型方法,返回 [Object Xxx] 的字符串。
对于非object,需要call来调用,为了统一,统一加上 call
Object.prototype.toString.call({}) // "[object Object]"Object.prototype.toString.call(function(){}) // "[object Function]"
做个总结
function getType(obj){let type = typeof objif(type !== 'object') return typereturn Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1')}
数据类型转换
除了显式转换,还有隐式转换这一说法。我觉得有了ts之后,这块知识更多是理论知识,意义不大是知识毒瘤,不值得花费大力气。
但有几个词很有意思:偏字符串、偏数值、无偏好。
显式转换,或者强制类型转换就是用 Number()、parseInt()、parseFloat()、toString()、String()、Boolean()
如果是对象,依次:
[Symbol.toPromitive]方法valueof方法- 如果上一步是NaN,调用 toString 方法
 
隐式
以前整理的,我就不删了。
基本就 + 和 ==
- 两者类型相同,不需要转换
 - 某一个为 null undefined ,另一边也得是同样的,否则false
 - 如果有 Symbol,返回false
 - 如果都是 string 或者 number,会统一转number 
''==0true - 如果有一个是boolean,会转成number 
1==truetrue - 如果一边是object,另一边是 string number symbol,会把object的 valueOf/toSting 法法转换
- 具体用哪个呢?
 - 如果倾向于转换成number,会 valueof 优先
 - 如果倾向于转换成是string,值调用 toString
 
 
[] == 0 // true 对象先 valueof 不行,就toString 变成'' 在转number[] == '' // true
加号 + 特殊
- 左右两侧都是数字,数学运算
 - 两侧都是字符串,直接拼接
 - 字符串和 undefined null boolean ,会调用 toString 进行字符串拼接 
''+undefined - 字符串和 对象、数组、正则等,会调用对象的默认转换方法,再进行拼接 
primitive > valueof>tostring - 数字和 undifined null boolean number,会都变为数字
 - 字符串和数字,字符串拼接
 
| undfined null | boolean | number | string | object | |
|---|---|---|---|---|---|
| undefined null | NaN | ‘’和toString | ‘’和toString | ||
| boolean | NaN | 转数字相加 | |||
| number | Number | Number | 数字相加 | 直接拼接 | |
| string | ‘’和toString | ‘’和toString | 直接拼接 | ‘’和p>v>t | |
| object | ‘’和toString | 
(这里应该整理比较好?)
- 对象作为 操作数 的时候,会优先调用 
valueOf方法{}+10===10, - 其他情况下会优先使用 toString 
10+{} === 1-[object Object] 
object的转换规则
刚才也提到了就是 p>v>t
- [Symbol.toPrimitive] 方法
 - valueOf 方法,如果能转换成基础类型就停止
 - toString 方法,如果能转换成基础类型就停止
 - 如果都没有返回基础类型,会报错
{}+10 // 10let obj = {[Symbol.toPrimitive]() {return 200;},valueOf() {return 300;},toString() {return 'Hello';}}console.log(obj + 200); // 400
实战:深拷贝
见 JS对象 
-1 参考链接
本知识点的梳理,受到了他们的启发:
- 拉钩教育 《JavaScript 核心原理精讲》章节1 章节2
 - 《JavaScript高级程序设计》第4版 章节3.4
 - 小黄书《你不知道的JavaScript》3.3
 - 书籍《JavaScript悟道》
 - 《JavaScript权威指南》第7版
 - 公众号 如何写出一个惊艳面试官的深拷贝
 
