下面这个例子可以获取到最新的值吗?
<!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}

分析原因
当 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())
}

我们在看下这个题
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
核心原因有两个:
- 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