js对象通过引用传递的详细讲解

下面这个例子可以获取到最新的值吗?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button onclick="timeChange()">更改时间</button>
    <button onclick="lookForTime()">获取时间</button>
    <script>
    function leftTimeRelatedFun() {
      let timeObject  = {
        startTime:0,
        endTime:0
      }
      // 点击了更改时间按钮
      function timeChange() {
        console.log('更改了时间')
        const endTime =  1000
        const startTime = 10
        // 下面这2种赋值有啥区别呀?
        //第1种: timeObject.startTime = startTime; timeObject.endTime = endTime
        //第2种
        timeObject = {
          startTime,
          endTime
        }
      }
      return {
        timeObject,
        timeChange
      }
    }
    const {
      timeObject,
      timeChange
    } = leftTimeRelatedFun()

  
    function lookForTime(){
      console.log('获取最新的值', timeObject)
    }
  </script>
</body>
</html>

现在问大家获取的值是:
{startTime: 0, endTime: 0} 还是 {startTime: 1000, endTime: 10} ?
也许很多小伙伴都认为获取的值是: {startTime: 1000, endTime: 10}
实际上不是,获取的是:{startTime: 0, endTime: 0}
image

分析原因

当 timeChange 函数中执行 timeObject = { startTime, endTime } 时,
修改的是对象引用(因为重新赋值了整个对象)
此时 timeObject 指向了一个新的对象。
但是返回的对象中的 timeObject 仍然指向原来的对象
因此外界获取不到最新的值。只能够拿到初始值(原来那个对象的值)。

或者说:修改对象的属性(object.xx =)改的是房子里的东西;
重新赋值(=)换的是房子本身,
外部拿的旧钥匙打不开新房子

在JavaScript中,对象是通过引用传递的

核心是:在JavaScript中,对象是通过引用传递的,当重新赋值整个(对象)变量时,
它指向了一个新的对象,而外部持有的(旧引用)不会自动更新。

解决办法一:逐一修改对象的属性

  function leftTimeRelatedFun() {
      // 此时页面上显示的开始时间和结束时间
      let timeObject  = {
        startTime:0,
        endTime:0
      }
      // 选择了时间按钮
      function timeChange() {
        console.log('更改了时间')
        const endTime =  1000
        const startTime = 10
        //第1种:修改:timeObject 变量本身没有变,
        // 只是修改了对象内部的属性值。对象的引用没有发生变化;始终指向同一个对象。外部持有的引用能看到最新的变化。
        timeObject.startTime = startTime; timeObject.endTime = endTime
      }
      return {
        timeObject,
        timeChange
      }
    }
    const {
      timeObject,
      timeChange
    } = leftTimeRelatedFun()

解决办法二:返回一个 getter 函数

function leftTimeRelatedFun() {
  let timeObject = { startTime: 0, endTime: 0 }
  
  function timeChange() {
    timeObject = { startTime: 10, endTime: 1000 }
  }
  
  // 通过这个方法来获取;闭包总是拿到最新的引用,
  function getTimeObject() {
    return timeObject 
  }
  
  return { getTimeObject, timeChange }
}

const { getTimeObject, timeChange } = leftTimeRelatedFun()

function lookForTime() {
  console.log('最新值', getTimeObject())
}

image

我们在看下这个题

function change(obj) {
  obj.name = 'changed';
  obj = { name: 'new' };
  obj.name = 'inner';
}
let data = { name: 'outer' };
change(data);
console.log(data.name); // 输出的是什么?

正确答案:输出的是changed

分析

当你把 data 传入 change 函数时
函数内部的形参 obj 和外面的 data 指向同一个对象(地址 0x001)。
此时内存情况:
data (地址 0x001) → { name: 'outer' }
obj (地址 0x001) → { name: 'outer' }
函数内部执行obj.name = 'changed';
它修改的是对象中的属性,引用地址没有发生改变。
此时的内存情况:
data (地址 0x001) → { name: 'changed' }
obj (地址 0x001) → { name: 'changed' }

obj = { name: 'new' };执行第二行代码(关键转折点)
这里不是修改原来的对象,而是给 obj 变量重新赋值了一个新的对象(新地址 0x002)
此时内存情况:
data (地址 0x001) → { name: 'changed' } (还在指着老对象)
obj (地址 0x002) → { name: 'new' } (换指了新对象)
然后执行obj.name = 'inner';

修改的是新对象(地址 0x002),老对象(地址 0x001)纹丝不动
函数执行完毕,打印 data.name 输出的是changed

总结函数传参:

如果传递的是引用类型,函数内部属性的修改会影响外部的变量。
如果重新赋值整个对象,则函数内部的参数和外部的变量引用地址发生了变化,以后他们之间修改互不影响

下面的题

function test(person) {
  // 传递的是引用类型,函数内部属性的修改会影响外部的变量
  person.age = 25;
  // 传递的是引用类型,重新赋值整个对象,函数内部的参数和外部的变量引用地址发生了变化,以后他们之间修改互不影响
  person = { age: 30 };
  person.age = 35;
  return person;
}

const p1 = { age: 20 };
// 此时 p2 = { age: 35}
const p2 = test(p1);

console.log(p1.age); // 输出25
console.log(p2.age); // 输出35

下面这个题

var funcs = [];
for (var i = 0; i < 3; i++) {
  funcs.push(function() { console.log(i); });
}
funcs[0](); // 3
funcs[1](); // 3
funcs[2](); // 3

核心原因有两个:

  1. var 没有块级作用域
    循环过程中,i 一直在被修改,最终停在了 3
    2.闭包捕获的是变量引用,不是值
    当函数真正执行时(你调用 funcs0 时),循环早已结束,i 已经是 3 了。

怎么改成输出 0、1、2?

var funcs = [];
for (let i = 0; i < 3; i++) {  // let 有块级作用域
  funcs.push(function() { console.log(i); });
}
funcs[0](); // 0
funcs[1](); // 1
funcs[2](); // 2

请问依次点击三个按钮控制台分别输出什么?

<button>0</button>
<button>1</button>
<button>2</button>
<script>
  var buttons = document.querySelectorAll('button');
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].onclick = function() {
      console.log(i);
    };
  }
</script>

输出的是3。原因是当点击按钮的时候,循环已经结束了。
此时的i=3;所以输出的是3

obj2 = obj1 会让 obj2 和 obj1 指向同一个对象

let obj1 = { name: 'Alice' }; 
let obj2 = obj1; // { name: 'Alice' }
obj2.name = 'Bob'; // { name: 'Bob' } 
console.log(obj1, obj2) //  { name: 'Bob' } 

总结 obj2 = obj1 会让 obj2 和 obj1 指向同一个对象,他们的引用地址是相同的
修改 obj2.name 会影响到 obj1,因为它们是同一个对象

引用地址的本质

let obj1 = { name: 'Alice' };
let obj2 = obj1;
// 它们在内存中的关系:
// obj1 → 0x001 (内存地址)
// obj2 → 0x001 (同一个内存地址)
// 0x001 处存储: { name: 'Alice' }
console.log(obj1 === obj2); // true(比较的是引用地址)

下面这个题

let obj1 = { name: 'Alice' };
let obj2 = obj1; // { name: 'Alice' }

obj2.name = 'Bob'; // { name: 'Bob' }

let obj3 = { name: 'Carol' };
obj2 = obj3; // { name: 'Carol' } 此时obj2和obj1断开了链接
obj2.name = 'Dave'; //  { name: 'Dave' }

console.log(obj1.name); //Bob
console.log(obj3.name); // Dave

再练习一个相似的题目

let a = { val: 1 };
let b = a; // { val: 1 }
b.val = 2; //  { val: 2 }
let c = { val: 3 };
b = c; // b = {val: 3}  // 此时b,a之间的链接断开了
b.val = 4; // b = {val: 4}
console.log(a.val); // ? 输出2
posted @ 2026-07-22 08:59  南风晚来晚相识  阅读(0)  评论(0)    收藏  举报