深拷贝和数据类型判断

69 阅读4分钟

基本类型和引用类型

除了基本数据类型 StringNumberBooleannullundefinedSymbol,所有类型都是引用数据类型; 引用数据类型存储在堆内存中,在栈中存储了指针;引用类型复制时只是的一个地址指针而已,实际指向的是同一个对象。

判断类型方式

类型判断typeofinstanceofconstructortoSting.call
numnumberfalsetrue[object Number]
strstgringfalsetrue[object String]
boolbooleanfalsetrue[object Boolean]
arrobjecttruetrue[object Array]
jsonobjecttruetrue[object Object]
funcfunctiontruetrue[object Function]
undundefinedfalse-[object Undefined]
nullobjectfalse-[object Null]
dateobjecttruetrue[object Date]
regobjecttruetrue[object RegExp]
errobjecttruetrue[object Error]
优点使用简单,能直接输出结果能检测出复杂的类型基本能检测出所有的类型检测出所有的类型
缺点检测出的类型太少基本类型检测不出,不能跨iframe不能跨iframe,constructor易被修改IE6下的undefined,null均为Object
1、typeof

typeof是一个操作符而不是函数,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示 包括以下 8 种:string、number、boolean、undefined、function 、symbol、bigInt、object。

2、instanceof

instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 需特别注意:instanceof 检测的是原型

即instanceof 用来比较一个对象是否为某一个构造函数的实例。instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型

3、constructor

JavaScript中,每个对象都有一个constructor属性,可以得知某个实例对象,到底是哪一个构造函数产生的, constructor属性表示原型对象与构造函数之间的关联关系。

  • 当一个函数F被定义时,JS引擎会为F添加prototype原型,然后在prototype上添加一个constructor属性,并让其指向F的引用,F利用原型对象的constructor属性引用了自身,当F作为构造函数创建对象时,原型上的constructor属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型。

  • 通过typeof运算符来判断它是原始的值还是对象。如果是对象,就可以使用constructor属性来判断其类型。

  • 如判断数组的函数:

function isArray(data){ 
  return typeof data == "object" && data.constructor == Array; 
}
isArray([])  // true

注意:null 和 undefined 是没有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。

4、Object.prototype.toString.call()

Object.prototype.toString(o)是 Object 的原型方法。

  1. 获取对象o的class属性。这是一个内部属性,
  2. 连接字符串:[object + 结果(1)],格式为 [object Xxx] ,其中 Xxx 就是对象的类型。

深拷贝例子

let obj1 = {
      // 基本数据类型可以不做处理, typeof !== 'object'
      num: 12,
      str: "xxx",
      bol: false,
      fn: function () {
        console.log(1111);
      }, // 函数的处理一般只是功能的复用
      // 也不需要处理
      nulVal: null,
      // 无元素,可以直接通过构造函数new 出来的新数据
      reg: /7/,
      date: new Date(),
      // 新的数据结构
      map: new Map([
        ["d", 4],
        ["5", 5],
      ]),
      set: new Set(["s", "g"]),
      array: [3, 3],
      arrayOfObj: [{ a: 3 }, { a: 7 }],
      obj: { a: 4 },
    };

方式一 1.耗性能 2. js关键字 function ... 等不能复制

let objCl = JSON.parse(JSON.stringify(obj1));
console.log(objCl, obj1);

在这里插入图片描述

方式二

function deepClone2(source) {
      const targetObj = source.constructor === Array ? [] : {};
      for (const keys in source) {
        if (Object.hasOwnProperty.call(source, keys)) {
          const element = source[keys];
          if (element && typeof element === "object") {
            targetObj[keys] = deepClone2(element);
          } else {
            targetObj[keys] = element;
          }
        }
      }
      return targetObj;
    }
    let clone2 = deepClone2(obj1);
    console.log(clone2, obj1);

在这里插入图片描述

方式三 Map 会增加引用计数,引用计数不为0 会导致垃圾回收机制无法回收变量的空间 WeakMap 不会额外引用计数

    let cacheMap = new WeakMap();
    function deepClone(obj) {
      // [1. 不处理]
      if (typeof obj !== "object" || !obj) return obj;
      // 判断是否已经处理过
      if (cacheMap.has(obj)) return cacheMap.get(obj);
      let constructor = obj.constructor;
      let tmp, params;
      // [2 无元素]
      if (obj instanceof RegExp || obj instanceof Date) {
        params = obj;
      }
      tmp = new constructor(params);
      cacheMap.set(obj, tmp);
      // 新结构
      if (obj instanceof Map) {
        for (let [key, value] of obj) {
          tmp.set(deepClone(key), deepClone(value));
        }
      } else if (obj instanceof Set) {
        for (let value of obj) {
          tmp.add(deepClone(value));
        }
      } else {
        for (let key in obj) {
          if (Object.hasOwnProperty.call(obj, key)) {
            tmp[key] = deepClone(obj[key]);
          }
        }
      }
      return tmp;
    }
    // 业务需求:环形对象
    let obj2 = {
      to: obj1,
    };
    obj1.to = obj2;
    let cloned = deepClone(obj1);
    console.log(cloned, obj1);

在这里插入图片描述