Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JS-对象 #54

Open
yaofly2012 opened this issue Apr 25, 2019 · 6 comments
Open

JS-对象 #54

yaofly2012 opened this issue Apr 25, 2019 · 6 comments

Comments

@yaofly2012
Copy link
Owner

yaofly2012 commented Apr 25, 2019

对象

一、概述

JS是一种面向对象的语言。除了基本数据类型number, string, boolean(true, false), null, undefined,其他的都是对象。对象就是一个"name-value"对集合。

二、操作对象

2.1 创建对象

JS有三种创建对象的方式:

  1. 字面量;
  2. Object.create;
  3. new构造方式。

2.1.1 Object.create(null | object[, properties])

ES5增加的方法,功能是创建一个对象,并且该对象的原型指向create的参数对象。

  1. 参数必须合法的原型(null或者对象),否则报错;
    null也是合法的原型值,表示没有原型对象,比如大名顶顶的Object.protototpe对象就没有原型。
Object.getPrototypeOf(Object.prototype) // null
  1. null表示创建一个没有原型的空对象。
var p = { a: 1}; // 对象字面量
var c1 = Object.create(p); // 对象c1的原型指向p
var c2 = Object.create(null);// 对象c2没有原型
Object.getPrototypeOf(c2) === null;
c2.__proto__ === void 0; // 不是null哦

clipboard.png

用ES3的方式模拟create行为

function inherit(proto) {
    function tempFunc() {}
    tempFunc.prototype = proto;
    return new tempFunc();
}
  1. 没法完全模拟create行为的

2.1.2 对象字面量

对象字面量是一种创建对象的便捷方式,见上例。其中对象p的创建方式就是对象字面量。JS解释器会对它进行处理的,等价于:

var p = Object.create(Object.prototype);
p.a = 1;

所以说对象字面量内部也是通过Object.create方式创建对象的,并且所有对象字面量方式创建的对象的原型都执行Object.prototype(如上图)。

思考:JS解释器如何区分语句块花括号{}和空对象花括号{}的?
先看看这两条语句执行的结果?

// 直接copy到控制台里
{} + [] // 0,相当于 +[]
[] + {} // "[object Object]"
  1. {}作为右值(赋值表达式右侧),实参,在运算符的右侧,被括号()包裹则作为对象,其他则视为语句块:
    下面输出都是:"[object Object]"
    console.log({} + []) // 作为实参了
    
    ({}) + [] // 被括号包裹
    
    var a = {} + [] //作为右值
    console.log(a) 
  2. 我们知道当ES6箭头函数的函数体只有一条语句时可以省略语句块花括号,但是如果箭头函数返回的是一个对象该如何书写呢?
    var func1 = () => {age: 12} // 本意是想返回对象{age:12},显然这样写就不对了,花括号会被作为语句块解析
    var func2 = () => ({age: 12}) // 可以用括号包裹下
    var func3 = () => { return {age: 12}} // 或显示的写全

2.1.3 new构造方式

JS的作者为了讨好类语言的开发者,引入了第三者创建对象方式,即new构造方式。这使得JS对象的创建有点像类语言的对象创建。

function Func(){}
var c1 = new Func();
var c2 = new Func; // 如果没有参数传递,可以省略括号。
function Func(name){
  this.name = name;
}
Func.prototype.say = function(){
};
var c = new Func('q');

clipboard.png

内部原理

这种方式的内部也是通过Object.create方式构建的。new方式创建对象大致分为三步:

  • Step1:创建一个对象A,并且对象A的原型指向构造函数的prototype属性
  • Step2:以对象A绑定到构造函数上调用构造函数
  • Step3:如果构造函数返回值是个非null的对象
    • 则返回构造函数的返回值作为new表达式的值,
    • 否则以对象A作为new表达式的值。
function Func(name){
  this.name = name;
}
Func.prototype.say = function(){
};
function create(){ // 模拟new操作符
  var func = arguments[0];
  var args = Array.prototype.slice.call(arguments, 1);
  var other = Object.create(func.prototype); // Step 1
  var result = func.apply(other, args); // Step 2
  return typeof result === 'object' && result ? result: other; // Step3 注意返回值
}
var c = create(Func, 'q');

2.2 访问对象属性

访问方式也就是get/set/delete。在get访问中会涉及原型链,set/delete访问不会。

var Obj = {
  name: 'john'
};

// Get操作
var n = Obj.name; // 等价var n = Obj["name"];
// Set操作
Obj.age = 12; 

2.2.1 Get操作流程:

clipboard.png

2.2.2 Set操作流程:

clipboard.png

2.2.3 delete操作

  1. 可以通过delete操作符删除对象的属性,只能删除对象本身的属性;
  2. 删除操作只是断开属性和宿主的关系,并不是真的删除属性值。
var p = {
  age: 26
}
var obj = Object.create(p);
obj.name = 'john';
console.log(obj.name); // john
console.log(obj.age); // 26
delete obj.name; // 删除属性
delete obj.age; // 删除属性
console.log(obj.name); // undefined
console.log(obj.age); // undefined

clipboard.png

2.3 检测属性: in & Object.prototype.hasOwnProperty

两者都可以用于检测对象是否含有某个属性。但区别是:

  1. in会查找对象的原型对象(这个跟for-in语句一样),而Object.prototype.hasOwnProperty就像命名一样当然不会。
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Apr 25, 2019

操作对象属性

一、谈谈属性

属性是一个key-value对,key是属性的标识(也叫名字),value是属性的值。

1.1 属性名字:

除了可以字符串命名属性名字,还可以是Symbol类型(ES2015)值表示属性名字。

var a = {
    sex: 'M', // 字符串属性名字
    [Symbol('age')]: 22 // Symbol属性名字,ES6可计算属性名
}
a[Symbol('n')] = 'john'

1.2 属性值:

属性值除了可以用一个具体的JS数据类型值(Data Property)外,还可以用个函数表示(getter/setter属性, Accessor Property),这样可以在get/set时执行一些逻辑。

var count=0;
var obj = {
  log: ['a', 'b', 'c'],
  get latest() {
      console.log(`${++count}`) // 记录get次数
      if (this.log.length == 0) {
        return undefined;
      }
      return this.log[this.log.length - 1];
  },
  set latest(val) {
     this.log.push(val) 
  }
}
console.log(obj.latest)
obj.latest = 'd';
console.log(obj.latest)

1.3 属性的特性

属性除了具有名字和值之外还有一些特性,用于描述属性。

  • 可配置性(configurable)
  • 可枚举性(enumerable)
  • 读写性
    数据类型属性和getter/setter属性使用不同的特性描述读写性。
    image

1. configurable

configurable特性表示对象的属性是否可以被删除,以及除value和writable特性外的其他特性是否可以被修改。

即当configurable=false时:

  1. 属性不能被删除;
  2. 属性的类型不能更改,即数据属性不能改成存取器属性,反之亦然;
  3. 属性的特性(除了value, writable)不能变更;
  4. 数据属性的valuewritable为啥能修改呢?因为数据属性的读写性有专门的特性(即writable)控制。

1,2,3条其实就是属性的配置信息,所以有个configurable特性。

var a = {};
Object.defineProperty(a, 'b', {
  value: 22,
  enumerable: true,
  writable: true
})
console.log(a.b) // 22
console.log(delete a.b); // false
console.log(a.b) // 22

// update value, OK
Object.defineProperty(a, 'b', {
   value: 23,
  enumerable: true,
  writable: true
})

// update writable, OK
Object.defineProperty(a, 'b', {
   writable: false,
  enumerable: true
})

 // update enumerable, TypeError: Cannot redefine property: b
Object.defineProperty(a, 'b', {   
  enumerable: !true,
  writable: true
})

// update type, TypeError: Cannot redefine property: b
Object.defineProperty(a, 'b', {   
  get: () => {}
})
  1. 尝试给特性configurable为false的属性修改除value和writable特性外的其他特性,会抛异常;
  2. 尝试删除特性configurable为false的属性返回false。

2. enumerable

是否可枚举。见for-in,Object.keys方法。

3. 数据类型属性( value,writable)

  1. value表示数据类型属性的具体值;
  2. writable表示数据类型属性值是否可被修改。

4. getter/setter属性(get, set)

  1. get方法表示属性的值;
  2. set方便表示如何修改属性值,没有的化则认为只读的属性。

5. Object.prototype.getOwnPropertyDescriptor(propertyName)

获取对象属性的特性描述对象。

二、属性的添加/编辑

通过这三种方式给一个对象添加属性,编辑已存在的属性。

  1. 赋值运算符
  2. Object​.define​Property()
  3. Object.defineProperties()
var o = {};
o.a = 1;
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : true,
  configurable : true,
  enumerable : true
});

// 另一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同于 :
Object.defineProperty(o, "a", {
  value : 1,
  writable : false,
  configurable : false,
  enumerable : false
});

赋值运算符最宽松,默认的Object.defineProperty最苛刻

  • 执行添加/编辑操作的策略很简单:
    当属性作为左值,或者使用Object​.define​Property(), Object.defineProperties()方法操作属性时,如果对象没有改属性,则执行添加操作,如果有则执行编辑操作。
  • 注意:只读的属性会影响其子对象通过赋值运算符方式添加属性:
    即对于只读属性依旧可以通过Object​.define​Property(), Object.defineProperties()方法修改值。
var a = {};
Object.defineProperty(a, 'age', { // age属性只读
  value: 22,
  writable: false
})

var b = Object.create(a);

// 赋值方式添加属性
b.age = 23; // 添加属性失败,因为原型链对象a.age是只读的
console.log(b); // {}

// defineProperty方式添加属性不受父对象影响
Object.defineProperty(b, 'age', {
  value: 23
})

console.log(b); // {age: 23}

2.1 non-writable属性行为表现

当属性的特性writable为false时,视为该属性为non-writable属性,即只读属性。

  1. 严格模式下对non-writable属性进行赋值操作时会跑异常。
"use strict"
var a= {};
Object.defineProperty(a, "name", {
    value: 12, 
    writable: !true, 
    enumerable: true,
    configurable: true
    })
    
console.log(a)

a.name = 'john'; // TypeError: Cannot assign to read only property 'name' of object
console.log(a)
  1. 可以使用Object​.define​Property(), Object.defineProperties()方法修改non-writable属性的值
"use strict"
var a= {};

Object.defineProperty(a, "name", {
    value: 12, 
    writable: !true, 
    enumerable: true,
    configurable: true
    })
console.log(a)

Object.defineProperty(a, "name", {value: 122,
    writable: !true, 
    enumerable: true,
    configurable: true})
   
console.log(a) // 122

2.2 Object​.define​Property(), Object.defineProperties()方法缺省参数。

  1. configurable, enumerable, writable默认为false;
  2. value,get, set默认为undefined;
  3. 如果value,get, set都不存在,则为value=undefined的数据属性;
  4. 如果value,get, set都存在,则抛异常;
var a = {};
Object.defineProperty(a, 'b', {
  value: 22,
  get: ()=> {}
})

TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute

  1. 编辑操作下,如果没有指定value或则get/set则会沿用原来的值。
    即可以只修改属性的特性。
var a = {}
Object.defineProperty(a, 'b', {   
  get: () => {},
  enumerable: true,
  configurable: true
})
// {get: ƒ, set: undefined, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(a, 'b'))

Object.defineProperty(a, 'b', { 
  enumerable: !true,
  configurable: true
})

// {get: ƒ, set: undefined, enumerable: false, configurable: true}
console.log(Object.getOwnPropertyDescriptor(a, 'b'))

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Apr 25, 2019

遍历对象

直接问如何遍历一个对象都是耍流氓,首先要明确遍历哪些属性?

  1. 继承属性是否要遍历?
  2. 不可枚举的属性是否要遍历?
  3. Symbol属性是否要遍历?

例如express的依赖merge-descriptors源码有这么一段:

Object.getOwnPropertyNames(src).forEach(function forEachOwnPropertyName (name) {
    if (!redefine && hasOwnProperty.call(dest, name)) {
      // Skip descriptor
      return
    }

遍历对象为啥使用Object.getOwnPropertyNames方法呢,不用for-in或者Object.keys?

1. for-in方式

最原始的方式估计就是for-in方式遍历对象了

var hasOwnProperty = Object.prototype.hasOwnProperty;
var a = {
    name: 'john',
    say: function() {
        console.log(a.name)
    }
}

var b = Object.create(a, {
    age: {
        value: 12,
        enumerable: true,
        writable: true,
        configurable: true
    },
    sex: {
        value: 'M',
        enumerable: false,
        writable: true,
        configurable: true
    }
})

for(var key in b) {
    console.log(`${key}-${hasOwnProperty.call(b, key)}`)
}

image

1.2 总结

  1. 遍历对象所有可枚举的属性;
  2. 会遍历原型链上的属性。
    一般配合Object.prototype.hasOwnProperty使用

2. Object.keys方法

Object.keys(b).forEach(key => {
    console.log(key)
})

2.2 总结

  1. 遍历对象本身,可枚举的属性;
    for-in相比,该方式不会遍历对象原型链上的属性。
  2. 如果遍历的目的是获取对象值,可以考虑下这个方法Object.values

3. Object​.get​OwnProperty​Names

Object.getOwnPropertyNames(b).forEach(key => {
    console.log(key)
})

3.2 总结

  1. 返回一个由指定对象的所有自身属性的属性名组成的数组。
  • 包括不可枚举属性
  • 不包括Symbol值作为名称的属性

4. Object.getOwnPropertySymbols

返回一个给定对象自身的所有 Symbol 属性的数组。

b[Symbol('test')] = 'A Symbol value'
console.log(Object.getOwnPropertySymbols(b).join(',')) // Symbol(test)

5. Object.entries

ES2017提供了一个新方法。和Object.keys类似,不过返回的是对象的字符串属性对[key, value]对格式数组。

Object.entries(b).forEach((entry) => {
    console.log(entry[0], entry[1]) // age 12
})

5.2 总结

  1. 返回一个给定对象自身,可枚举属性的键值对数组;
    相当于underscore.js的pairs函数
  2. 不包含Symbol属性。

参考

  1. MDN Object​.keys()
  2. MDN getOwnPropertyNames
  3. MDN entries
  4. MDN getOwnPropertySymbols

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Apr 25, 2019

对象的三个特性

  1. 类型信息
  2. 可扩展性
  3. 原型

一、类型信息

类型信息可以通过Object.prototype.toString间接获取,这也是类型判断的常用手法。

1.1 类型判断

  1. 见JS-类型;
  2. null, undefined比较特殊,旧版本的Object.prototype.toString并不支持,一般判断null, undefined的方法:
function isNull(obj){
    return obj === null;
}

function isUndefined(obj) {
    return obj === void 0;
}

二、可扩展性

标记对象是否可以添加新的属性。

  1. 默认所有内置对象都是可扩展的;
  2. 不可扩展对象无法转成可扩展对象。

2.2 三个级别控制

2.2.1 Object.preventExtensions()方法

  1. 可扩展表示可以新增属性就认为是可扩展的对象。默认情况下对象都是可扩展的。
  2. 使用Object.preventExtensions()方法可以让对象变成不可扩展的,即:
  • 不能添加新的属性;
  • 不能修改其原型属性(因为通过原型可以让对象间接增加新属性)。
"use strict"

var p = {
    name: 'john'
}
var b = Object.create(p);
Object.preventExtensions(b); // 转成不可扩展的对象
try {
    b.age = 22; // 尝试添加新属性 
} catch(e) {
    console.error(e) // 抛异常:TypeError: Cannot add property age, object is not extensible  
}
p.name2 = 12; // 原型可以添加新属性
console.log(p.name2); // 访问原型上的属性
  1. Object​.isExtensible()方法判断对象是否是可扩展的。

2.2.2 封闭对象 & Object​.seal()

  1. 封闭一个对象,阻止添加新属性并将所有现有属性标记为不可配置。
    可以理解为:Object​.seal() = Object.preventExtensions() + 将现有属性转成non-configurable属性。即:封闭的对象不能添加新属性,也不能删除对象(所以称之为封闭的)。
"use strict"

var p = {
    name: 'john'
}
var b = Object.create(p);
b.age = 22;

console.log(Object.getOwnPropertyDescriptors(b)); //{value: 22, writable: true, enumerable: true, configurable: true}
Object.seal(b);
console.log(Object.getOwnPropertyDescriptors(b)); //{value: 22, writable: true, enumerable: true, configurable: false}

p.name2 = '12' // 原型可以添加新属性
console.log(b.name2) // 访问原型上的属性
  1. Object​.isSealed()方法判断一个对象是否是封闭的。

2.2.3 冻结对象 & Object.freeze()

  1. 冻结对象

一个被冻结的对象再也不能被修改;冻结了一个对象则不能向这个对象添加新的属性,不能删除已有属性,不能修改该对象已有属性的可枚举性、可配置性、可写性,以及不能修改已有属性的值。此外,冻结一个对象后该对象的原型也不能被修改。freeze() 返回和传入的参数相同的对象。

简单理解:Object.freeze() = Object.seal() + 将现有属性转成non-configurable & non-writable属性(如果是数据属性) + 不能更换原型对象。

"use strict"

var p = {
    name: 'john'
}
var b = Object.create(p);
b.age = 22;

console.log(Object.getOwnPropertyDescriptors(b)); // {value: 22, writable: true, enumerable: true, configurable: true}
Object.freeze(b);
console.log(Object.getOwnPropertyDescriptors(b)); // {value: 22, writable: false, enumerable: true, configurable: false}
p.name2 = '12' // 原型可以添加新属性
console.log(b.name2) // 访问原型上的属性
  1. 浅冻结
    数据属性如果是个引用类型,则冻结操作不影响属性引用对象的属性的特性。
  2. Object​.isFrozen可以判断对象是否被冻结。

三、原型

3.1 原型

对象继承属性的对象,每个对象内部都有个私有属性([[Prototype]])指向其原型对象,原型对象又有其原型对象,一直到null,这个链构成了原型链

3.2 原型继承

  1. 对象继承对象;
    class继承是类继承类
  2. 动态继承;
    原型新增的属性,所有子对象可以直接访问。

3.2.2 缺点

  1. 实现私有化
    闭包方式

3.3 操作原型

1. 检测

  • instanceof
  • Object.prototype.isPrototypeOf

注意两者的区别。

2. get原型

利用getPrototypeOf实现instanceof

function isInstanceOf(obj, constructor) {
    if(typeof obj !== 'object') {
        return false
    }

    if(typeof constructor !== 'function') {
        throw new TypeError('constructor is not a function')
    }

    if(typeof constructor.prototype !== 'object') {
        throw new TypeError('Function has non-object prototype');
    }

    while(obj !== null) {
        if(obj === constructor.prototype) {
            return true;
        }

        obj = Object.getPrototypeOf(obj);
    }

    return false;
}

这里只实现了instanceof部分功能,即按照原型链查找的判断方式。还没有实现调用Symbol.hasInstance的判断方式。

3. set原型

参考

  1. MDN 原型&原型链
  2. 说说原型(prototype)、原型链和原型继承
    图画的不错
  3. Object.getPrototypeOf

@yaofly2012 yaofly2012 added the JS label May 28, 2019
@yaofly2012 yaofly2012 changed the title Object对象 JS-对象 Feb 9, 2020
@yaofly2012 yaofly2012 mentioned this issue Feb 10, 2020
@yaofly2012
Copy link
Owner Author

yaofly2012 commented Feb 12, 2020

练习

1. 输出以下代码运行结果

// example 1
var a={}, b='123', c=123;
a[b]='b';
a[c]='c';
console.log(a[b]);

// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);

// example 3
var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';
a[c]='c';
console.log(a[b]);

2. a.b.c.d 和 a['b']['c']['d'],哪个性能更高?

性能差异可以忽略。中括号涉及类型转换判断逻辑,.访问是特例情况,从这点考虑点访问更快些。

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 8, 2020

Object & Object.prototype方法

ES3

主要是基本对类型转换API,自身属性判断API(和for-in互补),原型判断API。

  1. Object.prototype.valueOf / Object.prototype.toString
  2. Object.prototype.hasOwnProperty
  3. Object.prototype.isPrototypeOf
  4. Object.prototype.propertyIsEnumerable
    协助for-in

ES5

主要是:

  • 属性特性和对象特性操作API,
  • 属性遍历API,
  • 原型获取API,
  • 更强大对对象创建API(Object.create解决特性和原型一起创建对问题)
  1. Object.defineProperty/Object.defineProperties
  2. Object.getOwnPropertyDescriptor/Object.getOwnPropertyDescriptors
  3. Object. preventExtensions / Object. isExtensible
  4. Object.seal / Object.isSeal
  5. Object.freeze / Object. isFrozen
  6. Object.keys / Object.values / Object.getOwnPropertyNames
  7. Object.getPrototypeOf
  8. Object.create

ESNext

主要是新API,新属性类型(Symbol)及相关操作API

  1. Object.assign
  2. Object.is
  3. Object.setPrototypeOf
  4. Object.entries / Object.fromEntries
  5. Object.getOwnPropertySymbols

@yaofly2012
Copy link
Owner Author

yaofly2012 commented Nov 17, 2020

深度Copy的思考

一、为啥需要Deep copy ?

摘自知乎:JavaScript 如何完整实现深度Clone对象?

  1. 有个对象传给一个函数,不放心它会不会随便修改这个对象,所以要克隆一把
  2. 想修改一个对象但又不想影响原对象
    其中前者是因为没做好后者,如果前者里说的这个函数是你自己写的请好好反省,是第三方库的请试着抛弃掉这个库。

二、Deep copy的难点

首先明确一点:深copy的目的是为了获取引用类型变量的值。
但是Deep Copy不是那么好实现的,因为:

  1. 引用类型变量的值不一定只保持在其成员变量里;
    比如Number, Boolean, String, Date, RegExp,以及ES2015引入的Map/WeakMap, Set/WeakSet等。

  2. 有些数据对象具有临时状态;
    比如 RegExp对象的lastIndex属性。

  3. 遍历JS对象属性的方式也多,每种方式各有差异。

  • Symbol属性
  • 字符串属性
  • 存取器属性
  • 不可枚举的属性是否遍历

三、实现方案

3.1 DeepCopy不是那么容易实现的

针对需要Copy的对象,选择合适的方式。

  1. 如果只是JSON处理的数据,也可以采用JSON.stringify;
  2. 基本类型+plain对象,也可以采用遍历属性方式;

3.2 常见方式:

1. JSON.stringify

局限性很明显,只能用于小范围内的数据深复制。

  • JSON的类型只有6中(nullboolean, number, String, Object, Array),只能用于简单的plain对象;
  • 无法解决循环引用问题。

2. 递归遍历对象属性方式

注意两点:

  1. 数组比较特殊,转化的时候不能修改对象类型;
  2. 利用ArrayWeakSet解决循环引用问题。
    思考:为啥用WeakSet ?
var duplicateObj = [];

function deepCopy(obj) {
  if(Object(obj) !== obj) {
    return obj;
  }

  if(duplicateObj.indexOf(obj) !== -1) {
    return obj;
  }
  duplicateObj.push(obj);
  
  var initialVal = Array.isArray(obj) ? [] : {};
  var result = Object.keys(obj).reduce((result, key) => {    
    result[key] = deepCopy(obj[key]);   
    return result
  }, initialVal)

  return result;
}

// Demo
var child = {
  name: 'child'
}

var parent = {
  name: 'p',
  books: [1, 2, 3, 4],
  child: child
}

child.parent = parent;

var b = deepCopy(parent);
console.log(b === parent); // false
console.log(b.child.parent === parent); // true,

遗留问题:Demo中b.child.parent === parent显然不合理啊。
问题在于检测到已Copy对象时返回的是原对象,应该返回原对象对应的Copy。

function deepCopy(obj) {
  var map = new WeakMap();

  function doCopy(obj) {
    if(Object(obj) !== obj) {
      return obj;
    }
    
    // 已存在,则返回已Copy的对象
    if(map.has(obj)) {
      return map.get(obj);
    }
  
    var result = Array.isArray(obj) ? [] : {};
    map.set(obj, result);
    
    Object.keys(obj).reduce((result, key) => {    
      result[key] = doCopy(obj[key]);   
      return result
    }, result)
    return result
  }
  
  return doCopy(obj);
}

// Demo
var child = {
  name: 'child'
}

var parent = {
  name: 'p',
  books: [1, 2, 3, 4],
  child: child
}

child.parent = parent;

var b = deepCopy(parent);
console.log(b === parent); // false
console.log(b.child.parent === parent); // false
console.log(b.child.parent === b); // true

3.3 利用MessageChannel

管道通讯传递数据采用的是The structured clone algorithm,也可以实现Deep Copy功能。

function deepCopy(data) {
    return new Promise(resolve => {
        var messageChannel = new MessageChannel();
        messageChannel.port1.onmessage = function(e) {
            resolve(e.data);
        }
        messageChannel.port2.postMessage(data);
    })
}
  1. 异步的;
  2. 函数,DOM节点参数Copy会报错
    Things that don't work with structured clone

四、第三方库怎么做的?

  1. underscore没有deppCopy方法
  2. lodash.cloneDeep
  3. npm deepcopy

五、真的需要deep copy ?

知乎讨论:JavaScript 如何完整实现深度Clone对象?
javascripe中深度拷贝使用JSON.stringify和parse好么?

对付 deep clone, 最好的办法是抛弃需要 deep clone 的代码

总结一句话:

没有万能的方式,只有合适的方式。不同的数据存在不同的copy方式,比如DOM元素的copy必须使用clone方法。

参考

  1. Javascript之深拷贝

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant