JS数据类型

JS 的数据类型有哪些

  • 原始类型 / 基本类型:Number、String、Boolean、Null、Undefined、Symbol、BigInt

  • 引用类型 / 对象类型:Object

原始类型 / 基本类型

原始/基本类型的值存储在栈内存 ,没有方法和属性

引用类型/ 对象类型

引用/对象类型的值存储在堆(heap)中。栈中存储的是对象在堆中的引用(指针)。可以添加方法和属性

Symbol 类型

通过 Symbol()生成的 symbol 变量,永远不会重复 。

由于 symbol 是原始类型,所以不能添加方法和属性。

基本包装类型

Number 是一个基本类型,但是它却能调用 toString() 的方法。

let number = 42;
console.log(number.toString()); // 输出:"42"

JavaScript 引擎的工作

  1. 自动创建 Number 类型的一个实例(和基本类型的值不同,这个实例就是一个基本包装类型的对象)

  2. 调用实例(对象)上指定的方法

  3. 销毁这个实例

基本类型的值虽然没有方法可以调用,但是后台临时创建的包装对象上有内置方法可以让我们调用方法,因此这样我们就可以对字符串、数值、布尔值这三种基本数据类型的数据进行更多操作。

由于基本包装类型的对象仅在执行瞬间存在,因此无法向其添加属性。相反,引用类型的对象会持续存在,直到它们不再被引用并被垃圾收集器回收,所以你可以向它们添加属性。

let number = 42;
number.myProperty = "hello";
console.log(number.myProperty); // 输出:undefined

隐式类型转换

转化为原始类型

在 JavaScript 中,当对象需要进行类型转换(例如转换为字符串、数字或布尔值)时,会调用内置的 [[ToPrimitive]]函数。

对象如何转原始类型?

  1. 如果对象拥有[Symbol.toPrimitive]方法,调用该方法。 若该方法能得到原始值,使用该原始值; 若得不到原始值,抛出异常

  2. 调用对象的valueOf方法

    若该方法能得到原始值,使用该原始值; 若得不到原始值,进入下一步

  3. 调用对象的toString方法

    若该方法能得到原始值,使用该原始值; 若得不到原始值,抛出异常

const obj = {
   [Symbol.toPrimitive]() {
    console.log('Symbol.toPrimitive');
    return 1;
  },
  valueOf() {
    console.log("Calling valueOf");
    return 42;
  },
​
  toString() {
    console.log("Calling toString");
    return "I am an object";
  },
};
​
console.log(obj + 1); // 输出 "Symbol.toPrimitive",结果为 2(obj 被转换为数字 1)
console.log("Hello, " + obj); // 输出 "Symbol.toPrimitive",结果为 "Hello, 1"(obj 被转换为字符串 "1")
​
const myObject = {
  [Symbol.toPrimitive]() {
    return {};
  },
  valueOf() {
    return {}
  },
  toString() {
    return "2"
  }
}
console.log(myObject == 2) // true


严格相等(===)、非严格相等( == )

对于 == 来说,如果两个值的类型一样,则直接比较。如果类型不一样,则先进行类型转换,再比较。 === 在比较两个值时都不会进行类型转换。

  1. 宽松相等(==):

    1. 如果两个操作数类型相同,执行严格比较。

    2. 如果两个操作数类型不同,则进行类型转换后再进行比较。规则如下:

      1. 如果一个操作数是数值(number),另一个操作数是字符串,则将字符串转换为数值,然后进行比较。

      2. 如果一个操作数是布尔值,则将布尔值转换为数值,然后进行比较。

      3. 如果一个操作数是对象,另一个操作数是数值或字符串,则将对象转换为原始值,然后进行比较。

  2. 严格相等(===):

    1. 如果两个操作数类型不同,直接返回false

    2. 如果两个操作数类型相同,执行严格比较。(需要注意的是,对于两个对象类型,会比较两个对象的地址是否相同,如果地址不同,则返回 false)

    3. NaN === NaN 返回 false。因为 NaN 是不等于任何值,包括它自身。

    4. 对于+0 和-0,+0 === -0 返回 true,因为严格相等不区分正负。

Object.is() 和 严格相等(===)的区别

  1. Object.is

    1. 当比较 NaN 时,Object.is(NaN,NaN)返回 true。

    2. 当比较+0 和-0 时,Object.is(+0,-0)返回 false,因为它能区分正零和负零(所以我们在日常开发中,如要区分正负,应该用 object.is().

  2. ===

    1. NaN === NaN 返回 false。因为 NaN 是不等于任何值,包括它自身。

    2. 对于+0 和-0,+0 === -0 返回 true,因为严格相等不区分正负。

null 和 undefined 和NaN 的区别

null 表示「无」的对象,转为数值时为0,undefined 表示「无」的原始值,转为数值时为NaN。

如果一个变量将来可能是任何值,此时此刻还没有值,可以赋值 undefined

如果一个变量将来可能是对象,此时此刻还不是对象,可以赋值 null

Number(null) //0
​
Number(undefined) //NaN

项目实践中的区别

null和undefined几乎是同义的

  • null 表示"没有对象",即该处不应该有值。典型用法是:

    • 作为函数的参数,表示该函数的参数不是对象或者没有值。

    • 作为对象原型链的终点。

  • undefined表示“缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

    • 变量被声明了,但没有赋值时,就等于undefined。

    • 调用函数时,应该提供的参数没有提供,该参数等于undefined。

    • 对象没有赋值的属性,该属性的值为undefined。

    • 函数没有返回值时,默认返回undefined。

为什么typeof null 的结果是Object

数据类型机器码标识
对象(Object)000
整数1
浮点数010
字符串100
布尔110
undefined-2^31(即全为1)
null全为0

这是因为在 JavaScript 的底层实现中,它们使用了一种称为“标签”的机制来存储不同类型的值。JavaScript 的不同数据类型都有相应的标签值。在早期的 JavaScript 实现中,对于对象类型,其标签值的二进制表示的低三位都是 0。而 null 值在内存中被表示为全 0,因此其低三位也都是 0。这导致了 typeof 操作符将 null 错误地识别为对象类型。

使用 void 0 替代 undefined

undefined是JavaScript 的一个全局变量,也就是挂载在window对象上的一个变量并不是不是关键字,可以使用undefined作为一个变量名,在非全局环境中可以被重新定义

NaN 以及判断

  • NaN 表示不是一个数字

  • NaN 和任何值相加都是 NaN

  • NaN 与任何值都不相等包括自身

判断

isNaN() 先尝试转换为数字,若无法转换为数字,则返回 true,否则返回 false

Number.isNaN() 直接检查一个值是否是 NaN

面试

等式成立a ==1 && a ==2 & a ==3

从上到下按照规则比较,直到能得到确切结果为止

  1. 两端存在 NaN,返回 false

  2. undefined 和 null只有与自身比较,或者互相比较时,才会返回 true

  3. 两端类型相同,比较值

  4. 两端都是原始类型,转换成数字重新比较

  5. 一端是原始类型,一端是对象类型,把对象转换成原始类型后重新比较

const a = {
  count: 1,
  valueOf() {
    return this.count++
  }
}
if (a == 1 && a == 2 && a == 3) {
  console.log('等式成立')
}

判断数组的方法

  1. Object.prototype.toString.call()对象原型链判断方法

  2. 原型链判断obj.__proto__ === Array.prototype

    Object.getPrototypeOf(obj) === Array.prototype
    obj.__proto__  === Array.prototype
  3. instanceof

  4. 👍 Array.isArray()

总结方法 1.2.3 有缺陷可以被人为修改原型,且方法 3 不适合有iframe的情况

0.1 + 0.2 !== 0.3,请解释其原理

计算机用二进制存储数据。

整数用二进制没有误差,如 9 表示为 1001

而有的小数无法用二进制表示,如 0.2 用二进制表示就是 1.10011001100...

所以,累加小数时会出现误差。

null 是不是对象类型

null 不是对象类型。但是执行以下代码会得到这种结果。

console.log(typeof null); // "object"

在早期的 JavaScript 实现中,对于对象类型,其标签值的二进制表示的低三位都是 0。而 null 值在内存中被表示为全 0,因此其低三位也都是 0。这导致了 typeof 操作符将 null 错误地识别为对象类型。

['1', '2', '3'].map(parseInt)

答案:[1,NaN,NaN]

第一循环

parseInt('1',0) 
// 参数:'1',进制值为默认的十进制(0)
// 返回结果:1

第二次循环

parseInt('2',1) 
// 参数:'2',进制值为 1 
// 无效的1进制 返回 NaN 

第三次循环

parseInt('3',2) 
// 参数:'3',进制值为 2 
// 无效的2进制数字 返回 NaN 

手写深拷贝

注意循环引用 、正则表达式、日期类型、Symbol

  • weakMap

  • 正则表达式、日期类型

  • Symbol属性 和 undefined

基础版本 只考虑数组和普通对象

function deepClone(source) {
  if (typeof source !== 'Object' || source == null) {
    return source
  }
  let target = Array.isArray(source) ? [] : {}
  for (key in source) {
    if (typeof source[key] !== 'Object' || source[key] == null) {
      target[key] = source[key]
    } else {
      target[key] = deepClone(source[key])
    }
  }
  return target
​
}

加强版本

// 利用weakMap 解决循环引用 , weakMap的键必须是对象
function deepClone(source, map = new WeakMap()) {
  if (typeof source !== 'Object' || source == null) {
    return source
  }
  if (map.has(source)) {
    return map.get(source)
  }
  let target = Array.isArray(source) ? [] : {}
  map.set(source, target)
  for (key in source) {
    if (typeof source[key] !== 'Object' || source[key] == null) {
      target[key] = source[key]
    } else {
      target[key] = deepClone(source[key], map)
    }
  }
  return target
}

// 考虑 正则表达式、日期类型、Symbol
function deepClone(source, map = new WeakMap()) {
  if (typeof source !== 'Object' || source == null) {
    return source
  }
  if (map.has(source)) {
    return map.get(source)
  }
  let target;
  if (Array.isArray[source]) {
    target = []
  } else if (source instanceof Date) {
    target = new Date(source)
  } else if (source instanceof RegExp) {
    target = new RegExp(source.source, source.flags)
  } else if (source instanceof Map) {
    target = new Map()
    source.forEach((v, k) => {
      const v1 = deepClone(v, map)
      const k1 = deepClone(k, map)
      target.set(v1, k1)
    })
  } else if (source instanceof Set) {
    target = new Set()
    source.forEach(v => {
      const v1 = deepClone(v, map)
      target.add(deepClone(v1))
    })
  } else {
    target = {}
  }

  map.set(source, target)
  for (key in source) {
    if (typeof source[key] !== 'Object' || source[key] == null) {
      target[key] = source[key]
    } else {
      target[key] = deepClone(source[key], map)
    }
  }
  const symbolKeys = Object.getOwnPropertySymbols(source)
  for (const s in symbolKeys) {
    target[s] = deepClone(source[s], map)
  }
  return target
}

获取数据类型

  • typeof,最常见的判断方法

  • instanceof ,用于已知对象类型

  • Object.prototype.toString.call(),对象原型链判断方法

typeof 的特点

  • 对于基本类型,除 null 以外,均可以返回正确的结果。

  • 对于引用类型,除 function 以外,一律返回 object 类型。

  • 对于 null ,返回 object 类型。

  • 对于 function 返回 function 类型。

console.log(typeof "helloworld")    ------------------>"string"
console.log(typeof 123)             ------------------>"number"
console.log(typeof [1,2,3])         ------------------>"object"
console.log(typeof new Function())  ------------------>"function"
console.log(typeof new Date())      ------------------>"object"
console.log(typeof new RegExp())    ------------------>"object"
console.log(typeof Symbol())        ------------------>"symbol"
console.log(typeof true)            ------------------>"true"
console.log(typeof null)            ------------------>"object"
console.log(typeof undefined)       ------------------>"undefined"
console.log(typeof 'undefined')     ------------------>"string"
​
​
[1,2,3] instanceof Array                -------->true
new Date() instanceof Date              -------->true
new Function() instanceof Function      -------->true
new Function() instanceof function      -------->false
null instanceof Object                  -------->false
​
​
 console.log(Object.prototype.toString.call("123"))           -------->[object String]
 console.log(Object.prototype.toString.call(123))             -------->[object Number]
 console.log(Object.prototype.toString.call(true))            -------->[object Boolean]
 console.log(Object.prototype.toString.call([1, 2, 3]))       -------->[object Array]
 console.log(Object.prototype.toString.call(null))            -------->[object Null]
 console.log(Object.prototype.toString.call(undefined))       -------->[object Undefined]
 console.log(Object.prototype.toString.call({name: 'Hello'})) -------->[object Object]
 console.log(Object.prototype.toString.call(function () {}))  -------->[object Function]
 console.log(Object.prototype.toString.call(new Date()))      -------->[object Date]
 console.log(Object.prototype.toString.call(/\d/))            -------->[object RegExp]
 console.log(Object.prototype.toString.call(Symbol()))        -------->[object Symbol]

  • 30
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值