数据类型及深浅拷贝

108 阅读5分钟

这是我参与8月更文挑战的第9天,活动详情查看: 8月更文挑战”juejin.cn/post/698796…

数据类型

分为基本类型数据和引用类型数据

基本类型数据

  • String、Number、Boolean、Null、Undefined、Symbol、BigInt
  • 值保存在栈内存中

引用类型数据

  • Object

    • Math、Date、RegExp、Function、Array
  • 值保存在堆内存里,栈内存里保存的是引用地址。

数据类型检测

数据类型的检测方法可分为三种,分别为:

  • typeof
  • insatnceof
  • toString

typeof

  • 基本类型数据除 null 以外,可正确返回数据类型;

    • typeof null "object" 。是js本身设计的一个bug,如果需要判断数据是不是null,可直接 xxx === null 来判断
  • 引用类型数据除 function 以外,其他几种都返回 object;

    • typeof function "function"

insatnceof

此方法源于原型链相关知识。

new 构造函数的时候,新生成的对象就继承了构造函数的属性和方法。在原型链上产生的结果就是新对象的 proto 指向构造函数的prototype,所以可以通过 instanceof 来判断新生成的对象是否是某个构造函数的实例。

image.png

var ObjectFactory = function () {
    // 创建一个对象
    var obj = new Object()
    // 返回第一个参数作为构造函数
    var Constructor = [].shift.call(arguments)
    // 将构造函数的原型复制于对象的原型
    obj.__proto__ = Constructor.prototype
    // 调用构造函数,并将obj 作为this, arguments作为参数
    var ret = Constructor.apply(obj, arguments)
    // 如果构造函数里返回一个对象的话,就直接返回,否则我们就返回this即new创建的对象
    return typeof ret === 'object'? ret: obj
}
// 效果等效
var a = ObjectFactory(Person, 'sven');
  • 基本类型数据都返回 false,结果不够精确
  • 引用类型数据返回true,比较准确

instanceof代码实现

function myInstanceof(left,right){
  // 先用typeof判断是否是基本数据类型,如果是就返回false
  if(typeof left !== "object" || left=== null){return false;}
  // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象
  let proto = Object.getPrototypeOf(left);
  while(true){ 
    // 循环向下寻找,直到找到相同的原型的对象
    if(proto===null){return false;}
    if(proto === right.prototype){return true;}
    proto = Object.getPrototypeOf(proto);
  }
}

toString

Object.prototype.toString()方法是 Object 的原型方法,返回格式为 [Object Xxx] (首字母大写)

  • 返回的数据类型很准确,甚至可以将 window 和 document 区分开
  • 除普通 object 对象外,其他数据类型的值调用是需要加 call 方法
Object.prototype.toString.call(xxx)

全局通用的数据类型判断方法

function getType(obj){
  let type = typeof obj;
  if(type !== "object"){ // 先进行typeof判断,如果是基础数据类型,直接返回
    return type;
  }
  // 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
  return Object.prototype.toString().call(obj).replace(/^[Object (\S+)]$/,'$1');
}

深浅拷贝

针对的都是第一层数据,而不是最外层整体的数据。

浅拷贝

自己创建一个新的对象,来接受你要重新复制或引用的对象值

  • 基本类型数据:直接把值复制一份赋给新的数据
  • 引用类型数据:复制过去的是值的引用地址。所以如果新的数据里修改了引用类型的值,原数据也会变

深拷贝

将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。

  • 基本类型数据:直接把值复制一份赋给新的数据
  • 引用类型数据:直接把堆内存中数据复制一份到新的内存中,所以如果新的数据里修改了引用类型的值,原数据不会变

浅拷贝方法

object.assign()

  • 两个参数:target source(可以是多个) 无返回值
  • 如果拷贝后修改源对象的值,那么在修改之前输出,也是改变后的值

注意点:

  • 不拷贝不可枚举属性
  • 不拷贝继承属性
  • 可拷贝 Symbol 类型的属性值

可以理解为内部使用循环遍历原对象的属性,将原对象的属性赋值给新对象的相应属性。

扩展运算符

直接在新对象中展开数据,即可。

  • 当数据第一层都为基本数据类型时,用扩展运算符比较方便

concat()、slice()

专用于数组的浅拷贝方法

arr.concat();
arr.slice();

手动实现浅拷贝方法

function shallowClone(target){
    if(typeof target === "object" && target !== null){
        let cloneTarget = Array.isArray(target) ? [] : {};
        for(let prop in target){
            if(target.hasOwnProperty(prop)){
                cloneTarget[prop] = target(prop); 
            }
        }
        return cloneTarget;
    } else {
        return target;
    }
}

深拷贝方法

JSON.stringify()

把一个对象序列化成为 JSON 的字符串,并将对象里面的内容转换成字符串,最后再用 JSON.parse() 的方法将JSON 字符串生成一个新的对象

注意点:

  • 如果对象中有函数、undefined、symbol等类型数据,拷贝后键值对会消失
  • 拷贝Date类型数据会变为字符串
  • 拷贝RegExp对象会成为空对象
  • 不可拷贝不可枚举属性
  • 不可拷贝对象原型链
  • 如果对象中有NaN、infinity等数据,拷贝后会成为null
  • 无法拷贝对象的循环引用

手写基础版深拷贝

function deepClone(target){
    let cloneObj = {};
    for(let prop in target){
        if(typeof target[prop] === "object" && target[prop] !== null){
            cloneObj[prop] = deepClone(target[prop]);
        } else {
            cloneObj[prop] = target[prop];
    }
    return cloneObj;
}
  • 仍不能复制不可枚举属性和symbol类型的值
  • 仅针对普通object对象进行深拷贝,对function、date、math等仍无法进行深拷贝
  • 对象的循环引用无法解决

手写最终版深拷贝

// 严格判断数据是否是引用类型数据
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj,hash = new WeakMap()){
    if(obj.constructor === Date) return new Date(obj);  // 日期对象直接返回一个新的日期对象
    if(obj.constructor === RegExp) return new RegExp(obj);  // 正则对象直接返回一个新的正则对象
    
    // 如果循环引用了就用 weakMap 来解决
    if(hash.has(obj)) return hash.get(obj)
    let allDesc = Object.getOwnPropertyDescriptors(obj);
    //遍历传入参数所有键的特性
    let cloneObj = Object.create(Object.getPrototypeOf(obj),allDesc);
    //继承原型链
    hash.set(obj,cloneObj);
    for(let key of Reflect.ownKeys(obj)){
        cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key],hash) : obj[key];
    }
    return cloneObj;
}