js 数据类型深入理解

1.undefined类型

判断undefined类型:推荐方法:if(typeof x === ‘undefined’);

var x; //undefined
void 0; //undefined--常用于表示不做任何操作

2.Number类型

Infinity代表无穷大,加上负号,代表无穷小
使用场景:可表示最高优先级或最大权重
精度问题:原因是计算时js引擎会先转为二进制,加法运算后,再转回十进制,对于小数位易出现误差

['0','1','2'].map(parseInt)   //[0,NaN,NaN]--map回调方法中直接使用parseInt会把数组元素和索引都作为参数传入

3.类型转换

js作为弱类型的语言,相对于其他高级语言的其中一个特点就是:在处理不同数据类型运算或逻辑操作时,会强制转换成同一数据类型
操作同种数据类型,也会发生转换:
“装箱转换”:把基本类型的数据转换成对应的对象的过程
“拆箱转换:把数据对象转换成基本数据类型的过程
在这里插入图片描述

对于装箱和拆箱转换操作,我们既可以显示地手动实现,比如将 Number 数据类型转换成 Number 对象;也可以通过一些操作触发浏览器显式地自动转换,比如将对 Number 对象进行加法运算。

var n = 1
var o = new Number(n) // 显式装箱
o.valueOf() // 显式拆箱
n.toPrecision(3) 
// 隐式装箱, 实际操作:var tmp = new Number(n);tmp.toPrecision(3);tmp = null;
o + 2 
// 隐式拆箱,实际操作:var tmp = o.valueOf();tmp + 2;tmp = null;
什么时候会触发类型转换?

下面这些常见的操作会触发隐式地类型转换,我们在编写代码的时候一定要注意。

  • 运算相关的操作符包括 +、-、+=、++、* 、/、%、<<、& 等。
  • 数据比较相关的操作符包括 >、<、== 、<=、>=、===。
  • 逻辑判断相关的操作符包括 &&、!、||、三目运算符。

Object

相对于基础类型,引用类型 Object 则复杂很多。简单地说,Object 类型数据就是键值对的集合,键是一个字符串(或者 Symbol) ,值可以是任意类型的值; 复杂地说,Object 又包括很多子类型,比如 Date、Array、Set、RegExp。

对于 Object 类型,我们重点理解一种常见的操作,即深拷贝

由于引用类型在赋值时只传递指针,这种拷贝方式称为浅拷贝。
而创建一个新的与之相同的引用类型数据的过程称之为深拷贝。

现在我们来实现一个拷贝函数,支持上面 7 种类型的数据拷贝。

对于 6 种基础类型,我们只需简单的赋值即可,而 Object 类型变量需要特殊操作。因为通过等号“=”赋值只是浅拷贝,要实现真正的拷贝操作则需要通过遍历键来赋值对应的值,这个过程中如果遇到 Object 类型还需要再次进行遍历。

为了准确判断每种数据类型,我们可以先通过 typeof 来查看每种数据类型的描述:

[undefined, null, true, '', 0, Symbol(), {}].map(it => typeof it)
// ["undefined", "object", "boolean", "string", "number", "symbol", "object"]

发现 null 有些特殊,返回结果和 Object 类型一样都为"object",所以需要再次进行判断。按照上面分析的结论,我们可以写出下面的函数:

function clone(data) {
  let result = {}
  const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
  if(!keys.length) return data
  keys.forEach(key => {
    let item = data[key]
    if (typeof item === 'object' && item) {
      result[key] = clone(item)
    } else {
      result[key] = item
    }
  })
  return result
}

在遍历 Object 类型数据时,我们需要把 Symbol 数据类型也考虑进来,所以不能通过 Object.keys 获取键名或 for…in 方式遍历,而是通过 getOwnPropertyNames 和 getOwnPropertySymbols 函数将键名组合成数组,然后进行遍历。对于键数组长度为 0 的非 Object 类型的数据可直接返回,然后再遍历递归,最终实现拷贝。

我们在编写递归函数的时候需要特别注意的是,递归调用的终止条件,避免无限递归。那在这个 clone 函数中有没有可能出现无限递归调用呢?

答案是有的。那就是当对象数据嵌套的时候,比如像下面这种情况,对象 a 的键 b 指向对象 b,对象 b 的键 a 指向对象 a,那么执行 clone 函数就会出现死循环,从而耗尽内存。

var a = {
var b = {}
a.b = b
b.a = a

怎么避免这种情况呢?一种简单的方式就是把已添加的对象记录下来,这样下次碰到相同的对象引用时,直接指向记录中的对象即可。要实现这个记录功能,我们可以借助 ES6 推出的WeakMap 对象,该对象是一组键/值对的集合,其中的键是弱引用的。其键必须是对象,而值可以是任意的。

我们对 clone 函数改造一下,添加一个 WeakMap 来记录已经拷贝过的对象,如果当前对象已经被拷贝过,那么直接从 WeakMap 中取出,否则重新创建一个对象并加入 WeakMap 中。具体代码如下:

function clone(obj) {
  let map = new WeakMap()
  function deep(data) {
    let result = {}
    const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
    if(!keys.length) return data
    const exist = map.get(data)
    if (exist) return exist
    map.set(data, result)
    keys.forEach(key => {
      let item = data[key]
      if (typeof item === 'object' && item) {
        result[key] = deep(item)
      } else {
        result[key] = item
      }
    })
    return result
  }
  return deep(obj)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值