美文网首页
[深入09] 深浅拷贝

[深入09] 深浅拷贝

作者: woow_wu7 | 来源:发表于2021-08-02 22:09 被阅读0次

    导航

    [深入01] 执行上下文
    [深入02] 原型链
    [深入03] 继承
    [深入04] 事件循环
    [深入05] 柯里化 偏函数 函数记忆
    [深入06] 隐式转换 和 运算符
    [深入07] 浏览器缓存机制(http缓存机制)
    [深入08] 前端安全
    [深入09] 深浅拷贝
    [深入10] Debounce Throttle
    [深入11] 前端路由
    [深入12] 前端模块化
    [深入13] 观察者模式 发布订阅模式 双向数据绑定
    [深入14] canvas
    [深入15] webSocket
    [深入16] webpack
    [深入17] http 和 https
    [深入18] CSS-interview
    [深入19] 手写Promise
    [深入20] 手写函数

    [react] Hooks

    [部署01] Nginx
    [部署02] Docker 部署vue项目
    [部署03] gitlab-CI

    [源码-webpack01-前置知识] AST抽象语法树
    [源码-webpack02-前置知识] Tapable
    [源码-webpack03] 手写webpack - compiler简单编译流程
    [源码] Redux React-Redux01
    [源码] axios
    [源码] vuex
    [源码-vue01] data响应式 和 初始化渲染

    前置知识

    堆栈

    • stack栈
      • <font color=red>栈区</font> 包含了:<font color=red>变量的标识符</font> 和 <font color=red>变量的值</font>
      • 栈区:
        • 指的是内存中的栈内存
        • 基本类型的数据(值类型数据)保存在栈中
      • 比较
        • <font color=red>基本类型数据的比较是(值)得比较</font>
        • <font color=red>基本类型的数据(不可变),不能添加属性和方法</font>
    • heap堆
      • 堆区
        • 引用类型的数据保存在栈和堆中
        • 栈区保存:<font color=red>变量标识符</font> 和 指向堆内存中对象的 <font color=red>指针</font>
        • 堆区保存:具体的对象数据
      • 比较
        • <font color=red>引用类型数据的比较是(引用)的比较</font>
        • <font color=red>引用类型的数据(可变),可以添加属性和方法</font>
    image image

    数据类型

    • 基本数据类型(值类型):number,string,boolean,null,undefined,symbol
    • 引用类型的数据:object,array,function等
    • 区别:
      • <font color=red>基本类型没有属性和方法,大小固定,保存在栈区</font>
      • <font color=red>引用类型有属性和方法,大小不固定,保存在栈区和堆区,栈中保存指向堆中数据的地址</font>

    数据类型的案例

    引用类型和原始类型的案例
    
    var a = 1 // 基本类型的数据
    var b = {name: 'woow_wu7'} // 引用类型的数据
    var aa = a // a 和 aa 是不同的数据
    var bb = b // b 和 bb 指向堆中的同一份数据,修改堆中数据,b和bb的指向没变,则引用的值也会跟着改变
    a = 2
    b.name = 'wang'
    
    console.log(a, aa, 'a和aa是不同的数据') // 改变后不等
    console.log(b.name, bb.name, 'b和bb两个变量中的指针 => 都同时指向了同一个堆内存中的数据') // 改变后相等
    console.log(b === bb) // true,说明两个变量指向了同一个堆内存
    

    Map数据结构

    • Object对象的key只能是字符串
      • 字符串-值对应
    • Map数据结构的key可以是任意类型
      • 值-值对应
    • <font color=red>Map类似于对象,也是key,value的键值对的集合</font>
    • Map是一种更完善的hash结构实现,如果你需要键值对的数据结构,Map比Object更合适
    • Map.prototype.set(key, value) // key可以是任意类型
    • Map.prototype.get(key)
    • Map.prototype.has(key) // 返回布尔值
    • Map.prototype.delete(key) // 删除某个键,但返回布尔值,表示是否删除成功
    • Map.prototype.clear() // 清除所有成员,没有返回值
    • Map.prototype.keys() values() entries() forEach()
    • <font color=red>Map构造函数可以接受数组为参数,成员必须是一个个表示键值对的数组</font>
    • <font color=red>Map能保证对象key的唯一性</font>
    
    const mapArrKey = [1,2];
    const mapKeyAddress = ['chongqign']
    
    const mapInstance = new Map([
      ['name', 'woow_wu'],
      [[1,2], 20],
      [mapArrKey, 20],
      [{age: 20}, {age: 20}],
    ])
    console.log(mapInstance, 'mapInstance')
    console.log(mapInstance.size, 'size') // 4
    console.log(mapInstance.get(mapArrKey), 'Map.prototype.get(key) => key是一个数组')
    console.log(mapInstance.get([1,2]), 'Map.prototype.get(key)') // undefined 必须是同一个数组
    
    mapInstance.set(mapKeyAddress, '地址')
    console.log(mapInstance.get(mapKeyAddress))
    console.log(mapInstance.has(mapKeyAddress), 'Map.prototype.has(key) => key是否存在,布尔值') // true
    console.log(mapInstance.delete(mapKeyAddress), 'Map.prototype.delete(key) => 删除键,返回布尔值,表示是否删除成功') // true
    console.log(mapInstance.get(mapKeyAddress)) // undefined
    console.log(mapInstance.clear(), 'Map.prototype.clear() => 删除所有键,没有返回值')
    console.log(mapInstance)
    

    Reflect

    • 操作对象的api
    • reflect:反映,反射的意思
    • Reflect.get(target, name, receiver)
      • 获取target对象的name属性,如果没有该属性返回undefined
      • 如果name属性部署了getter函数,getter函数中的this指向receiver参数对象
      • 如果target参数不是对象,Reflect.get()会报错
    • Reflect.set(target, name, value, receiver)
      • 设置target对象的name属性为value
      • 如果name属性部署了settter函数,setter函数中的this指向receiver参数对象
    • Reflect.deleteProperty(obj, name)
      • 删除obj的name属性
      • 相当于: delete obj.name
    • Reflect.constructor(target, args)
      • 执行构造函数target,传入target构造函数的参数是args
      • 如果target不是函数,就会报错
    • Reflect.getPrototypeOf(obj)
      • 相等于:Object.getPrototypeOf(obj)
    • Reflect.setProrotypeOf(obj, prototypeObj)
    • Reflect.apply(func, thisArg, args)
      • 等于:Function.prototype.apply.call(func, thisArg, arges)
    • <font color=red>Reflect.ownKeys(target)</font>
      • 返回对象参数的所有属性,注意:包括Symbol类型的属性
      • 等同于Object.getOwnPropertyNames与Object.getOwnPropertySymbols之和
      • 包括symbol类型的属性!!!

    运算符的结合性

    • <font color=red>一元运算符,三元运算符,赋值运算符是右结合,其他的都是左结合</font>
    • 三元运算符是右结合:即从右向左算,先算右边的三元表达式,再算左边的三元表达式
    三元运算符右结合
    
    let name = null
    
    1 === true ? name = 'wang' : 1 < 0 ? name = 'zhang' : name = 'woow_wu7';
    //相当于:1 === true ? name = 'wang' : (1 < 0 ? name = 'zhang' : name = 'woow_wu7')
    
    console.log(name, 'name')
    // 'woow_wu7'
    // 1 === true // false,类型不一样都是false
    

    typeof返回值

    • typeof可以用来判断基本数据类型,返回值是字符串
    • typeof不能区分对象类型的数据:如对象还是数组
    • <font color=red>typeof 的返回值一共有7种类型</font>
    typeof
    - 返回值有(7种):number,string,boolean,undefined,symbol,function,object
    - 基础数据类型(6种):number,string,boolean,undefined,symbol,null
    
    typeof NaN ---------------------- 'number'
    typeof Symbol() ----------------- 'symbol'
    typeof function(){} ------------- 'function'
    typeof []  ---------------------- 'object'
    typeof {}  ---------------------- 'object'
    typeof null --------------------- 'object'
    

    浅拷贝和深拷贝的区别

    • <font color=red>浅拷贝是只进行一层拷贝,深拷贝是拷贝所有层级,直到属性对应的值是原始数据为止</font>
    • 浅拷贝
      • 创建一个新对象(开创一个新的堆空间),该新对象有着原始对象属性值的精确拷贝
      • 属性值是基本类型,拷贝的就是基本类型的值(即拷贝的是值,互不干扰
      • 属性值是引用类型,拷贝的就是指向堆内存的指针(即拷贝的是指针,相互干扰
    • 深拷贝
      • 在堆内存中创建一个新空间,把对象完成的拷贝到新空间中,相互独立,互不干扰

    浅拷贝和赋值的区别

    之所以不易区分浅拷贝和赋值,是因为拷贝后一般都伴随者赋值

    • 赋值:两个变量对象的指针,指向同一个堆内存中的对象数据,不会开创新的堆空间
    • 浅拷贝:<font color=red>开创一个新的堆内存空间(即创建一个新对象),新对象是对原始对象的一个精确拷贝,属性是基本类型的值拷贝的就是基本类型的值,如果属性是引用类型的值,拷贝的就是堆内存的指针</font>
    • <table><tr><td bgcolor=orange>一句话总结:
      (1)赋值不会开创新的堆内存空间,而浅拷贝会开创新的堆内存空间;
      (2)赋值:改变对象属性相互影响;
      (3)浅拷贝:改变属性值是原始类型时,互不干扰。改变的属性值是引用类型时,相互影响
    • </table></tr></td>

    赋值和浅拷贝的区别实例

    赋值和浅拷贝的区别
    
    var a = {
      name: 'woow_wu',
      score: {
        ch: 90,
        en: 80
      }
    };
    var b = a
    var c = {...a}
    console.log(a===b, '赋值 => 不会开创新的堆空间,修改相互影响') // true,说明是同一份堆数据
    console.log(a===c, '浅拷贝 => 会开创新的堆空间,修改原始值属性互不干扰,修改引用值属性,相互影响') // false,不同堆数据
    a.name = 'wang'
    console.log(b, 'b') // 相互影响
    console.log(c, 'c => 浅拷贝,修改属性值为基本类型 => 互不干扰') // 互不干扰
    a.score.en = 100
    console.log(c, 'c => 浅拷贝,修改属性值为引用类型 => 相互影响') // 相互影响
    

    浅拷贝

    对象的浅拷贝

    • Object.assign()
    • {...} 展开运算符

    数组浅拷贝

    • Array.prototype.slice() // 不传参
    • Array.prototype.concat() // 不传参
    • [...] 展开运算符
    const arr = [1, 2, 3]
    ---
    
    1. slice 
    - slice(start, end)
      - 截取目标数组的一部分,----------------------------------- ( 返回一个新数组,不改变原数组 )
      - start起始位置,从0开始,可以取到
      - end终止位置,注意取不到end // arr.slice(0, 2) // [1, 2]
    - 下面三种写法等价
      - arr.slice() 
      - arr.slice(0)  
      - arr.slice(0, 3) 三者的结果一样
      
    
    2. concat
    - concat
      - 用于多个数组的合并,它将新数组的成员,添加在原属数组的尾部,---- ( 返回一个新数组,不改变原数组 )
      - 参数
        - concat的参数除了是 ( 数组 ) 还可以是 ( 其他任意类型的值 )
    
      
    3. 数组的浅拷贝
    - arr.concat() // 不加参数
    - arr.slice() // 不加参数
    - [...arr]
    

    深拷贝

    方法一 JSON.parse(JSON.stringify())

    • 缺点:
    • 只能深拷贝对象和数组,但不能拷贝函数,循环引用,原型链上的属性和方法(Date, RegExp, Error等)
    const objComplex = {
      name: 'woow_wu7',
      address: {
        city: 'chongqing',
        district: 'yubei',
        town: 'jiazhou',
        detail: ['chongqing', 'yubei', 'jiazhou']
      },
      arr: [1,2, {l:20, r: 30}],
      fn: function(){}, // Function
      date: new Date(), // Date
      err: new Error(), // Error
      reg: new RegExp(), // RegExp
      number: 1,
      string: '',
      boolean: true,
      null: null,
      undefined: undefined,
      symbol: Symbol('symbol'), // Symbol
    }
    const copy = JSON.parse(JSON.stringify(objComplex))
    console.log(objComplex, 'objComplex')
    console.log(copy, 'copy')
    
    如下图:
    JSON.parse(JSON.stringify()) 不能拷贝function,Date,Error,RegExp,等对象
    
    image

    方法二

    基础版 - for...in循环递归(1)

    • <font color=red>要求:可以拷贝对象和数组</font>
    • 未解决:
      • 循环引用
      • Symbol()类型key的属性对应值的拷贝
      • 其他对象的复制如 Date,Error,Regexp,Symbol数据类型等
    const objComplex = {
      name: 'woow_wu7',
      address: {
        city: 'chongqing',
        district: 'yubei',
        town: 'jiazhou',
        detail: ['chongqing', 'yubei', 'jiazhou']
      },
      score: [100, 200]
    }
    
    function deepClone(parameter) {
      const parameterType = Object.prototype.toString.call(parameter).slice(8, -1)
      // 获取类型字符串
      // Array.prototype.slice(8, -1) 从下标为8的字符开始截取,直到倒数第2个值
      // 因为第一个参数位置可以取到,第二个参数位置取不到
      const cloneObj = null
      // 参数是数组,赋值[]
      // 参数是对象,赋值{}
      // 其他类型:直接返回
      if (parameterType === 'Array') {
        cloneObj = []
      }
      else if (parameterType === 'Object') {
        cloneObj = {}
      }
      else {
        return parameter
      }
    
      for(let key in parameter) {
        // for...in 
        // 1. 循环用来遍历对象 ( 所有可遍历的属性 ),会 ( 跳过不可遍历的属性 )
        // 2. 不仅可以遍历 ( 自身属性 ),还可以遍历 ( 继承的属性 )
        // 3. 所以一般情况下,( for...in都要结合hasOwnProperty来遍历自身的属性 )
        
        if (parameter.hasOwnProperty(key)) {
          // 是否是自身属性
          if (typeof parameter[key] === 'object') {
            // 对象或数组,继续判断
            // 这里使用typeof没有去区是分数组或对象,因为会在deepClone中去做判断
            cloneObj[key] = deepClone(parameter[key])
          } else {
            // typeof不是objet
            // 则有可能是:number string boolean undefined symbol function
            // 这里没有考虑 function 的拷贝
            cloneObj[key] = parameter[key]
          }
        }
      }
    
      return cloneObj
    }
    const res = deepClone(objComplex)
    console.log(res, 'res')
    
    ----------
    更精简的写法
    
    const obj = {
      name: 'woow_wu7',
      address: {
        city: 'chongqing',
        districe: 'yubei',
        town: 'jiazhou',
        detail: ['chongqign', 'yubei', 'jiazhou']
      },
      arr: [1,2]
    }
    
    function deepClone(parameter) {
      if (typeof parameter === 'object') {
        // 这里只考虑 对象和数组
        // typeof返回值是字符串,有7种
        // number string boolean undefined symbol function object
        const objClone = Array.isArray(parameter) ? [] : {}
        for (let key in parameter) {
          if (parameter.hasOwnProperty(key)) {
            objClone[key] = deepClone(parameter[key])
            // 不管是对象类型还是基本数据类型,都去调用deepClone(parameter[key])
            // 在deepClone()函数中会去判断对象类型是数组还是对象,基本数据类型直接返回并赋值给objClone[key]
          }
        }
        return objClone
      }
      else {
        // 不是数组和对象直接返回形参
        // 注意形参是新声明的变量
        return parameter
      }
    }
    
    const res = deepClone(obj)
    console.log(obj)
    console.log(res)
    
    

    Map 解决循环引用 - for...in循环递归(2)

    • <font color=red>要求: 可以拷贝对象和数组,并解决循环引用问题</font>
    (1) 什么是循环引用?
    
    const obj = {name: 'wang'}
    obj.circle = obj
    // obj新增circle属性,值是obj对象本身
    // 这样的情况,像上面的代码例子中,for..in循环中deepClone(parameter[key])会不断重复执行
    // 最终造成内存溢出
    
    
    ----------
    (2) 如何解决循环引用?
    1. 检查map实例中是否有克隆过的对象
    2. 如果存在,直接返回
    3. 如果不存在,就赋值键值对,key是传入的对象,value是克隆的对象
    
    var objComplex = {
      address: {
        city: 'chongqing',
        town: 'jiazhou',
      },
      score: [100, 200],
    }
    objComplex.circular = objComplex
    
    function deepClone(objComplex, mapx = new Map()) { // 默认值,是一个空的map实例
    
      if (typeof objComplex !== 'object') {
        // 不是对象和数组直接返回
        return objComplex
      }
      const objClone = Array.isArray(objComplex) ? [] : {}
      
      if (mapx.get(objComplex)) {
        // 存在被克隆的对象,直接返回
        return mapx.get(objComplex)
      }
      // 不存在,就添加键值对,将被克隆对象作为key,克隆的对象作为value
      mapx.set(objComplex, objClone)
     
      for(let key in objComplex) {
       objClone[key] = deepClone(objComplex[key], mapx)
       // 注意:mapx要传入做判断
       // 不管objComplex[key]是什么类型,都调用deepClone(),因为在deepClone()中会判断
      }
      return objClone
    }
    const res = deepClone(objComplex) // 这样就不会内存溢出了
    console.log(res, 'res')
    

    Reflect 解决Symbol数据类型复制 - Reflect.ownKeys()循环递归(3)

    • <font color=red>要求: 可以拷贝对象和数组,并解决循环引用问题,并解决Symbol数据类型</font>
    • 解决 Symbol 数据类型
    • Symbol不能用 new 去调用,参数可以是数组
    • Reflect.ownKeys(obj)返回参数对象的所有自身属性,包括symbol数据类型的属性
      • 参数如果不是obj,则会抛出typeError
      • <table><tr><td bgcolor=orange>缺点:Reflect不能取到原型链上的属性和方法</table></tr></td>
    
    用 Reflect 解决 Symbol类型数据的复制
    
    var objComplex = {
      address: {
        city: 'chongqing',
        town: 'jiazhou',
      },
      score: [100, 200],
    }
    objComplex.circular = objComplex
    objComplex[Symbol()] = 'symbol'
    
    function deepClone(objComplex, mapx = new Map()) {
    
      if (typeof objComplex !== 'object') {
        return objComplex
      }
    
      const objClone = Array.isArray(objComplex) ? [] : {}
      if (mapx.get(objComplex)) {
        return mapx.get(objComplex)
      }
      mapx.set(objComplex, objClone)
      // for(let key in objComplex) {
      //   objClone[key] = deepClone(objComplex[key], mapx)
      // }
      Reflect.ownKeys(Array.isArray(objComplex) ? [...objComplex] : { ...objComplex }).forEach(key => {
        // Reflect.ownKeys(obj)返回对象参数的所有属性,包括symbol类型的属性
        objClone[key] = deepClone(objComplex[key], mapx)
      })
      return objClone
    }
    const res = deepClone(objComplex)
    console.log(res, 'res')
    
    • 2021/06/20 优化
    • 上面的ownKeys参数中是不需要判断是不是数组的,因为ownKeys可以遍历数组和对象
    <script>
          // deep clone
          const obj = {
            name: "woow_wu7",
            address: {
              city: "chongqing",
              districe: "yubei",
              town: "jiazhou",
              detail: ["chongqign", "yubei", "jiazhou"],
            },
            arr: [1, 2],
            [Symbol("unique")]: "unique",
          };
          obj.circle = obj;
    
          const deepClone = (param, map = new Map()) => {
            let typeParams = Object.prototype.toString.call(param);
    
            if (typeof param !== "object" && typeof param !== "function") {
              return param;
            }
    
            if (map.get(param)) {
              return map.get(param);
            }
    
            let resObj = Array.isArray(param) ? [] : {};
    
            map.set(param, resObj);
    
            // for (let key in param) {
            //   resObj[key] = deepClone(param[key], map);
            // }
            Reflect.ownKeys(param).forEach((key) => { // ownKeys参数中,不用做判断是不是数组了
              resObj[key] = deepClone(param[key], map);
            });
    
            return resObj;
          };
    
          const newObj = deepClone(obj);
          newObj.name = "222";
          console.log(`newObj`, newObj);
          console.log(`obj`, obj);
        </script>
    

    结构化克隆算法解决其他对象的拷贝 (4)

    • <font color=red>要求: 可以拷贝对象和数组,并解决循环引用问题(Map缓存),并解决Symbol数据类型(Reflect.ownKeys()),解决其他对象的拷贝(结构化克隆),构造函数生成实例的原型对象的拷贝</font>
    • 比如:Date,Regexp,构造函数生成的实例的原型对象的属性拷贝
    • <table><tr><td bgcolor=orange>缺点:不能处理Error,不能处理Function,不能处理DOM节点</table></tr></td>
    function Message() {
      this.sex = 'man'
    }
    Message.prototype.age = 1000
    
    var objComplex = {
      address: {
        city: 'chongqing',
        town: 'jiazhou',
      },
      score: [100, 200],
      [Symbol()]: 'symbol',
      date: new Date(),
      reg: new RegExp(),
      fn: function () { },
      err: new Error(),
      message: new Message()
    }
    objComplex.circle = objComplex
    
    function deepClone(objComplex, mapx = new Map()) {
    
      if (typeof objComplex !== 'object') {
        return objComplex
      }
    
      let objClone = Array.isArray(objComplex) ? [] : {}
      if (mapx.get(objComplex)) {
        return mapx.get(objComplex)
      }
      mapx.set(objComplex, objClone)
      
      // for(let key in objComplex) {
      //   objClone[key] = deepClone(objComplex[key], mapx)
      // }
      
      // Reflect.ownKeys(Array.isArray(parameter) ? [...parameter] : { ...parameter }).forEach(key => {
      //   objClone[key] = deepClone(parameter[key], mapx)
      // })
      
      switch (objComplex.constructor) { // 传入的参数对象的构造函数
        case Date:
        case RegExp:
        case Message: // 自定义的构造函数
          objClone = new objComplex.constructor(objComplex)
          break
          // 如果是Date,RegExp,Message构造函数的请求
          // 就分别用这些构造函数生成实例,然后再赋值给拷贝对象
        default:
          Reflect.ownKeys(Array.isArray(objComplex) ? [...objComplex] : {...objComplex}).forEach(key => {
            objClone[key] = deepClone(objComplex[key], mapx)
          })
      }
      return objClone
    }
    const res = deepClone(objComplex)
    console.log(res, 'res')
    console.log(res.message.age, '克隆的对象')
    console.log(objComplex.message.age, '原对象')
    

    2021/07/17复习

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <script>
          // deepClone
          // 1. Map 解决循环引用 => 缓存
          // 2. Reflect 解决Symbol()数据类型 => Reflect.ownKeys()
          // 3. 结构化克隆解决特殊对象的拷贝 => Date RegExp
          //  - 虽然结构化克隆解决了 ( Date ) ( RegExp ) 对象的拷贝问题
          //  - 但是结构化克隆仍然没有解决 ( Error ) ( function ) ( DOM节点 ) 的拷贝问题
    
          const obj = {
            num: 1,
            str: "str",
            boo: true,
            und: undefined,
            [Symbol()]: Symbol(),
            arr: [1, 2, 3],
            obj: { name: "woow_wu7" },
            date: new Date(),
            regexp: new RegExp(),
          };
          obj.circle = obj;
    
          const deepClone = (params, map = new Map()) => {
            if (typeof params !== "object") {
              return params;
            }
            let resObj = Array.isArray(params) ? [] : {};
    
            if (map.get(params)) {
              return map.get(params);
            }
    
            map.set(params, resObj);
    
            // for (let key in params) {
            //   if (params.hasOwnProperty(key)) {
            //     resObj[key] = deepClone(params[key], map);
            //   }
            // }
    
            // Reflect.ownKeys(params).forEach(key => {
            //   resObj[key] = deepClone(params[key], map)
            // })
    
            switch (params.constructor) {
              case Date:
              case RegExp:
                resObj = new params.constructor(params);
                break;
              default:
                Reflect.ownKeys(params).forEach((key) => {
                  resObj[key] = deepClone(params[key], map);
                });
                break;
            }
    
            return resObj;
          };
    
          const res = deepClone(obj);
          console.log(`res`, res);
          console.log(`obj`, obj);
        </script>
      </body>
    </html>
    

    基本:https://juejin.im/post/6844904197595332622
    深入:https://juejin.im/post/6844903929705136141
    知乎(lodash deepClone源码分析):https://zhuanlan.zhihu.com/p/41699218
    stack,heap:https://segmentfault.com/a/1190000002789651
    https://juejin.im/post/6844903885472022535#heading-0
    我的简书 https://www.jianshu.com/p/a2306eba08bf

    相关文章

      网友评论

          本文标题:[深入09] 深浅拷贝

          本文链接:https://www.haomeiwen.com/subject/cvzjvltx.html