Wissen Sie nicht, wie man dunkle und helle Kopien handschriftlich schreibt? (Spätestens im Jahr 2023)

Vorwort

Bei der JavaScript-Programmierung müssen wir häufig Daten kopieren. Wann sollte man also eine tiefe Kopie und wann eine flache Kopie verwenden? Darüber sollte man nachdenken.


Flache Kopie


Eine flache Kopie ist eine Kopie der Struktur der obersten Ebene eines Objekts. Sie erstellt ein neues Objekt, aber dieses neue Objekt kopiert nur die Heap-Speicherreferenz des ursprünglichen Objekts, nicht das Objekt selbst. Eine flache Kopie führt dazu, dass das neue Objekt und das Originalobjekt denselben Heap-Speicher gemeinsam nutzen. Das heißt, wenn sich der Attributwert des ursprünglichen Objekts ändert, ändert sich auch der Attributwert des neuen Objekts und umgekehrt.

Objekt.zuordnen 

object.assign ist eine Objektmethode in ES6. Diese Methode kann für viele Zwecke verwendet werden, beispielsweise zum Zusammenführen von JS-Objekten. Eine der Verwendungszwecke besteht darin, flaches Kopieren durchzuführen. Der erste Parameter dieser Methode ist das zu kopierende Zielobjekt und die folgenden Parameter sind die zu kopierenden Quellobjekte (es können auch mehrere Quellen sein).

Die Syntax von object.assign lautet: Object.assign(target, ...sources)

Mangel 

  • Geerbte Eigenschaften von Objekten werden nicht kopiert
  • Nicht aufzählbare Eigenschaften von Objekten werden nicht kopiert
let obj1 = { a:{ b:1 }, sym:Symbol(1)}; 
Object.defineProperty(obj1, 'innumerable' ,{
    value:'不可枚举属性',
    enumerable:false
});
let obj2 = {};
Object.assign(obj2,obj1)
obj1.a.b = 2;
console.log('obj1',obj1);
console.log('obj2',obj2);

 

Wie Sie dem obigen Code entnehmen können, können Sie mit object.assign auch Objekte vom Typ Symbol kopieren. Wenn Sie jedoch zum Attribut der zweiten Ebene des Objekts, obj1.ab, gelangen, wirkt sich die Änderung des Werts des ersteren auch auf dessen zweite Ebene aus. Layer-Eigenschaft. Der Wert des Attributs zeigt, dass immer noch ein Problem beim Zugriff auf den gemeinsamen Heap-Speicher besteht, was bedeutet, dass diese Methode nicht weiter kopieren kann, sondern nur die flache Kopierfunktion abschließt.

Spread-Operator

Nutzen Sie den Spread-Operator von JavaScript, um die flache Kopierfunktion beim Erstellen von Objekten abzuschließen. 

扩展运算符的语法为:`let cloneObj = { ...obj };`
/* 对象的拷贝 */
let obj = {a:1,b:{c:1}}
let obj2 = {...obj}
obj.a = 2
console.log(obj)  //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2
console.log(obj)  //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}}
/* 数组的拷贝 */
let arr = [1, 2, 3];
let newArr = [...arr]; //跟arr.slice()是一样的效果

Der Spread-Operator hat den gleichen Fehler wie object.assign, das heißt, die implementierte Funktion zum flachen Kopieren ist ähnlich. Wenn es sich bei den Attributen jedoch alle um Grundtypwerte handelt, ist es bequemer, den Spread-Operator zum Ausführen eines flachen Kopierens zu verwenden.

concat kopiert das Array 

Die Array-  concat Methode ist eigentlich eine flache Kopie. Wenn Sie also ein Array verbinden, das einen Referenztyp enthält, müssen Sie darauf achten, die Attribute der Elemente im ursprünglichen Array zu ändern, da sich dies auf das nach dem Kopieren verbundene Array auswirkt. Es kann jedoch  concat nur für flache Kopien von Arrays verwendet werden und die Verwendungsszenarien sind relativ begrenzt. Der Code wird unten angezeigt. 

let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr);  // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]

 Slice-Kopie-Array

slice Die Methode ist auch ziemlich begrenzt, weil 它仅仅针对数组类型. slice方法会返回一个新的数组对象Dieses Objekt bestimmt die Start- und Endzeit des Abfangens des ursprünglichen Arrays durch die ersten beiden Parameter der Methode und hat keinen Einfluss oder eine Änderung des ursprünglichen Arrays.

slice 的语法为:arr.slice(begin, end);
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr);  //[ 1, 2, { val: 1000 } ]

Wie Sie dem obigen Code entnehmen können, ist dies der Fall 浅拷贝的限制所在了——它只能拷贝一层对象. wenn 存在对象的嵌套,那么浅拷贝将无能为力. Daher wurde Deep Copy entwickelt, um dieses Problem zu lösen. Es kann das Problem der Verschachtelung mehrschichtiger Objekte lösen und das Kopieren vollständig realisieren.

Ideen        

  1. Erstellen Sie eine einfache Kopie des Basistyps
  2. Erstellen Sie einen neuen Speicher für den Referenzdatentyp und kopieren Sie eine Ebene mit Objektattributen (und verweisen Sie weiterhin auf denselben Speicherplatz wie das ursprüngliche Objekt).

erreichen 


const shallowClone = (target) => {
  // 判断目标对象是否为对象类型且不为null
  if (typeof target === 'object' && target !== null) {
    // 创建一个克隆对象 cloneTarget,如果目标对象是数组则创建一个空数组,否则创建一个空对象
    const cloneTarget = Array.isArray(target) ? []: {};
    // 遍历目标对象的属性,使用 for...in 循环,判断属性是否为对象本身的属性(而非原型链上的属性)
    for (let prop in target) {
      if (target.hasOwnProperty(prop)) {
        // 给克隆对象的属性赋值,将目标对象的属性值赋给克隆对象的属性
        cloneTarget[prop] = target[prop];
      }
    }
    // 返回克隆对象
    return cloneTarget;
  } else {
    // 如果目标对象不是对象类型或为null,则直接返回目标对象
    return target;
  }
}

 

 Indem Sie die Typbeurteilung verwenden und eine for-Schleife für Referenztypobjekte ausführen, um die Objekteigenschaften zu durchlaufen und den Eigenschaften des Zielobjekts Werte zuzuweisen, können Sie grundsätzlich eine flache Kopie des Codes manuell implementieren.

Deep-Copy-Prinzip und Implementierung

Die flache Kopie erstellt nur ein neues Objekt und kopiert den Basistypwert des Originalobjekts, während der Referenzdatentyp nur eine Attributebene kopiert und tiefere Kopien nicht erstellt werden können. Anders verhält es sich mit Deep Copy. Bei komplexen Referenzdatentypen wird eine Speicheradresse im Heap-Speicher vollständig geöffnet und das Originalobjekt zur Speicherung vollständig kopiert.

Diese beiden Objekte sind unabhängig voneinander und unbeeinflusst, wodurch eine vollständige Speichertrennung erreicht wird. Generell lässt sich das Prinzip von Deep Copy wie folgt zusammenfassen:

Dem Zielobjekt wird eine vollständige Kopie eines Objekts aus dem Speicher übergeben, und im Heap-Speicher wird ein neuer Speicherplatz zum Speichern des neuen Objekts geöffnet. Durch Änderungen am neuen Objekt wird das ursprüngliche Objekt nicht verändert, wodurch eine echte Trennung zwischen den Objekten erreicht wird die Zwei. 

Beggars Version von JSON.stringify() 

JSON.stringify() Es handelt sich um die einfachste Deep-Copy-Methode im aktuellen Entwicklungsprozess. Sie serialisiert tatsächlich ein Objekt in eine  JSON Zeichenfolge, konvertiert den Inhalt des Objekts in eine Zeichenfolge und generiert schließlich mithilfe  JSON.parse() der Methode  JSON ein neues Objekt aus der Zeichenfolge. 

let a = {
    age: 1,
    jobs: {
        first: 'FE'
    }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

 Mangel

  • Wenn der Wert des kopierten Objekts einen dieser Typen hat 函数, verschwindet das Schlüssel-Wert-Paar in der Zeichenfolge nach der JSON.stringify()-Sequenz undefined.symbol
  • Die Kopie Date引用类型wird zu einer Zeichenfolge
  • Nicht aufzählbare Eigenschaften können nicht kopiert werden
  • Die Prototypenkette des Objekts konnte nicht kopiert werden
  • Die Kopie RegExp引用类型wird zu einem leeren Objekt
  • Das Objekt enthält NaNund das Ergebnis der JSON.parse() Infinity- -InfinitySerialisierung wirdnull
  • Der Zirkelverweis des Objekts kann nicht kopiert werden, was 对象成环(obj[key]=obj)der Fall ist
function Obj() { 
  this.func = function () { alert(1) }; 
  this.obj = {a:1};
  this.arr = [1,2,3];
  this.und = undefined; 
  this.reg = /123/; 
  this.date = new Date(0); 
  this.NaN = NaN;
  this.infinity = Infinity;
  this.sym = Symbol(1);
} 
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{ 
  enumerable:false,
  value:'innumerable'
});
console.log('obj1',obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log('obj2',obj2);

 

 

Obwohl er immer noch die oben genannten Probleme hat, reicht es aus, den täglichen Entwicklungsbedarf einfach und schnell zu decken

Verwenden Sie die JSON.stringify-Methode, um Deep-Copy-Objekte zu implementieren. Obwohl noch viele Funktionen nicht implementiert werden können, reicht diese Methode aus, um den täglichen Entwicklungsbedarf zu decken, und ist die einfachste und schnellste. Für andere Datentypen, die problematischeren Attributen entsprechen und tief kopiert werden müssen, ist JSON.stringify vorerst noch nicht zufriedenstellend, daher sind die folgenden Methoden erforderlich.

Basisversion (handschriftliche rekursive Implementierung)

Das Folgende ist ein Beispiel für die Implementierung der DeepClone-Funktionskapselung. Es verwendet for in, um die Attributwerte der eingehenden Parameter zu durchlaufen. Wenn der Wert ein Referenztyp ist, wird die Funktion erneut rekursiv aufgerufen. Wenn es sich um Basisdaten handelt Typ, es wird direkt kopiert.

let obj1 = {
  a:{
    b:1
  }
}
function deepClone(obj) { 
  let cloneObj = {}
  for(let key in obj) {                 //遍历
    if(typeof obj[key] ==='object') { 
      cloneObj[key] = deepClone(obj[key])  //是对象就再次调用该函数递归
    } else {
      cloneObj[key] = obj[key]  //基本类型的话直接复制值
    }
  }
  return cloneObj
}
let obj2 = deepClone(obj1);
obj1.a.b = 2;
console.log(obj2);   //  {a:{b:1}}

 

 Obwohl Rekursion verwendet werden kann, um eine tiefe Kopie zu implementieren, wie oben JSON.stringify, gibt es immer noch einige Probleme, die nicht vollständig gelöst wurden, wie zum Beispiel:

  1. Diese Deep-Copy-Funktion kann keine nicht aufzählbaren Eigenschaften und Symboltypen kopieren.
  2. Diese Methode führt nur das rekursive Kopieren von Werten gewöhnlicher Referenztypen durch, kann diese jedoch nicht korrekt für Referenztypen wie Array, Date, RegExp, Error und Function kopieren.
  3. Es gibt eine Schleife in den Eigenschaften des Objekts, d. h. der Zirkelverweis wird nicht aufgelöst.

Auch diese Basisversion ist relativ einfach zu schreiben und kommt mit den meisten Anwendungssituationen zurecht. Aber es gibt immer noch viele Mängel. Was sollen wir also tun? ,

Keine Sorge, wir haben auch erweiterte Versionen

Erweiterte Version (rekursive Implementierung)

Was sollten wir tun, um die oben genannten Probleme zu lösen?

  1. Für die nicht aufzählbaren Eigenschaften und Symboltypen, die das Objekt durchlaufen können, können wir die Methode Reflect.ownKeys verwenden (kann alle Eigenschaften eines Objekts abrufen, einschließlich aufzählbarer Eigenschaften, nicht aufzählbarer Eigenschaften und Symboltypeigenschaften).
  2. Wenn der Parameter vom Typ „Datum“ oder „Regexp“ ist, wird direkt eine neue Instanz generiert und zurückgegeben.
  3. Verwenden Sie die Methode Object.getOwnPropertyDescriptors, um alle Eigenschaften des Objekts und die entsprechenden Merkmale abzurufen. Kombinieren Sie sie übrigens mit der Methode Object.create, um ein neues Objekt zu erstellen und die Prototypenkette des ursprünglichen Objekts zu erben.
  4. Verwenden Sie den Typ „weakMap“ als Hash-Tabelle. Da „weakMap“ ein schwacher Referenztyp ist, kann er Speicherverluste wirksam verhindern. Dies ist sehr hilfreich bei der Erkennung von Zirkelverweisen. Wenn ein Zyklus vorhanden ist, gibt die Referenz direkt den in der „Weakmap“ gespeicherten Wert zurück.

Implementieren Sie Deep Copy 

// 判断一个对象是否为复杂数据类型,即对象或函数类型,且不为 null
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

// 定义深拷贝函数 deepClone,接受两个参数:obj 为要进行深拷贝的目标对象,hash 为已经拷贝过的对象的哈希表(用于解决循环引用问题)
const deepClone = function (obj, hash = new WeakMap()) {
  // 如果目标对象是日期对象,则直接返回一个新的日期对象,避免修改原日期对象
  if (obj.constructor === Date) {
    return new Date(obj)
  }
  
  // 如果目标对象是正则对象,则直接返回一个新的正则对象,避免修改原正则对象
  if (obj.constructor === RegExp){
    return new RegExp(obj)
  }
  
  // 如果目标对象已经被拷贝过,则从 hash 中获取已经拷贝过的对象并返回,避免出现循环引用问题
  if (hash.has(obj)) {
    return hash.get(obj)
  }

  // 获取目标对象的所有属性描述符
  let allDesc = Object.getOwnPropertyDescriptors(obj)

  // 创建一个新对象 cloneObj,并将其原型链指向 obj 的原型对象
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

  // 将目标对象和克隆对象的映射关系存入 hash 中,用于解决循环引用问题
  hash.set(obj, cloneObj)

  // 遍历目标对象的所有属性(包括字符串类型和 Symbol 类型的属性名)
  for (let key of Reflect.ownKeys(obj)) { 
    // 如果目标对象的属性值是复杂数据类型(即对象或数组),则递归调用 deepClone 函数进行深拷贝,并将拷贝结果赋值给克隆对象的对应属性
    if (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') {
      cloneObj[key] = deepClone(obj[key], hash)
    } else {
      // 如果目标对象的属性值不是复杂数据类型,则直接将其赋值给克隆对象的对应属性
      cloneObj[key] = obj[key]
    }
  }
  // 返回深拷贝后的新对象
  return cloneObj
}


// 下面是验证代码
let obj = {
  num: 0,
  str: '',
  boolean: true,
  unf: undefined,
  nul: null,
  obj: { name: '我是一个对象', id: 1 },
  arr: [0, 1, 2],
  func: function () { console.log('我是一个函数') },
  date: new Date(0),
  reg: new RegExp('/我是一个正则/ig'),
  [Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
  enumerable: false, value: '不可枚举属性' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj    // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
 

Zusammenfassen

In diesem Artikel stellen wir das dunkle und flache Kopieren und ihre jeweiligen Vor- und Nachteile vor. Anschließend erläutern wir Schritt für Schritt die Ideen und die Umsetzung der handschriftlichen Methode des dunklen und flachen Kopierens, von flacher bis tiefer. Dies ist für uns sehr hilfreich um die zugrunde liegenden Prinzipien von js tief zu verstehen.

Abschließend wünsche ich mir, dass alle stärker werden! ! ! nb

Acho que você gosta

Origin blog.csdn.net/YN2000609/article/details/132410435
Recomendado
Clasificación