关于函数传参
事情要从第一次刷力扣说起, 我怎么也没想到萌新的我第一天刷力扣, 并且刷的第一题(数组去重), 就捅出这么一个大窝, 本文将说明函数传参的原理, 以及顺带说明了用JS怎么刷力扣, 如果本文顺利看下来,
先上被坑的那个力扣题 :
我由于没有好好审题, 因此用了一个新的数组空间去做了这题, 也阴差阳错地解决了一个难点, 我的解题代码如下 :
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function (nums) {
let newNums = [];
for (let i = 0; i < nums.length; i++) {
if (newNums.indexOf(nums[i]) === -1) {
newNums.push(nums[i]);
}
}
nums = newNums;
return nums.length;
};
关于思路的详细解析, 可以见另一篇文章, 大概就是开辟了一个新数组, 然后遍历原数组, 遍历的每个值都进行一次判断(判断是否新数组中有该值, 有则放进新数组, 无则跳过, 进行下一轮遍历)
力扣上面的两个注释解释下(题目有说) :
@param {number[]} nums意思是nums的值为去重后的新数组@return {number}意思是返回数组的长度
本题思路是对的, 但是运行的时候确实报错了
然后我在自己的vscode运行了下, 结果如下
可见, 自己运行是对的, 但是跑力扣的时候却报错了, OK, 填坑之旅开始
想法一 : 因为力扣知道你申请了另一片数组空间?
首先, 力扣无法检测你是否申请了另一片空间存放数组的, 它只能检测空间复杂度是否超过规定值, 因此不是空间复杂度的原因.
想法一排除
想法二 : 力扣需要修改的nums是全局变量的nums
先提前剧透一下, 这个想法, 对了
在经过一番摸鱼滚打后, 大概知道了力扣过测试用例的方法
let nums = [1,1,2];
let len = removeDuplicates(nums);
console.log(len);
console.log(nums);
结合力扣给的示例 :
得出结论, 这里面关键的信息是, 力扣希望你调用函数时, 函数作用域内的nums, 在经过数组去重后, 修改全局作用域下的nums, 因此, 这就能理解问题所在了, 在经过一轮排查, 我发现了一个盲点
nums = newNums;
说明这里的nums, 并没有修改全局作用域下的nums, 也就是它给的测试用例, 下面举个例子说明下函数传参问题
函数传参
查阅了资料后, 知道当函数传递的类型为值传递, 关于值传递, 先放一段大佬的解释 :
什么是值传递 ?
当函数参数是引用类型时,将参数复制了一个副本到局部变量,只不过复制的这个副本是指向堆内存中的地址而已,我们在函数内部对对象的属性进行操作,实际上和外部变量指向堆内存中的值相同,但是这并不代表着引用传递,函数参数传递的并不是变量的引用,而是变量拷贝的副本,当变量是原始类型时,这个副本就是值本身,当变量是引用类型时,这个副本是指向堆内存的地址。
什么是引用类型(复杂数据类型) ?
简单理解, 像数组, 对象等类型为引用类型, 那么像数字型, 字符串型为原始类型(简单数据类型)
先看个简单的例子 :
let nums = [1, 2, 3];
nums = [1, 2, 3, 4];
console.log(nums);
输出 :
分析 :
0x666666和0x777777为数组申请空间的首地址
因此当nums = [1, 2, 3, 4];时, 仅仅改变了nums指针的指向而已
正题 :
let nums = [1, 2, 3];
change(nums);
function change(nums) {
nums = [1, 2];
console.log(nums + "函数作用域");
}
console.log(nums + "全局作用域");
输出 :
分析 :
let nums = [1, 2, 3];对应步骤①, nums在堆中申请了一块数组空间存放[1, 2, 3] 并且nums指向了首地址change(nums);对应步骤②, 当函数调用时, 此时函数中接受的实参为一个值, 当值传到函数里面时, nums为一个副本, 它变成了局部变量nums, 并且指向了0x666666, 和全局变量一样
nums = [1, 2];对应步骤③, 此处最为关键, 此时函数申请了另外一片空间, 存放了[1, 2], 并且局部变量的nums指向了0x777777, 也就是新空间的首地址, 因此, 全局作用域下的nums, 和局部作用域下的nums, 已经变得不一样了
那有没有办法修改全局变量的nums呢, 方法肯定有的, 这里只提供一种最简单的作为解释
例子 :
let nums = [1, 2, 3];
change(nums);
function change(nums) {
nums[2] = 1;
console.log(nums + "函数作用域");
}
console.log(nums + "全局作用域");
输出 :
分析 : 结合上图的步骤②, 此时nums还指向0x66666
nums[2] = 1;, 这里修改的还是原数组的值, 因为nums[2]指向了nums的第三个值存放的地址
改进
到这里, 基本上结论已经浮出水面了, 结合上述原因, 对代码重新作出了改进
var removeDuplicates = function (nums) {
let newNums = [];
for (let i = 0; i < nums.length; i++) {
if (newNums.indexOf(nums[i]) === -1) {
newNums.push(nums[i]);
}
}
// 遍历新数组, 一个个存放到原数组中
for (let i = 0; i < newNums.length; i++) {
nums[i] = newNums[i];
}
// 删除多余的部分
nums.splice(newNums.length, nums.length);
return nums.length;
};
力扣 :
终于......
结论 : 通过解决了函数传参的问题, 解决了本文的疑惑, 至于浅拷贝和深拷贝, 由于涉及不多, 并没有花时间讲述, 虽然最终题解时间复杂度和空间复杂度, 都很菜, 这里先不给出最优解, 本文旨在讲述函数传参的问题和前端用JS做力扣要怎么做, 最优解有不少大佬讲得比我好, 可以直接去看力扣题解
PS : 萌新发文章, 轻喷, 有做的不好的欢迎指出, cya