Variable Assignment and Primitive/Object Mutability

栏目: IT技术 · 发布时间: 4年前

内容简介:If you’re not familiar with how JavaScript variable assignment and primitive/object mutability works, you might find yourself encountering bugs that you can’t quite explain.JavaScript has seven

Variable Assignment and Primitive/Object Mutability

If you’re not familiar with how JavaScript variable assignment and primitive/object mutability works, you might find yourself encountering bugs that you can’t quite explain. I think this is one of the more important foundational JavaScript topics to understand , and I’m excited to share it with you today!

JavaScript Data Types

JavaScript has seven primitive data types[1]:

  • Boolean ( true , false )
  • Null ( null )
  • Undefined ( undefined )
  • Number (e.g., 42 )
  • BigInt (e.g., 10000000000000000n )
  • String (e.g., "Hello world" )
  • Symbol (e.g., Symbol(11) )

Additionally, JavaScript has object data types. JavaScript has several built-in object data types, the most well-known and widely-used being Array , Object , and Function .

Assignment, Reassignment, and Mutation

Assignment, reassignment, and mutation are important concepts to know and differentiate in JavaScript. Let’s define each and explore some examples.

Assignment

To understand assignment, let’s analyze a simple example.

let name = 'Julie';

To understand what happened here, we need to go right-to-left:

  1. We create the string "Julie"
  2. We create the variable name
  3. We assign the variable name a reference to the string we previously created

So, assignment can be thought of as the process of creating a variable name and having that variable refer to data (be it a primitive or object data type).

Reassignment

Let’s extend the last example. First, we will assign the variable name a reference to the string "Julie" and then we will reassign that variable a reference to the string "Jack" :

let name = 'Julie';
name = 'Jack';

Again, the play-by-play:

  1. We create the string "Julie"
  2. We create the variable name
  3. We assign the variable name a reference to the string we previously created
  4. We create the string "Jack"
  5. We reassign the variable name a reference to the string "Jack"

If this all seems basic, that’s okay! We’re laying the foundation for understanding some more complicated behavior and I think you’ll be glad we did this review.

Mutation

Mutation is the act of changing data. It’s important to note that, in our examples thus far, we haven’t changed any of our data.

Primitive Mutation (spoiler: you can’t)

In fact, we wouldn’t have been able to change any of our data in the previous example even if we wanted to—primitives can’t be mutated (they are immutable ). Let’s try to mutate a string and bask in the failure:

let name = 'Jack';
name[2] = 'e';
console.log(name);
// "Jack"

Obviously, our attempt at mutation failed. This is expected: we simply can’t mutate primitive data types.

Object Mutation

We absolutely can mutate objects! Let’s look at an example.

let person = {
  name: 'Beck',
};
person.name = 'Bailey';
console.log(person);
// { name: "Bailey" }

So yeah, that worked. It’s important to keep in mind that we never reassigned the person variable, but we did mutate the object at which it was pointing.

Why This All Matters

Get ready for the payoff.I’m going to give you two examples mixing concepts of assignment and mutation.

Example 1: Primitives

let name = 'Mindy';
let name2 = name;
name2 = 'Mork';
console.log(name, name2);
// "Mindy" "Mork"

Not very surprising. To be thorough, let’s recap the last snippet in more detail:

  1. We create the string "Mindy"
  2. We create the variable name and assign it a reference to the string "Mindy"
  3. We create the variable name2 and assign a reference to the string "Mindy"
  4. We create the string "Mork" and reassign name2 to reference that string
  5. When we console.log name and name2 , we find that name is still referencing "Mindy" and name2 is referencing the string "Mork"

Example 2: Objects

let person = { name: 'Jack' };
let person2 = person;
person2.name = 'Jill';
console.log(person, person2);
// { name: "Jill" }
// { name: "Jill" }

If this surprises you, try it out in the console or your favorite JS runtime environment!

Why does this happen? Let’s do the play-by-play:

  1. We create the object { name: "Jack" }
  2. We create the person variable and assign it a reference to the created object
  3. We create the person2 variable and set it equal to person , which is referring to the previously-created object. (Note: person2 is now referencing the same object that person is referencing!)
  4. We create the string "Jill" and mutate the object by reassiging the name property to reference "Jill"
  5. When we console.log person and person2 , we note that the one object in memory that both variables were referencing has been mutated.

Pretty cool, right? And by cool, I mean potentially scary if you didn’t know about this behavior.

The Real Differentiator: Mutability

As we discussed earlier, primitive data types are immutable. That means we really don’t have to worry about whether two variables point to the same primitive in memory: that primitive won’t change. At best, we can reassign one of our variables to point at some other data, but that won’t affect the other variable.

Objects, on the other hand, are mutable. Therefore, we have to be keep in mind that multiple variables may be pointing to the same object in memory. “Mutating” one of those variables is a misnomer, you’re mutating the object it’s referencing, which will be reflected in any other variable referencing that same object.

Is This a Bad Thing?

This question is far too nuanced to give a simple yes or no answer. Since I have spent a good amount of time understanding JavaScript object references and mutability, I feel like I actually use it to my advantage quite a bit and, for me , it’s a good thing. But for newcomers and those who haven’t had the time to really understand this behavior, it can cause some pretty insidious bugs.

How Do I Prevent This from Happening?

In many situations, you don’t want two variables referencing the same object. The best way to prevent this is by creating a copy of the object when you do the assignment.

There are a couple ways to create a copy of an object: using the Object.assign method and spread operator , respectively.

let person = { name: 'Jack' };
// Object.assign
let person2 = Object.assign({}, person);
// Spread operator
let person3 = { ...person };
person2.name = 'Pete';
person3.name = 'Betty';
console.log(person, person2, person3);
// { name: "Jack" }
// { name: "Pete" }
// { name: "Betty" }

Success! But a word of caution: this isn’t a silver bullet because we’re only creating shallow copies of the person object.

Shallow Copies?

If our object has objects nested within it, shallow copy mechanisms like Object.assign and the spread operator will only create copies of the root level object, but deeper objects will still be shared. Here’s an example:

let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = { ...person };
person2.name = 'Betty';
person2.animal.type = 'Cat';
person2.animal.name = 'Whiskers';
console.log(person);
/*
{
  name: "Jack",
  animal: {
    type: "Cat",
    name: "Whiskers"
  }
}
*/

Ack! So we copies the top level properties but we’re still sharing references to deeper objects in the object tree. If those deeper objects are mutated, it’s reflected when we access either the person or person2 variable.

Deep Copying

Deep copying to the rescue! There are a number of ways to deep copy a JavaScript object[2]. I’ll cover two here: using JSON.stringify/JSON.parse and using a deep clone library.

JSON.stringify/JSON.parse

If your object is simple enough, you can use JSON.stringify to convert it to a string and then JSON.parse to convert it back into a JavaScript object.

let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = JSON.parse(JSON.stringify(person));

And this will work… but only in limited situations. If your object has any data that cannot be represented in a JSON string (e.g., functions), that data will be lost! A risky gambit if you’re not super confident in the simplicity of your object.

Deep Clone Library

There are a lot of good deep clone libraries out there. One such example is lodash with its _.cloneDeep method. These libraries will generally traverse your object and do shallow copies all the way down until everything has been copied. From your perspective, all you have to do is import lodash and use cloneDeep :

let person = {
  name: 'Jack',
  animal: {
    type: 'Dog',
    name: 'Daffodil',
  },
};
person2 = _.cloneDeep(person);

Conclusion

This discussion is really the tip of the iceburg when it comes to variable assignment and data mutability in JavaScript. I invite you to continue researching this topic, experimenting with topics like equality comparison when assigning object references and copying objects.

References:

  1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures
  2. https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript

以上所述就是小编给大家介绍的《Variable Assignment and Primitive/Object Mutability》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C算法(第二卷:图算法)(第3版)

C算法(第二卷:图算法)(第3版)

塞德威克(Sedgewick Robert) / 周良忠 / 第1版 (2004年1月1日) / 2004-4 / 38.0

《C算法(第2卷)(图算法)(第3版)(中文版)》所讨论的图算法,都是实际中解决图问题的最重要的已知方法。《C算法(第2卷)(图算法)(第3版)(中文版)》的主要宗旨是让越来越多需要了解这些算法的人的能够掌握这些方法及基本原理。书中根据基本原理从基本住处开始循序渐进地讲解,然后再介绍一些经典方法,最后介绍仍在进行研究和发展的现代技术。精心挑选的实例、详尽的图示以及完整的实现代码与正文中的算法和应用......一起来看看 《C算法(第二卷:图算法)(第3版)》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具