Table of Contents generated with DocToc (opens new window)

# 数据类型

首先数据分为基本类型数据(String,Number,Boolean,Null)和引用类型数据

  • 基本数据类型的特点: 直接存储在栈(stack)中的数据
  • 引用数据类型的特点: 存储的是该对象在栈中的引用,真实数据存放在堆内存中

引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

# 栈内存和堆内存

JS 中的栈内存和堆内存 (opens new window)

# 栈内存

栈内存用来存放基本数据类型(Number、String、Boolean、Null 和 Undefined),在执行完之后销毁。

==先进后出,后进先出==

# 堆内存

堆内存用来存放引用数据类型(object、array、function、date),在没有被引用之后销毁。

当我们声明和定义了一个引用数据类型之后,这个对象保存在堆内存中,而这个对象的地址则保存在栈内存中以用于引用。

在全局作用域声明和定义的引用数据类型,销毁的方法是手动赋值 null。

# 深拷贝和浅拷贝

源代码 (opens new window)

深拷贝和浅拷贝是只针对 Object 和 Array 这样的引用数据类型的。

  • 浅拷贝:拷贝的是地址,最终两个变量指向同一份数据,修改其中一个变量会改变另一个
  • 深拷贝:两个变量是独立的,互不影响

实现深拷贝

  • 递归实现

    function deepClone(source) {
      // if source not an object
      if (typeof source != 'object') return source
      // if source an array
      const target = myToString(source) === '[object Array]' ? [] : {}
      for (const key in source) {
        // if (source.hasOwnProperty(key)) {
        //不使用source.hasOwnProperty(key)是为了确保能够调用到正确的方法,防止this指向问题
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          // 如果还有子属性
          if (
            myToString(source[key]) === '[object Object]' ||
            myToString(source[key]) === '[object Array]'
          ) {
            target[key] = deepClone(source[key])
          } else {
            target[key] = source[key]
          }
        }
      }
      return target
    }
    function myToString(variable) {
      return Object.prototype.toString.call(variable)
    }
    
    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
  • JSON.stringify()和 JSON.parse()实现

    缺点:RegExp 对象无法拷贝

    /**
     * 深拷贝-json方式
     */
    function deepClone(obj) {
      let tmp = JSON.stringify(obj)
      let result = JSON.parse(tmp)
      return result
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • Object.create()

    Object.create()创建一个新对象,并将新对象的__proto__属性指向原有对象。

    function deepClone(obj) {
      const target = {}
      for (const key in obj) {
        // if (obj.hasOwnProperty(key)) {
        //不使用obj.hasOwnProperty(key)是为了确保能够调用到正确的方法,防止this指向问题
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          // 如果还有子属性
          if (typeof obj[key] === 'object') {
            target[key] = deepClone(obj[key])
          } else {
            target[key] = obj[key]
          }
        }
      }
      return target
    }
    const obj = {
      name: 'zs',
      age: 18,
      children: { name: 'wx', age: 20 },
    }
    const newObj = deepClone(obj)
    
    obj.name = 'ls'
    obj.children.name = 'ss'
    console.log('obj: ', obj)
    console.log('newObj: ', newObj)
    
    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
    26
    27
  • Object.assign(target,...source)

    注意: Object.assign 是第一层深拷贝,第二层如果还是复杂类型(对象)浅拷贝

    const obj1 = {
      name: 'zs',
      age: 18,
      children: { name: 'wx', age: 20 },
    }
    var obj2 = Object.assign({}, obj1)
    obj1.name = 'ls'
    obj1.children.name = 'wx'
    console.log(obj1) //{ name: 'ls', age: 18, children: { name: 'wx', age: 20 } }
    console.log(obj2) //{ name: 'zs', age: 18, children: { name: 'wx', age: 20 } }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

# structuredClone

Window (opens new window) 接口的 structuredClone() 方法使用结构化克隆算法 (opens new window)将给定的值进行深拷贝 (opens new window)。

兼容性:Chrome98

structuredClone不能克隆的格式:

  • Proxy(很常见:你的数据很可能来自 Vue 的 ref/reactive)
  • undefined 的 getter/setter
  • function
  • DOM 节点
  • WebSocket、File 等浏览器对象
  • 循环引用

对于vue中的proxy对象可以使用toRaw先转成原生对象