javascript之Object对象回顾

217 阅读15分钟

Object是在javascript中一个被我们经常使用的类型,js中几乎所有的对象都是继承自Object对象,虽然我们平时只是简单的使用Object对象来存储数据,但事实上,Object对象是javascript中的核心内容。Object对象其实包含了很多有用的属性和方法,深入理解它的属性和方法有利于在工作中更好且更优雅的操作对象。诸如原型/原型链,对象深拷贝/浅拷贝等知识点也是面试过程中常会考察的点。

Object对象的创建

Object对象的创建常见的有两种方式:对象字面量和Object构造函数的方式。

// 1. 对象直面量
var obj = {
    name: 'zhang',
    age:18
}
console.log(obj.constructor === Object); // true

// 2.Object构造函数
var obj1 = new Object();
obj1.name = 'zhang';
obj1.age = 18;
console.log(obj1.constructor === Object); // true

无论是通过对象字面量的方式还是Object构造函数的方式创建的Object对象都具有实例属性constructor,该属性指向创建实例对象的构造函数的引用,可以看到两者的constructor属性都是Object。Object对象实例的另一个属性是__proto __ 属性, __ proto __ 属性是一个访问器属性,通过它可访问对象的内部的[[Prototype]], __ proto __ 属性也可以在对象定义时通过对象[[Prototype]]来创建。

    let Circle = function() {};
    let p = {
        a: function() {
            console.log('aaa');
        }
    };

    // 通过__proto__设置对象的原型链引用
    Circle.prototype.__proto__ = p;
    let shape = new Circle();
    shape.a(); // aaa
    console.log(Circle.prototype === shape.__proto__); // true

Object对象的方法

Object对象的方法分为构造函数方法和实例方法。

构造函数方法

  • Object.assign():将所有可枚举属性的值从一个或多个源对象分配到目标对象,它将返回目标对象。

    语法:Object.assign(target, ...sources)

target是目标对象,sources源对象。返回值为目标对象,如果目标对象中的属性具有相同的键,则属性将被源对象中的属性覆盖,后面的源对象属性将类似地覆盖前面的源对象的属性。并且其只会拷贝源对象自身的并且可枚举的属性到目标对象。

// 1.复制一个对象
const obj = {a: 1};
const copy = Object.assign({}, obj);
console.log(copy); // {a: 1}

// 2.深拷贝问题,如果源对象的属性值是一个对象的引用,它仅仅会复制其引用值
function test() {
    let obj1 = {a: 0, b: {c: 0}};
    let obj2 = Object.assign({}, obj1);
    console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 0}}
    
    obj1.a = 1;
    console.log(JSON.stringify(obj1)); // {a: 1, b: {c: 0}}
    console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 0}}
    
    obj1.b.c = 2;
    console.log(JSON.stringify(obj1)); // {a: 1, b: {c: 2}}
    console.log(JSON.stringify(obj2)); // {a: 0, b: {c: 2}}
    
    // Deep Clone
    obj1 = {a: 0, b: {c: 0}};
    let obj3 = JSON.parse(JSON.stringify(obj1));
    obj1.a = 3;
    obj1.b.c = 3;
    console.log(JSON.stringify(obj3)); // {a: 0, b: {c: 2}}
}
test();

// 3.合并对象,相同属性会被覆盖
const o1 = {a: 1, b: 1, c: 1};
const o2 = {b: 2, c: 2};
const o3 = {c: 3};

const obj = Object.assign({}, o1, o2, o3);
console.log(obj); // {a: 1, b: 2, c: 3}

// 4.继承属性和不可枚举属性不能被拷贝
const obj = Object.create({foo: 1}, { // foo为原型上的属性,属于继承属性
    bar: {
        value: 2 // bar是个不可枚举属性,enumerable默认为false
    },
    baz: {
        value: 3,
        enumerable: true
    }
});

const copy = Object.assign({}, obj);
console.log(copy); // {baz: 3}
  • Object.create():创建一个新对象,使用现有的对象提供新对象的__ proto __ 。

    语法:Object.create(proto, propertiesObject)

proto指定新创建对象的原型对象,propertiesObject为新创建的对象添加指定的属性值和对应的属性描述符。返回值为带有指定的原型对象和属性的新对象。

const o1 = {a:1, say: function(){console.log(this.a)}}

const o2 = Object.create(o1, {
    foo: {
        writeable: true,
        configurable: true,
        value: 'hello'
    },
    bar: {
        configurable: false,
        get: function() {return 10},
        set: function(value) {console.log('Setting o.bar to ', value)}
    }
});

console.log(o2.a);     // 1
console.log(o2.say()); // 1
console.log(o2.bar);   // 10
o2.bar = 20;           // Setting o.bar to 20
  • Object.defineProperties():在一个对象上定义新的属性或修改现有属性,并返回该对象。

    语法:Object.defineProperties(obj, props)

obj为在其上定义或修改属性的对象,props为定义了可枚举属性或修改的属性描述符的对象。描述符有几个键:configurable只有为true时该属性才可以从对应对象修改或删除,enumerable在枚举相应对象上的属性时该属性显现,value与属性关联的值,默认为undefined,writable只有为true该属性相关联的值才能被赋值运算符改变,get作为该属性的getter函数,函数的返回值被用作属性的值,set作为属性的setter函数,函数仅接受参数赋值给该属性的新值。

const obj = {};
Object.defineProperties(obj, {
   'property1': {
       value: true,
       writable: true
   },
    'property2': {
        value: 'Hello',
        writable: false
    }
});

console.log(obj); // {property1: true, property2: 'Hello'}
obj.property1 = false;
obj.property2 = 'World';
console.log(obj); // {property1: false, property2: 'Hello'}
  • Object.defineProperty():直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回该对象。

    语法:Object.defineProperty(obj, prop, desciptor)

obj为要定义属性的对象,prop为要定义或修改的属性的名称或Symbol,descriptor为要定义或修改的属性描述符。

属性描述符有两种:数据描述符和存取描述符。数据描述符是具有值的属性,存取描述符是由getter和setter函数所描述的属性。数据描述符不能拥有get和set键值,存取描述符不能拥有value和writable键值。

// 创建属性
const o = {};

// 添加一个数据描述符属性
Object.defineProperty(o, 'a', {
    value: 37,
    writable: true,
    enumerable: true,
    configurable: true
});

// 添加一个存取描述符属性
var bValue = 38;
Object.defineProperty(o, 'b', {
    get(){return bValue;},
    set(newValue){ bValue = newValue;},
    enumerable: true,
    configurable: true
});

console.log(o); // {a: 37, b:38}

bValue = 39;
console.log(o.b); // 39
  • Object.entries():返回一个给定对象自身可枚举属性的键值对数组。

    语法:Object.entries(obj)

obj为可以返回其可枚举属性的键值对的对象,返回值为给定对象自身可枚举属性的键值对数组。

const obj = {foo: 'bar', baz: 42};
console.log(Object.entries(obj)); // [['foo', 'bar'],['baz', 42]]

// 类数组对象
const obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.entries(obj)); // [['0','a'],['1','b'],['2','c']]

// Object转换为Map
const obj = {0: 'a', 1: 'b', 2: 'c'};
const map = new Map(Object.entries(obj));
console.log(map); // Map {'0': 'a', '1': 'b', '2': 'c'}
  • Object.freeze():冻结一个对象,被冻结的对象不能被修改,添加或删除属性,对象的原型也不能被修改。

    语法:Object.freeze(obj)

obj为要被冻结的对象,返回值为被冻结的对象。

const obj = {
    prop: function(){}, 
    foo: 'bar'
};

Object.freeze(obj);

obj.foo = 'baz';
console.log(obj.foo); // 'bar' 此时修改属性等操作都失效 

// 浅冻结,冻结对象的属性若为对象或数组,仍可添加删除
const obj1 = {
    internal: {},
    arr: []
};

Object.freeze(obj1);
obj1.internal.a = 'aValue';
obj1.arr.push(1);

console.log(obj1); // {internal: {a: 'aValue'}, arr: [1]}
  • Object.fromEntries():将键值对列表转换为一个对象

    语法:Object.fromEntries(iterable)

iterable为类似Array、Map或其他可迭代对象,返回值为新对象。

// Map转化为Object
const map = new Map([['foo', 'bar'],['baz', 42]]);
const obj = Object.fromEntires(map);
console.log(obj); // {foo: 'bar', baz: 42}

// Array转化为Object
const arr = [['0', 'a'], ['1', 'b'], ['2', 'c']];
const obj = Object.fromEntries(arr);
console.log(obj); // {0: 'a', 1: 'b', 2: 'c'}
  • Object.getOwnPropertyDescriptor():返回指定对象上一个指定属性对应的属性描述符

    语法:Object.getOwnPropertyDescriptor(obj, prop)

obj为需要查找的目标对象,prop为目标对象内的属性名称,返回值为指定属性的属性描述符对象,不存在该属性则返回undefined。

const o = {
    get foo(){return 17;}
};
const d = Object.getOwnPropertyDescriptor(o, 'foo');
console.log(d); // {configurable: true, enumerable: true, get: foo(), set: undefined}

const o1 = {
    bar: 42
};
const d1 = Object.getOwnPropertyDescriptor(o1, 'bar');
console.log(d1); // {value: 42, writable: true, enumerable: true, configurable: true}
  • Object.geOwnPropertyNames():返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

    语法:Object.getOwnPropertyName(obj)

obj为一个对象,其自身的可枚举和不可枚举属性的名称被返回。

// 数组
const arr = ['a', 'b', 'c'];
console.log(Object.getOwnPropertyNames(arr).sort()); // ['0','1','2','length']

// 类数组对象
const obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.getOwnPropertyNames(obj).sort()); // ['0', '1', '2']
  • Object.getPrototyopeOf():返回指定对象的原型

    语法:Object.getPrototypeOf(object)

obj为要返回其原型的对象,返回值为给定对象的原型,如果没有则返回null。

const proto = {};
const obj = Object.create(proto);
console.log(Object.getPrototypeOf(obj) === proto); // true
  • Object.is():判断两个值是否为同一个值。

    语法:Object.is(value1, value2)

value1为被比较的第一个值,value2为被比较的第二个值。返回值为一个boolean值,表示两个参数是否是同一个值。

Object.is('foo', 'foo');     // true
Object.is(window, window);   // true
 
Object.is('foo', 'bar');     // false
Object.is([], []);           // false
 
var test = { a: 1 };
Object.is(test, test);       // true
 
Object.is(null, null);       // true
 
// 特例,Object.is不做类型转换
Object.is(+0, -0);            // false
Object.is(-0, -0);           // true
Object.is(NaN, 0/0);         // true
  • Object.keys():返回一个由一个给定对象的自身可枚举属性组成的数组。

    语法:Object.keys(obj)

obj为要返回其枚举自身属性的对象,返回值为所有可枚举属性键值组成的字符串数组。

// 数组
const arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // ['0','1','2']

// 类数组对象
const obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.keys(obj)); // ['0','1','2']
  • Object.seal():封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。

    语法:Object.seal(obj)

obj为将要被密封的对象,返回值为密封的对象。属性不可配置的效果是不能添加或删除属性,不能重新定义为访问器属性,但属性的值仍然可以修改,并且不会影响从原型链上继承的属性。

const obj = {
    prop: function(){},
    foo: 'bar'
};

Object.seal(obj);

obj.foo = 'baz';
console.log(obj); // {foo: "baz", prop: ƒ}

delete obj.foo;
console.log(obj); // {foo: "baz", prop: ƒ}
  • Object.setPrototypeOf():设置一个指定的对象的原型到另一个对象

    语法:Object.setPrototypeOf(obj, prototype)

obj为要设置其原型的对象,prototype为该对象的新原型。

// 向一个原型附加一个链
function Mammal() {
    this.isMammal = 'yes';
}
  • Object.values():返回一个给定对象自身的所有可枚举属性值的数组。

    语法:Object.values(obj)

obj为被返回可枚举属性值的对象,返回值为一个包含对象自身的所有可枚举属性值的数组。

// 数组
const arr = ['a', 'b', 'c'];
console.log(Object.values(arr)); // ['a','b','c']

// 类数组对象
const obj = {0: 'a', 1: 'b', 2: 'c'};
console.log(Object.values(obj)); // ['a','b','c']

// 随机键的类数组对象
const obj = {100: 'a', 2: 'b', 7: 'c'};
console.log(Object.values(obj)); // ['b','c','a']

实例方法

  • hasOwnProperty():返回一个布尔值,指示对象自身属性中是否具有指定的属性。

    语法:obj.hasOwnProperty(prop)

prop为要检测的属性的string形式的名称,或者Symbol,返回值为某个对象是否含有指定的属性的布尔值。

// 1.判断属性是否存在
const o = new Object();
o.hasOwnProperty('prop'); // false
o.prop = 'exists';
o.hasOwnProperty('prop'); // true
delete o.prop;
o.hasOwnProperty('prop'); // false

// 2.区别自身属性和继承属性
const o = new Object();
o.prop = 'exists';
o.hasOwnProperty('prop');           // true
o.hasOwnProperty('toString');       // false
o.hasOwnProperty('hasOwnProperty'); // false
  • isPrototypeOf():测试一个对象是否存在于另一个对象的原型链上。

    语法:prototypeObj.isPrototypeOf(obj)

obj为原型链上搜寻的目标对象,返回值为一个布尔值,表示目标对象是否在另一个对象的原型链上。

function Foo(){};
function Bar(){};
function Baz(){};

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

const baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
  • toString():返回一个表示该对象的字符串

    语法:obj.toString()

// object
const o = {a: 1, b: 2};
console.log(o.toString()); // '[object Object]'

// Array
const arr = [1,2,3];
console.log(arr.toString()); // '1,2,3'
  • valueOf():返回指定对象的原始值

    语法:obj.valueOf()

// Array:返回数组对象本身
var array = ["ABC", true, 12, -5];
console.log(array.valueOf() === array);   // true

// Date:当前时间距1970年1月1日午夜的毫秒数
var date = new Date();
console.log(date.valueOf());   // 1617097249869

// Number:返回数字值
var num =  15;
console.log(num.valueOf());   // 15

// 布尔:返回布尔值true或false
var bool = true;
console.log(bool.valueOf() === bool);   // true

// Function:返回函数本身
function foo(){}
console.log( foo.valueOf() === foo );   // true
var foo2 =  new Function("x", "y", "return x + y;");
console.log( foo2.valueOf() );

// Object:返回对象本身
var obj = {name: "张三", age: 18};
console.log( obj.valueOf() === obj );   // true

// String:返回字符串值
var str = "http://www.xyz.com";
console.log( str.valueOf() === str );   // true

toString()方法和valueOf()方法以及Symbol.toPrimitive方法

  • valueOf()方法和toString()

    js中每个对象都有一个toString()方法和valueOf()方法,其中toString()方法返回一个表示该对象的字符串,valueOf()方法返回该对象的原始值。

    不同类型的对象的valueOf()方法的返回值
    对象返回值
    Array返回数组对象本身
    Boolean布尔值
    Date返回的时间是从1970年1月1日午夜开始计时的毫秒数UTC
    Function函数本身
    Number数字值
    Object对象本身
    String字符串
    Math和Error对象没有valueOf()方法
    /* 数组 */
    var arr = [1,2,3];
    console.log(typeof(arr.toString()));   // string
    console.log(arr.toString());           // '1,2,3'
    console.log(typeof(arr.valueOf()));    // object
    console.log(arr.valueOf());            // [1,2,3]
    
    /* Boolean */
    var bool = true;
    console.log(typeof(bool.toString()));  // string
    console.log(bool.toString());          // 'true'
    console.log(typeof(bool.valueOf()));   // boolean
    console.log(bool.valueOf());           // true
    
    /* Date */
    var date = new Date();
    console.log(typeof(date.toString()));  // string
    console.log(date.toString());          // 'Sat Sep 19 2020 16:36:32 GMT+0800 (中国标准时间)'
    console.log(typeof(date.valueOf()));   // number
    console.log(date.valueOf());           // 1600504592998
    
    /* Function */
    var fun = function(a){console.log(a)}; 
    console.log(typeof(fun.toString()));  // string
    console.log(fun.toString());          // 'function(a){console.log(a)}'
    console.log(typeof(fun.valueOf()));   // function
    console.log(fun.valueOf());           // ƒ (a){console.log(a)}
    
    /* Number */
    var num = 123;
    console.log(typeof(num.toString()));  // string
    console.log(num.toString());          // '123'
    console.log(typeof(num.valueOf()));   // number
    console.log(num.valueOf());           // 123
    
    /* Object */
    var obj = {a:1};
    console.log(typeof(obj.toString()));  // string
    console.log(obj.toString());          // '[object Object]'
    console.log(typeof(obj.valueOf()));   // object
    console.log(obj.valueOf());           // {a: 1}
    
    /* String */
    var str = "hello";
    console.log(typeof(str.toString()));  // string
    console.log(str.toString());          // 'hello'
    console.log(typeof(str.valueOf()));   // string
    console.log(str.valueOf());           // 'hello'
    
  • Symbol.toPrimitive

    对象的Symbol.toPrimitive属性,指向一个方法。该对象被转化为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

    Symbol.toPrimitive被调用时,会接受一个字符串参数hint,表示当前运算的模式,有三种模式:

    • number: 数字模式,该场合需要转成数值
    • string: 字符串模式,该场合需要转成字符串
    • defalut: 默认模式,该场合可以转成数值,也可以转成字符串

    对于大多数标准对象,数字模式有以下优先级排序:

    1. 调用valueOf()方法,如果结果为原始值,则返回。
    2. 否则,调用tosSring()方法,如果结果为原始值,则返回。
    3. 如果再无可选值,则抛出错误。

    同样,对于大多数标准对象,字符串模式有以下优先级排序:

    1. 调用toString()方法,如果结果为原始值,则返回。
    2. 否则,调用valueOf()方法,如果结果为原始值,则返回。
    3. 如果再无可选值,则抛出错误。

    在大多数情况下,标准对象会将默认模式按数字模式处理(除了Date对象,在这种情况下,会将默认模式按字符串模式处理),如果自定义Symbol.toPrimitive方法,则可以覆盖这些默认的强制转换特性。

// 没有Symbol.toPrimitive属性的对象
var obj1 = {};
console.log(+obj1);         // NaN
console.log(`${obj1}`);		// [object Object]
console.log(obj1+"");       // [object Object]

// 有Symbol.toPrimitive属性的对象
let obj2 = {
    [Symbol.toPrimitive](hint) {
        if (hint === 'number') {
            return 10;
        }
        if (hint === 'string') {
            return 'hello';
        }
        return true;
    }
}
console.log(+obj2);          // 10
console.log(`${obj2}`);      // hello
console.log(obj2+"");        // true

// 有Symbol.toPrimitive属性的对象
let obj = {
    [Symbol.toPrimitive](hint) {
        if (hint === 'number') {
            console.log('Number模式');
            return 123;
        }
        if (hint === 'string') {
            console.log('String模式');
            return 'str';
        }
        if (hint === 'default') {
            console.log('Default模式');
            return 'default';
        }
    }
}
console.log(2*obj);           // Number模式 246
console.log(3+obj);           // Default模式 3default
console.log(obj+"");          // Default模式 default
console.log(String(obj));     // String模式 str

由上总结,一般情况下,+连接运算符传入的参数是default,乘法等算数运算符传入的是number,对于String(str),${str}等情况,传入的参数是string。

当然也可以根据不同模式的优先级顺序重写toString和valueOf方法。

let obj = {
    valueOf() {
        return 0;
    },
    toString() {
        return '1';
    },
    [Symbol.toPrimitive]() {
        rteturn 2;
    }
}
console.log(1+obj);      // 3
console.log('1'+obj);    // 12

可以看到,Symbol.toPrimitive方法在转换基本类型的时候优先级最高。

Object.create与new的区别

我们知道Object.create和new都可以创建一个对象,那他们所创建的对象的差别在哪呢?

首先我们知道new创建一个实例对象的步骤是:1.新建一个新对象并将隐式原型指向构造函数的原型;2.将构造函数的作用域赋值给新对象;3.执行构造函数的代码;4.返回新对象。可以看出通过new创建的对象其原型对象为Object.prototype。而Object.create()创建新对象时,指定了新对象的原型为第一个参数。Object.create()用第二个参数来创建非空对象的属性描述默认为false,而构造函数或字面量创建的对象属性描述默认为true。

// js实现一个Object.create
function myCreate(obj, propertiesObject) {
  function F(){}
  F.prototype = obj;
  const newObj = new F();
  if(propertiesObject){
    Object.defineProperties(newObj, propertiesObject);
  }
  return newObj;
}
const hh = myCreate({a: 11}, {mm: {value: 10}});
console.log(hh); 

原型与原型链

// js实现一个Object.create
function myCreate(obj, propertiesObject) {
  function F(){}
  F.prototype = obj;
  const newObj = new F();
  if(propertiesObject){
    Object.defineProperties(newObj, propertiesObject);
  }
  return newObj;
}
const hh = myCreate({a: 11}, {mm: {value: 10}});
console.log(hh); 

原型与原型链

我们知道在js中一切皆对象,而对象又分为普通对象和函数对象,所有的函数对象在创建后都有一个prorotype属性,这个属性指向函数的原型对象,原型对象默认拥有一个constructor属性,指向了它的那个构造函数。普通对象虽然没有prototype属性,但有__ proto __属性,指向了该对象隐藏的属性[[prototype]],即创建该对象的构造函数的原型对象。

function Dog(name, color) {
    this.name = name;
    this.color = color;
}
const dog1 = new Dog('dog1','black');

console.log(Dog.prototype === dog1.__proto__); // true

由上可以知道,所有的构造函数都拥有一个prototype属性,指向其原型对象,而所有的实例对象都具有一个__ proto __ 属性,指向了创建该实例对象的构造函数的原型对象,这样就形成了一个链,所有的对象之间通过__ proto __ 连接起来,也就是原型链。当搜索当前的对象不存在的属性时,会通过原型链层层往上找,直到最上层Object对象。需要注意的是,所有的函数都能通过__ proto __ 找到Function构造函数的原型,所有的对象都能通过__ proto __ 找到Object构造函数的原型,而Object构造函数原型对象的 __ proto __指向null。

function Foo(){}

Foo.prototype.x = 10;
Foo.prototype.calculate = function() { return this.x + this.y;}

const b = new Foo();
b.y = 20;

const c = new Foo();
c.y = 30;

console.log(b.calculate()); // 30
console.log(c.calculate()); // 40

原型链.jpeg

对象的浅拷贝与深拷贝

浅拷贝

浅拷贝是指只复制一层对象,当对象的属性是引用类型时,实质复制的是其引用

function shallowClone(initalObj){
  	var obj={};
  	for(var i in initalObj){
      	obj[i]=initalObj[i];
    }
  	return obj;
}

Es6的方法

let newObj=Object.assign({},obj);

ES6的对象扩展

let newObj={...obj}

深拷贝

将一个对象从内存中拷贝一份,从堆内存中开辟一个新的区域存放新对象,且改变新对象不会影响原对象

基础版
function deepClone(initalObj){
  	if(typeof initalObj ==='object'){
      let newObj={};
      for(var i in initalObj){
        	newObj[i]=deepClone(initalObj[i]);
      }
      return newObj;
    }else{
      	return initalObj;
    }
}
考虑数组
function deepClone(initalObj){
  	if(typeof initalObj ==='object'){
    //let newObj=Object.prototype.toString.call(inital)==='[Object Array]'
      	let newObj=Array.isArray(initalObj)?[]:{};
      	for(var i in initalObj){
          	newObj[i]=deepClone(initalObj[i]);
        }
      	return newObj;
    }else{
      	return initalObj;
    }
}
循环利用

循环利用会因为递归导致陷入死循环,解决循环利用死循环的问题,可以开辟一个新的内存来存储当前对象与拷贝对象的对应关系,当需要拷贝当前对象时,先去存储空间中找,有没有拷贝这个对象,如果有直接返回,如果没有继续拷贝

Map结构的键可以是任意值,包括函数,对象等引用类型。

function deepClone(initalObj,map=new Map()){
  	if(typeof initalObj==='object'){
      	let newObj=Array.isArray(initalObj)?[]:{};
      	if(map.get(initalObj)){
          	return map.get(initalObj);
        }
      	map.set(initalObj,newObj);
      	for(const i in initalObj){
          	newObj[i]=deepClone(initalObj[i]);
        }
      	return newObj;
    }else{
      	return initalObj;
    }
}