手撕面试常客之JavaScript的深浅拷贝(处理多种特殊值),看这一篇就够了。

102 阅读1分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

手撕面试常客之JavaScript的深浅拷贝(处理多种特殊值),看这一篇就够了。

    1. 需要深拷贝的对象,包括日期对象、正则对象、函数等一些特殊的对象。
let objClone = {
  name: 'beijing',
  age: 77,
  open: true,
  0: null,
  1: undefined,
  2: Symbol(),
  arr: ['foo', 'bar'],
  detail: {
      address: '北京',
      phone: '1234567890'
  },
  3: /^\d+$/,
  4: new Date(),
  5: new Error(),
  6: 10n,
  fn: function () {
      console.log('哈哈');
  },
  body:document.body
};
    1. 这个深浅拷贝方法还用到了几个了工具类的方法我把它提取出来了。
    • 2.1 检测数据类型的方法
        const checkType = function checkType(obj) {
            if (obj == null) return obj + ''
            let reg = /^\[object ([a-zA-Z0-9]+)\]$/i,
                type = typeof obj,
                isObjorFunc = /^(object|function)$/i.test(type)
            type = isObjorFunc? reg.exec(Object.prototype.toString.call(obj))[1].toLowerCase() : type
            return type
        }
    
    • 2.2 检测是否是纯粹对象(直属类是Object或者由Object.create(null)创建的对象)该方法借鉴于JQuery源码中的工具方法,对其进行了稍加改动。
        const isPlainObject = function isPlainObject(target){
            let proto,//存放target的原型
                Ctor //存放target的构造函数
            if (!target || Object.prototype.toString.call(target) !== '[object Object]')  return false
            proto = Object.getPrototypeOf(target)
            if (!proto) return true
            Ctor = Object.prototype.hasOwnProperty.call(proto, "constructor") && proto.constructor
            let ObjectFunctionString = Function.prototype.toString.call(Object); //"function Object() { [native code] }"
            return typeof Ctor === "function" && Function.prototype.toString.call(Ctor) === ObjectFunctionString
         }
    
    • 2.3 检测是否是window对象,此方法借鉴于JQuery源码中的工具方法。
        const isWindow = function isWindow(obj) {
            return obj != null && obj === obj.window //window对象 有个特殊的地方 window.window等于它自己,大家可以在浏览器试一下
        }
    
    • 2.4 检测是否是类数组或者数组,此方法借鉴于JQuery源码中的工具方法。
        const isArrayLike = function isArrayLike(obj){
        if (obj == null) return false
        if(typeof obj !== 'object') return false
        let length = !!obj && "length" in obj && obj.length,
            type = checkType(obj)
        if (isWindow(obj)) return false //window对象上是有length属性的 记录iframe标签的个数
        return type === 'array' || length === 0 || typeof length === "number" && length > 0 && (length - 1) in obj
        }
    
    • 2.5 // 迭代数组/类数组/对象,此方法借鉴于JQuery源码中的工具方法。
        const each = function each(target, callback) {
            if(typeof callback !== 'function') callback = Function.prototype
            let i = 0,//记录循环的次数
                len,//数组的长度
                item,//target的每一项
                keys,//如果target是对象 存放 对象key的列表
                key;//如果target是对象 对象的某一个key
            if (isArrayLike(target)) {
              len = target.length
              for(; i < len; i++){
                item = target[i]
                if (callback.call(target,item, i) === false) break //回调函数返回false 支持停止迭代
              }
            } else {
              keys = Object.keys(target)
              if (typeof Symbol !== 'undefined') keys = keys.concat(Object.getOwnPropertySymbols(target))
              for(; i<keys.length; i++){
                key = keys[i]
                item = target[key]
                if ( callback.call(target, item, key) === false) break
              } 
            }
          }
    
    1. 工具方法准备完毕,接下来完成深浅拷贝方法,该方法有两个参数。
    • 第一个参数deep为布尔值,true表示深拷贝,false表示浅拷贝,默认为false。
    • 第二个参数为target,表示要克隆的目标。
        const clone = function clone() {
            let target = arguments[0],
                deep = false, //深浅拷贝
                type, // target 的类型
                isArray, // target 是否是数组或者类数组
                isObject, // target 是否是普通对象
                ctor,// 存放 target的构造函数
                result, //假如 target是数组或者对象,用来存放深拷贝后的值
                treated = arguments[arguments.length-1] //为了防止死递归,存放已经处理过的对象
            // 判断第一个参数是否为 boolean
            if (typeof target === 'boolean') {
              if (arguments.length === 1) return target;
              deep = target
              target = arguments[1]
            }
    
            // 为了防止死递归,循环引用的情况
            if (!Array.isArray(treated) || !treated.treated) {
              treated = []
              treated.treated = true
            }
            if(treated.includes(target)) return target
            treated.push(target)
    
            // 特殊值的拷贝
            type = checkType(target)
            isArray = isArrayLike(target)
            isObject = isPlainObject(target)
            if(target == null) return target
            ctor = target.constructor
            // 特殊值拷贝
            if (/^(regexp|date|error)$/i.test(type)) {
              if (type === 'error') target = target.message
              return new ctor(target)
            }
            if (/^(function|generatorfunction)$/i.test(type)) {
              return function proxy(...params){
                return target.call(this, ...params)
              }
            }
            if (target instanceof HTMLElement) {
              return document.createElement(target.localName)
            }
            // 如果不是数组 也不是对象 返回 自己
            if (!isArray && !isObject)  return target 
    
            // 数组和对象的拷贝
            result = isArray ? [] : {}
            each(target, function(val, key){
              if (deep) {
                // 深拷贝
                // 深拷贝的时候把treated数组传进去,记录已经处理过的对象,防止循环引用,出现死递归的情况
                result[key] = clone(deep,val, treated)
                return
              }
              //浅拷贝
              result[key] = val
            })
            return result
          }
    
          let newObj = clone(true,objClone)
          console.log(newObj);
    
          console.log(newObj.arr === objClone.arr);
          console.log(newObj.detail === objClone.detail);
          console.log(newObj[4] === objClone[4]);
          console.log(newObj[3] === objClone[3]);
          console.log(newObj[5] === objClone[5]);
          console.log(newObj.body === objClone.body);
          console.log(newObj.fn === objClone.fn);
    
    • 浏览器打印情况
    js-deep.png

结束语

JQuery虽然已经过时了,但是作为每个前端人都知道的一个类库,其源码必定是经过千锤百炼的,其中一些优秀的思想也是值得大家学习的,本文也借鉴了一些JQuery的方法,贴出来供大家学习。