数据类型

ECMAScript数据类型分为俩大类:

值类型:StringNumberBooleanUndefinedNullSymbol以及Bigint

引用类型:ArrayObjectFunctionSetWeakSetMapWeakMap

TIP

值类型的值直接存储在栈内、引用类型的值存储在堆中,并有相对应的引用地址存储在栈内。

故变量赋值时,引用类型变量赋值的是引用地址,指向的是存储在堆中的同一份数据,需要深拷贝。

深拷贝

// 深拷贝方式:

// 无法拷贝内置方法、函数和循环引用
JSON.parse(JSON.stringify(data))

// 浅层深拷贝
Object.assign({}, data)
{...data}
[...data]

/**
 * 以上方式都不是最优解,但是项目中使用最多的是JSON.parse(JSON.stringify())
 * 只需确认是否含有函数和循环引用即可
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14

手写深拷贝

// 深拷贝 需考虑内置对象、循环引用等问题
function deepClone(data, map = new WeakMap()) {
  // 循环引用直接返回
  if (map.has(data)) return data

  // 值类型 和 null 直接返回
  if (typeof data !== 'object' || data === null) return data

  // 记录 已设置的引用类型 避免循环引用
  map.set(data, true)

  // 内置对象 返回其实例化
  if (/Date|Math|RegExp/.test(data.constructor.name)) {
    return new data.constructor(data)
  }

  const result = Array.isArray(data) ? [] : {}

  // 遍历递归
  for (const key in data) {
    result[key] = deepClone(data[key], map)
  }

  return result
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

思考

以下p1 p2分别输出什么

function test(person) {
  person.age = 26
  person = {
    name: 'hzj',
    age: 18
  }
  return person
}
const p1 = {
  name: 'fyq',
  age: 19
}
const p2 = test(p1)
console.log(p1)
console.log(p2)















 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
答案解析
// p1 { name: 'fyq', age: 26 }
// p2 { name: 'hzj', age: 18 }
1
2
  • 函数参数传递->引用地址传递,当person.age = 26,修改了p1引用地址引用的值
  • person = {name: 'hzj', age: 18}, 直接用新的引用地址替换了函数参数引用地址 返回的是个新的引* 用地址 即p2

判断数据类型

判断数据类型方法有:typeofinstanceofObject.prototype.toString.call()

  1. typeof
typeof 可以判断值类型中除Null以外的所有类型,引用类型中Function,其它无法判断

typeof null === 'object'? 为什么?
因为 JavaScript中不同对象在底层都表示成二进制,而typeof将前三位为零的都判断为object
null的二进制全是0,所以返回 object
1
2
3
4
5
  1. instanceof

instanceof是根据原型链判断,如果是在同一原型链上则相等

function myInstanceof(left, right) {
  if (
   (typeof right !== 'object'
   && typeof right !== 'function')
   || right === null) {
     throw new TypeError("Right-hand side of 'instanceof' is not an object")
   }
  // 获取right的原型
  const rP = right.prototype
  // 获取left的隐式原型
  let lP = left.__proto__
  while (true) {
    if (lP === null) return false
    if (lP === rP) return true
    lP = lP.__proto__
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  1. Object.prototype.toString.call()

TIP

此方法是将值 调用Object方法 转字符串来判断

examples:

Object.prototype.toString.call({}) === '[object Object]'

Object.prototype.toString.call([]) === '[object Array]'

Object.prototype.toString.call(1) === '[object Number]'

相等运算符

ECMAScript中判断相等有以下三种:=====Object.is()

注意

== 不完全相等,会先进行隐式转换再判断是否相等,不校验数据类型

1 == '1' || false == 0 || null == void 0 等等

=== 完全相等,校验数据类型

但 NaN === NaN 和 -0 === +0 判断不严格

Object.is 修复了以上俩种情况

Object.is(NaN, NaN) // true

Object.is(-0, +0) // false

题目

  • 0.1 + 0.2 === 0.3 ?
答案

false

0.1 和0.2在转化二进制的时候丢失精度 , 故 在转换回来的时候 不为 0.3

  • 对象转原始类型是根据什么流程运行的?
答案

调用Symbol.toPrimitive()方法,优先调用再返回

调用valueOf(),如果转换为原始类型,则返回

调用toString(),如果转换为原始类型,则返回

如果都没有返回原始类型,会报错

const obj = {
  value: 3,
  valueOf() {
    return 4;
  },
  toString() {
    return '5'
  },
  [Symbol.toPrimitive]() {
    return 6
  }
}
console.log(obj + 1); // 输出7
1
2
3
4
5
6
7
8
9
10
11
12
13
// 定义一个变量 a, 让a == 1 && a == 2 条件成立
const a = {
  value: 1,
  valueOf() {
    return this.value++
  }
}
1
2
3
4
5
6
7
上次更新: 2021/10/20 下午4:40:42
贡献者: 陈书进