复杂的对象数据处理后,可能发现某个对象值变了,有时却不变。

上述问题往往会困扰 JS初学者,甚至老手也不一定能说出所以然。

本文将对此进行一定深层探究 —— 深入理解 JS中的对象的值传递。

Why

在引入概念之前,先出下面四道题,可以先试着写下,最下面公布答案哦~

P.S 各题互不关联

let a = [1,2]
let b = a
a = [3, 4]
console.log(b)
let a = [1,2]
let b = a
a[0] = 3
console.log(b)
let a = [1,2]
let b = a
a = [3,4]
a[0] = 3
console.log(b)
let a = [1,2]
let b = a
a.pop()
console.log(b)

附上 TS Playground,其实一个改改就够了,顺手都 share 如下:

Example 1 Example 2 Example 3 Example 4

如果你可以全对 💯,那证明对 JS 对象值的传递理解还算扎实。

如果其中有错误,那我觉得你有必要继续看下去了 (づ。◕‿‿◕。)づ


What

在解释上述 🌰 分析之前,我们需要复习一下 JS 的基础数据类型:

🙌 8 种(7 种原始类型 以及 对象)更多参考 MDN JavaScript 数据类型

  • 原始类型:Number、BigInt、String、Boolean、Null、Undefined 和 ES6中的 Symbol
  • 复杂类型:Object对象(数组是其中一种内置对象,函数等都是特殊的对象类型)

!!!注意:原始类型是不可变的(immutable),只有对象是可变的(mutable)。

我们再来具体理清下 = 赋值操作具体执行了什么?

  • 原始类型:值的拷贝/传递
  • 复杂类型:引用地址的拷贝;或称共享传递(call by sharing)名称不重要,领会 ↓ 即可

有了以上理论理解的基础,我们再来依次分析下之前的 🌰

直接上图,应该不难理解了吧,当执行了 a = [3,4] ,之后,a 和 b 变量的引用根本变了,此时不论 a如何改变其值,都不再影响 b。

但是,ab 共享引用(传递)时(即 Example2、4),a 的变化会直接影响 b

.pop() 或者 .push 等方法会直接更改数组,这里就不多加引申了。


继续举个简单的 🌰 ,看看你的掌握程度,以下会输出什么? (快速回答哦 (๑•̀ㅂ•́)و✧ )

let a = [1,2]
let b = a
b = []
console.log(a.length == 0)

let c = []  // TS类型强定义,TS Playground 中为 let c:number[] = []
console.log(b, c)
console.log(b == c)

let x = {}
let y = {}
console.log(x == y)

TS Playground

看看是否与你所想一致?为什么就不说了哦

最后,出个可能会作为面试题的 🌰

function changeAgeAndReference(person) {
    person.age = 18;
    person = {
        name: "Anna",
        age: 16
    };
    return person;
}
let personObj1 = {
    name: "Olaf",
    age: 1000
};
console.log(personObj1); // -> ?
let personObj2 = changeAgeAndReference(personObj1);
console.log(personObj1); // -> ?
console.log(personObj2); // -> ?

TS playground

🎉 如果真面试到,能否回答正确并且解释出所以然? 有兴趣的可以 Play 研究下(相对比较基础),但实际业务代码这么写会死得很惨 (-"-怒)

How

对象的实际应用

JS 中,数组、函数等皆为对象,那我们通常会怎么应用对象呢?举个 🌰

class student { // 构造函数
  name: string
  constructor(myname:string){
      this.name = myname;
  }
  sayHi(){
      console.log("Hi, I'm " + this.name)
  }
}

let s1 = new student("Elsa")
let s2 = s1  // ❌ 这样引用,s2的更改会直接影响s1
s2.name = "Anna"
s1.sayHi()

let s3 = new student("Olaf") //  ✅ 通常会用 new 来创建一个新的对象
s3.sayHi()

TS Playground

对于 TS 开发者,Interface 不失为一种好方式,有兴趣的参考 TS 的 Interface 了解一下?

浅拷贝 & 深拷贝

🎉 继续敲黑板,面试问到相关问题的几率 50%+,问答开始:

Q:浅拷贝、深拷贝是什么?有什么区别?

🙌 浅拷贝只复制一层对象的属性,而深拷贝则递归复制了所有层级。

Q:具体有哪些应用场景?什么时候用浅拷贝、什么时候用深拷贝?

有一份 Object 数据,你打算对它进行处理,但又希望拷贝一份副本出来,方便数据对比恢复数据等。

浅拷贝有效性针对的是单一层级对象,比如简单的一维数组等等。

深拷贝有效性针对的是多层级对象,比如后端返回的Json对象等。

Q:如何在 JS 中实现浅拷贝 & 深拷贝?

🙌 本来想自己总结几种常用方式的,浅拷贝和深拷贝 高赞文章已有,太棒了~

个人通常使用扩展运算符 ... 来实现浅拷贝

使用JSON.parse(JSON.stringify()) 来深拷贝

Summary

  • 原始类型是不可变的(immutable),只有对象是可变的(mutable)
  • 对象赋值 = 为引用地址的拷贝,改变会影响原对象(实际开发中,需慎用)
  • 需要掌握对象的实际应用方式 & 浅拷贝和深拷贝