variable

JavaScript variables are loosely typed, and variables are nothing more than the name of a particular value at a particular point in time.

Raw value & reference value

ECMAScript variables are of both original and reference types. Primitive values are simple data (Number, String, Undefined) and reference values are objects (Object, Array, etc.).

The variable that holds the original value is accessed by value, and we are manipulating the actual value stored in the variable.

let a = 10;
let b = a;
console.log(a,b);/ / 10 10
b=30;
console.log(a,b);/ / 10 to 30
Copy the code

Variables that hold reference values are accessed by reference. A reference value is an object held in memory. You’re actually operating on a reference to the object, not the actual object itself. We can look at the following example, assuming that we think of the operation object as the operation itself, then we can understand as: the system copies the same variable of A to B, the two do not affect each other, this is not difficult to understand. But it’s not. A and B share this variable. They refer to the same block of memory.

let a = {};
let b = null;
b=a;
console.log(b===a)//true
Copy the code

Dynamic properties

For reference values, attributes can be added, modified, or removed dynamically, whereas original values cannot have attributes, but there are exceptions.

The original value cannot have attributes, and adding attributes to it does not raise an error, but does not work.

However, variable initializations of primitive types take two forms: literals and the new keyword. With the new keyword, an instance of type Object is created, but behaves like the original value. That is, it is functionally primitive and essentially an Object.

// Primitive value literals
let name = 'zs';
name.age = 10;
console.log(name.age);//undefined

// Original value new keyword
let name = new String("zs");
console.log(name, typeof name);//[String: 'zs'] object
name.age = 18;
console.log(name);//[String: 'zs'] { age: 18 }

/ / reference value
let person = {};
person.name = 'wn';
person.age = 18;
console.log(person.name,person.age);//wn18
Copy the code

Duplicate values

When the original value is assigned to another variable, the original value is copied. The two variables can be used independently of each other.

For reference values, the copy is actually a pointer, meaning that two variables point to the same object.

/ / the original value
let a = 10;
let b = a;
console.log(a,b);/ / 10 10
b=30;
console.log(a,b);/ / 10 to 30

/ / reference value
let zs = {name: 'zs'};
let person = zs;
console.log(person.name);//zs
zs.name='zss';
console.log(person.name);//zss
console.log(zs.name);//zss
Copy the code

Passing parameters

All function parameters in ECMAScript are passed by value. Values outside the function are copied to arguments inside the function. If it’s an original value, it’s a copy of the original value variable, if it’s a reference value, it’s a copy of the reference value variable, and that means that changes to the object in the function are reflected outside of the function.

function setNum(num) {
  num=20;
}
function setName(person) {
  person.name='zss';
}
let num=10;
let zs = {name: 'zs'};
setName(zs);
setNum(num);
console.log(num);/ / 10
console.log(zs);//{ name: 'zss' }
Copy the code

When a parameter is passed by reference, changes to an object’s properties inside the function are reflected outside the function because they refer to the same object. But function arguments are passed by value, and object passing can be understood as passing a reference inside the function as a value. We cannot change the reference.

Note: Parameters in a function are local variables, meaning that the function is recycled when it finishes executing.

var person = {name: 'zs'};

function setName(person) {
  person.name = 'zss';
  console.log(person === window.person)//true
  person = {name: 'ls'};
  console.log(person === window.person)//false
  console.log(person,window.person);//{name: "ls"} {name: "zss"}
}

setName(person);
console.log(person);//{name: "zss"}
Copy the code

This example takes advantage of the fact that properties declared globally by the var keyword are mounted to the window object. Let’s compare the function parameters with the original object. Person ===window.person = false; person== window.person = false; Because arguments are local variables inside the function and are destroyed after the function completes execution, the person inside the function is eventually reclaimed. The figure below shows the change of direction of variables at a glance.

Determine the type

Typeof is the best way to determine whether a variable is undefined, string, number, or Boolean. Note in particular that Typeof treats null values as object.

Typeof is useful for raw values, but not for reference values, and we don’t care if it is of type object, but rather what typeof object it is. The instanceof operator comes along to fulfill this requirement.

// Typeof determines the original value
let n = 10, str = 'a', bool = false, undef;
console.log(typeof n);//number
console.log(typeof str);//string
console.log(typeof bool);//boolean
console.log(typeof undef);//undefined
console.log(typeof null);//object

//instanceof determines the reference value
class Person {}
let zs=new Person();
let obj={};

console.log(zs instanceof Person);//true
console.log(Person[Symbol.hasInstance](zs));//true is the same as using the instanceof operator
console.log(obj instanceof Person);//false
Copy the code

Depth copy

Shallow and deep copying is mostly for objects and arrays. In layman’s terms, shallow copy copies only one layer, and if there are objects in that layer, then there are references. Deep copies recurse all the way to the original value, and copies of the original value are two independent values that do not affect each other. Common shallow copies are: destruct assignment (… Expand operators), object.assign (), etc.

// Destruct a shallow copy of the assignment
let obj1 = {a: 1.b: 2.arr: [1.2.3]};
letobj2 = {... obj1};let obj3 = Object.assign(obj1, obj2);
obj2.a = 10;
obj2.arr[0] = 10;
console.log(obj1);//{ a: 1, b: 2, arr: [ 10, 2, 3 ] }
console.log(obj2);//{ a: 10, b: 2, arr: [ 10, 2, 3 ] }
console.log(obj3);//{ a: 1, b: 2, arr: [ 10, 2, 3 ] }
Copy the code

The concept of shallow copy is easy to understand from the above code, the first layer of properties are independent, while obj.arr refers to the same object.

The basic idea of deep copy is to recurse to the original value and then copy it. JavaScript natively does not provide us with deep-copy functionality. We can implement one ourselves:

function deepClone(obj) {

  let keys = null, newObj = null;
  // Handle object and ARR separately
  if (Array.isArray(obj)) {
    // Generate the keys of the array
    keys = [...new Array(obj.length)].map((_, i) = > i);
    newObj = [];
  } else {
    // Get the key of the object
    keys = Object.getOwnPropertyNames(obj);
    newObj = {};
  }
  for (let i = 0, len = keys.length; i < len; i++) {
    let key = keys[i];
    let val = obj[key];
    // If it is not the original value, it is recursive
    if (typeof val === "object")
      newObj[key] = deepClone(val);
    else newObj[key] = val;
  }
  return newObj;
}

let obj1 = {
  a: 1.b: 2.c: {
    d: 3.e: 4.arr: [1.2[3.4]]}}let obj2 = deepClone(obj1);
obj2.a = 10;
obj2.c.d = 30;
obj2.c.arr[0] = 10;
obj2.c.arr[2] [0] = 30;
console.log(obj1);
//{ a: 1, b: 2, c: { d: 3, e: 4, arr: [ 1, 2, [ 3, 4 ] ] } }
console.log(obj2);
//{ a: 10, b: 2, c: { d: 30, e: 4, arr: [ 10, 2, [ 30, 4 ] ] } }
Copy the code

We can see that the deepClone() function is sufficient for our needs, but we run into a new problem: what if our object is a custom object? The following code looks like this:

class Person {
  constructor(name = ' ') {
    this.name = name;
  }

  setName(name) {
    this.name = name; }}let obj1 = {
  a: 1.b: 2.person: new Person('zs')}let obj2 = deepClone(obj1);
console.log(obj1);//{ a: 1, b: 2, person: Person { name: 'zs' } }
console.log(obj1.person.setName)//[Function: setName]
console.log(obj2);//{ a: 1, b: 2, person: { name: 'zs' } }
console.log(obj2.person.setName)//undefined
Copy the code

See from the results, our custom Person class contains methods have not been copied, the reason is that the Object. GetOwnPropertyNames (obj) didn’t get to the class of methods, we can solve like this:

  // Handle object and ARR separately
  if (Array.isArray(obj)) {
    // Generate the keys of the array
    keys = [...new Array(obj.length)].map((_, i) = > i);
    newObj = [];
  } else {
    // Get the key of the object
    keys = Object.getOwnPropertyNames(obj);
    // Maintain the original prototype chain
    newObj = new obj.constructor();// Preserve the prototype chain of an object by instantiating a new object
  }
Copy the code

Here is the complete function with the test data:

function deepClone(obj) {
  // Special processing
  if (obj === null) return null
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  if (typeofobj ! = ="object") return obj;
  // Handle object and ARR separately
  let keys = null, newObj = null;
  if (Array.isArray(obj)) {
    // Generate the keys of the array
    keys = [...new Array(obj.length)].map((_, i) = > i);
    newObj = [];
  } else {
    // Get the key of the object
    keys = Object.getOwnPropertyNames(obj);
    // Maintain the original prototype chain
    newObj = new obj.constructor();
  }
  for (let i = 0, len = keys.length; i < len; i++) {
    let key = keys[i];
    let val = obj[key];
    // If it is not the original value, it is recursive
    if (typeof val === "object")
      newObj[key] = deepClone(val);
    else newObj[key] = val;
  }
  return newObj;
}
class A {
  constructor(val = null) {
    this.val = val;
  }

  setVal(val) {
    this.val = val; }}let obj1 = {
  a: 1.c: {
    d: 4
  },
  person: new A('obj1'),
  arr: [1[2.3.4]]};let obj2 = deepClone(obj1);
obj1.arr[0] = 10;
obj1.arr[1] [0] = 20;
obj2.person.val = 'obj2'
obj1.person.setVal(Awesome!);
obj1.person.addtionFunc = function () {}

console.log(obj1)
console.log(obj2)
/** obj1 { a: 1, c: { d: 4 }, person: A { val: 666, addtionFunc: [Function (anonymous)] }, arr: [ 10, [ 20, 3, 4 ] ] } obj2 { a: 1, c: { d: 4 }, person: A { val: 'obj2' }, arr: [ 1, [ 2, 3, 4 ] ] } */
Copy the code

At this point, a deep-copy function that can fulfill the basic requirements is complete. An easy way to implement deep copy is through JSON serialization. It’s essentially a copy of the original value, because after serialization, the object becomes a JSON string, which is the original value, so it’s a copy of the original value. There is a drawback, however, that JSON serialized objects lose all their methods and prototype chains:

class Person {
  constructor(name = ' ') {
    this.name = name; }}let obj1 = {
  a: 1.b: 2.func(){},person: new Person('zs')};console.log(obj1);
//{ a: 1, b: 2, func: [Function: func], person: Person { name: 'zs' } }
console.log(typeof JSON.stringify(obj1));
//string
console.log(obj1.person instanceof Person);
//true
let obj2 = JSON.parse(JSON.stringify(obj1));
console.log(obj2);
//{ a: 1, b: 2, person: { name: 'zs' } }
console.log(obj2.person instanceof Person);
//false
Copy the code