JavaScript中调用具备Hoisting特性的函数出现异常行为的原因解析
问题根因拆解
你碰到的怪异表现和JS变量提升(Hoisting)没有任何关系,完全是几个独立的认知偏差、控制台特性、代码bug凑出来的错觉:
核心认知偏差:你从一开始就没正确使用unique函数
unique是纯函数:接收传入的数组,返回一个全新的去重数组,完全不会修改原数组。
很多JS新手会默认“只要调用了函数,控制台就会自动显示函数的返回值”,实际上只有你显式写console.log(xxx)的内容才会输出到控制台。你代码里两次调用unique(randomArray),既没有用变量接返回值,也没有把返回值打印到控制台,根本没有渠道直接观测到“函数返回了什么”。你之前看到的所谓“首次调用返回[34,45,25]、第二次返回[34,45]”,本质是运行环境的控制台把延迟求值的输出内容错配到了这两行代码上,和函数本身的执行逻辑没有关联。
要拿到去重结果,必须显式接收返回值、打印输出,例如:
const deduped = unique(randomArray); console.log(deduped),直接调用函数不接返回值,不会产生任何可观测的效果。
关键错觉来源:控制台打印引用类型的延迟求值特性
你默认console.log(randomArray)打印的是调用时刻的数组快照,但实际所有主流JS运行环境(包括你用的RunJS、浏览器控制台),在打印数组、对象这类引用类型时,存的是内存地址引用,不是当下的值拷贝。
简单说:你执行console.log(randomArray)的瞬间,数组确实是你看到的[34,45,34,34],但控制台不会立刻把这个值存死;等整个脚本跑完、甚至你手动点开控制台折叠的数组条目时,它才会去读取内存里这个数组的最新值。
如果这中间数组的值被意外修改,你看到的打印结果就和实际调用时刻的状态对不上,制造出“加了一行console.log就影响函数执行结果”的虚假关联。
代码里的两个隐性bug,会进一步放大认知偏差
- 循环变量
i没有用let声明:你写的for (i = 0; i < ARRAY_LENGTH; i++),因为缺少声明关键字,i会变成绑定全局作用域的隐式全局变量,后续任何代码意外修改i的值,都会直接打乱循环的正常执行逻辑。 - 随机数索引范围写错:你用
Math.floor(Math.random() * ARRAY_LENGTH)生成索引,取值范围只有0-3,只会从NUMBERS数组的前四个元素里取随机数,和你预期的“从整个NUMBERS列表随机取数”的逻辑完全不符。
无错觉的验证写法
const NUMBERS = [34, 25, 45, 11, 36, 98, 58, 97, 56] const ARRAY_LENGTH = 4; function getRandomNumberList () { let res = [] // 给循环变量加let声明,避免全局污染 for (let i = 0; i < ARRAY_LENGTH; i++) { // 随机范围匹配整个NUMBERS数组长度 const randomIndex = Math.floor(Math.random() * NUMBERS.length) res.push(NUMBERS[randomIndex]) } return res } let randomArray = getRandomNumberList() // 转成字符串打印,绕开控制台引用延迟的坑,拿到真实快照 console.log('原始数组:', JSON.stringify(randomArray)) function unique (arr) { return [...new Set(arr)] } // 显式接收去重结果,直接打印 const firstDedupRes = unique(randomArray) console.log('首次去重结果:', JSON.stringify(firstDedupRes)) const secondDedupRes = unique(randomArray) console.log('二次去重结果:', JSON.stringify(secondDedupRes))
内容的提问来源于stack exchange,提问作者Charles b
相关产品推荐
相关产品推荐

