V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
ynohoahc
V2EX  ›  问与答

关于 js 函数返回值的一个小问题

  •  
  •   ynohoahc · 2020-04-24 09:58:55 +08:00 · 2859 次点击
    这是一个创建于 1703 天前的主题,其中的信息可能已经有所发展或是发生改变。

    也算是吃前端这碗饭吃了一年多了, 但是今天遇到个基础问题解决不了, 很惭愧 因为公司网络,不能外网传文件(图片), 我就用伪代码稍微代替一下我的问题场景

    function test(list) { list = list.slice(1) }

    a = [0,1,2,3,4,5] test(a)

    这样子我预想 a 应该是等于[1,2,3,4,5]了, 但实际上还是[0,1,2,3,4,5]

    第 1 条附言  ·  2020-04-24 10:32:32 +08:00
    谢谢大佬们的答疑
    我也清楚是什么原因了
    我之前以为传参是引用传递的形式, 在经过 list = list.slice(1)之后我以为不改变内存地址,而是直接覆盖原有内存地址中 list 值.
    但实际上 js 函数传参都是值传递的形式,所以我传进 test 里面的形参 list 在经过 list = list.slice(1)之后, 实际上 list 的内存地址是直接被覆盖了, 而不是改变 list 值.
    45 条回复    2020-04-25 00:28:15 +08:00
    shintendo
        1
    shintendo  
       2020-04-24 10:14:44 +08:00   ❤️ 1
    list 是局部变量,你在函数里给 list 赋值毫无意义。这应该是编程常识吧,跟 js 没什么关系
    max21
        2
    max21  
       2020-04-24 10:16:56 +08:00   ❤️ 1
    function test(list) { return list.slice(1) }
    hewelzei
        3
    hewelzei  
       2020-04-24 10:22:24 +08:00
    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
    slice() 是返回新的数组,不改变原来的数组,应该仔细看看 MDN 文档,补充一下基础知识
    mc201319
        4
    mc201319  
       2020-04-24 10:23:54 +08:00 via Android
    你这里是把 a 的值当做参数穿进去的,list 只是和 a 的值相同,他们的指针已经完全不同了
    Vegetable
        5
    Vegetable  
       2020-04-24 10:23:58 +08:00   ❤️ 1
    @shintendo #1 是因为这个?不是因为 slice 不是 in place 的?
    slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括 end )。原始数组不会被改变。

    https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/slice

    你这个需求应该是 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
    Array.prototype.splice

    ```Javascript
    function test(list) {
    list.splice(0, 1)
    }
    const a = [0, 1, 2, 3, 4, 5]

    test(a)
    console.log(a)
    //[ 1, 2, 3, 4, 5 ]
    ```
    Vegetable
        6
    Vegetable  
       2020-04-24 10:24:23 +08:00
    拜托,我只是一个后端,都知道这个,前边的大哥都在干什么?
    Vegetable
        7
    Vegetable  
       2020-04-24 10:26:28 +08:00
    @Vegetable #6 哦是我理解出现了偏差,楼上说的没错,楼主困惑的应该是为什么赋值没有生效

    这我也不知道怎么解释了...
    LyleRockkk
        8
    LyleRockkk  
       2020-04-24 10:27:08 +08:00
    吃前端这碗饭一年多了...
    maichael
        9
    maichael  
       2020-04-24 10:27:54 +08:00   ❤️ 1
    @Vegetable #7 很好解释,js 函数传参都是值传递,外面的 a 和里面的 list 实际上处理值相同之外没什么关系。
    wszgrcy
        10
    wszgrcy  
       2020-04-24 10:29:54 +08:00 via Android
    请加参数前面加& (滑稽)
    LyleRockkk
        11
    LyleRockkk  
       2020-04-24 10:30:15 +08:00
    LZ 应该是 想通过一个 function 改变 外部定义好的 a 的值,不过还是基础问题,不管在 function 内部,还是外部,都要有对 a 赋值的过程,才能改变 a
    b821025551b
        12
    b821025551b  
       2020-04-24 10:31:21 +08:00   ❤️ 1
    你这一年多的工作经历是切图么
    leoskey
        13
    leoskey  
       2020-04-24 10:31:46 +08:00
    这里是 值传递,拷贝一份 a 传到 list,所以 a 和 list 是不同的。
    TomVista
        14
    TomVista  
       2020-04-24 10:32:58 +08:00
    @Vegetable emm,上面讲的也有道理的,,另外 list = list.slice(1) 这个东西因为命名的原因语义并不太清楚,理解的不一样很正常
    icebreaker12
        15
    icebreaker12  
       2020-04-24 10:36:10 +08:00
    其实利用浅拷贝把 a 改成对象就能实现了。

    function test(list) { list.arr = list.arr.slice(1) }
    var a = { arr:[0,1,2,3,4,5] };
    test(a);
    max21
        16
    max21  
       2020-04-24 10:37:07 +08:00   ❤️ 1
    @leoskey 对象都是引用传递的,这里没变化主要还是 slice 这个函数不改变原始值,所以 a 没变化,仅此而已
    CharmanderS5
        17
    CharmanderS5  
       2020-04-24 10:37:35 +08:00 via Android
    两个数组引用的地址不用 所以原数组不会被改变
    ynohoahc
        18
    ynohoahc  
    OP
       2020-04-24 10:38:18 +08:00
    @LyleRockkk #8 哈哈哈 划水了一年多
    ynohoahc
        19
    ynohoahc  
    OP
       2020-04-24 10:39:17 +08:00
    @b821025551b #12 哎 心灰意冷 实际上切图都不会切 都直接拿 UI 的
    shintendo
        20
    shintendo  
       2020-04-24 10:42:30 +08:00
    @Vegetable 因为楼主写的是 list = list.slice(1)而不是 list.slice(1),所以我默认楼主知道这不是 in place 的
    Vegetable
        21
    Vegetable  
       2020-04-24 10:44:24 +08:00
    @leoskey #13 没有拷贝吧,只是两个不同的地址指向了同一个数组,当再次赋值的时候,list 这个地址指向了 slice 方法产生的新数组,a 这个数组完全没有变过.

    如果直接操作传入的 list,外边的 a 是会改变的.
    LyleRockkk
        22
    LyleRockkk  
       2020-04-24 10:47:34 +08:00
    @max21 这位老哥正解了,数组也是引用传递的,slice 返回了新数组,并没有改变 a

    var a = [1,2,3];
    function test(list){
    list.push(4);
    }
    test(a);

    这样,a 就会变了
    ynohoahc
        23
    ynohoahc  
    OP
       2020-04-24 11:01:25 +08:00
    @LyleRockkk #22 啊? 我个人并不认可 max21 老哥的说法, 我知道 slice 不是变异方法. "对象都是引用传递的"这句话我不知道是什么场景下说的, 但是至少在函数传参的场景中,按照 javascript 高程所说, 都是"值传递"的
    https://i.loli.net/2020/04/24/UkoxRCSygI5jKPO.jpg

    所以我传给 test 的只是 a 这个变量代表的内存地址所代表的的值,
    当我在内部执行完 list = list.slice(1)后, 实际上只是内部的形参 list 所代表的内存地址所代表的值变了一下而已, 跟外部的实参 a 实际上没任何关系.

    这是我理解的
    yaphets666
        24
    yaphets666  
       2020-04-24 11:07:02 +08:00
    面试经常问的 哪些数组方法是改变原数组的 哪些方法不改变原数组
    redam
        25
    redam  
       2020-04-24 11:19:50 +08:00
    一点浅见:
    js 函数参数是值传递指的是,函数参数获得传入变量的一份复制。实际上变量引用地址也是复制了的。楼主代码没有生效的原因是,list 进行了重新的赋值,改变了其引用,所以在函数内的修改,外部变量 list 并没有改变,稍微改造一下,不改变 list 的引用就可以生效了:

    ```
    function test(list) {
    let _list=list.slice(1);
    list.length=0;
    list=list.push(..._list);
    }


    let a = [0, 1, 2, 3, 4, 5];

    test(a);
    console.log(a); //[ 1, 2, 3, 4, 5 ]
    ```
    wildnode
        26
    wildnode  
       2020-04-24 11:20:59 +08:00
    说下我个人的理解哈,JS 中函数参数确实都是值传递的,但是对于复杂类型(对象,数组),这个值传递中的值指的是变量的内存地址,而不是变量本身,比如楼主的例子,你把 slice 换成 pop,list 是会变的,这说明并不是楼上一些 V 友所说的"值传递"。第二个问题,既然是传递的内存地址,为什么函数中给 list 赋值后,list 没有发生变化呢,这里我觉得应该是在函数的作用域中对形参赋值,会改变形参的地址,但是并不会对之前地址中的变量产生影响。

    Emmmm,感觉我也说不明白,抛砖引玉等大佬吧。
    max21
        27
    max21  
       2020-04-24 11:22:13 +08:00
    @ynohoahc a 保存着 [0,1,2,3,4,5]的引用,执行 test 函数后,a 赋值给 list,那么这时 a 和 list 同时保存[0,1,2,3,4,5]的引用。然后 list = list.slice(1),list.slice(1)返回一个新的对象然后赋值给 list,这时 list 更改了,但这里并没有更改 a,a 还是保存了[0,1,2,3,4,5]的引用,然后函数执行完 list 被回收了,
    wutiantong
        28
    wutiantong  
       2020-04-24 11:29:20 +08:00
    能遇到并且问出这个问题,至少标志着 lz 的编程能力即将入门了
    shintendo
        29
    shintendo  
       2020-04-24 11:34:54 +08:00
    @max21
    @LyleRockkk
    js 只有值传递,没有引用传递
    实际上如 @leoskey 所说,正因为不是引用传递,所以楼主的代码才无效
    LyleRockkk
        30
    LyleRockkk  
       2020-04-24 11:46:16 +08:00
    @ynohoahc em... 我说的引用传递,是我理解的,对引用类型的值的传递。 图里面书说的很准确,用的多了,容易有自己的说法,还好理解没跑偏
    ryncv
        31
    ryncv  
       2020-04-24 11:55:36 +08:00
    通常所说的 js 引用传递,是因为复杂类型的值其实就是引用啊。
    声明 const a = [1,2]; 数组 [1,2]被保存在堆内存,a 是一个引用地址。
    在调用时 function(a){},a 是一个引用的值没毛病啊。
    原来 a -> [0,1,2,3,4,5]
    关键原因还是 [list.slice 返回了一个新数组] 。函数内的 list=list.slice 后,list 指向了一个新的数组[1,2,3,4,5],所以原数组不变。
    不信试试下面这个:
    function test(list) { list = list.pop() }
    原来的 list 一定被改了。
    noe132
        32
    noe132  
       2020-04-24 12:00:45 +08:00
    variable holds the reference to the object

    function call test(a) pass the reference of a to parameter variable list

    a and list holds the same reference but they are different variable
    wutiantong
        33
    wutiantong  
       2020-04-24 12:03:12 +08:00
    @ryncv 把你的例子改成 function test(list) { list.pop() } 会好很多,不必要的那次赋值会让搞不懂的人更加困惑。
    leihongtao1230
        34
    leihongtao1230  
       2020-04-24 12:05:32 +08:00 via Android
    我来回答一下,对于函数的参数是复杂类型的,传递参数是复制引用传递,也就是把内存地址复制一份给参数,也就是你用这个地址修改原来的数据可以,但是你把这个参数重新赋值就不会影响原来的对象
    ryncv
        35
    ryncv  
       2020-04-24 12:09:44 +08:00
    @wutiantong 是的,感谢指正,写快直接把 lz 例子抄过来了。
    auroraccc
        36
    auroraccc  
       2020-04-24 12:19:05 +08:00
    a 和 list 都是指向[0,1,2,3,4,5]的指针,函数内部把 list 的指向[1, 2, 3, 4, 5],并不会更改 a
    isyuu
        37
    isyuu  
       2020-04-24 14:33:49 +08:00
    据我所知除了 C/C++的 应用传递(&), c#的 ref 关键字, 其他 java/js/python 这么写都没卵用吧, 你对多只是对 list 这个局部指针赋值而已...
    leoskey
        38
    leoskey  
       2020-04-24 14:45:01 +08:00
    @shintendo
    @Vegetable 这位解释的是正确的,a 与 list 是不同地址指向同一数组,对 list 赋值改变了 list 的地址
    yeqizhang
        39
    yeqizhang  
       2020-04-24 14:45:30 +08:00 via Android
    和 java 的差不多,以前我也干过去改变传入的对象的引用的事。
    你要是函数 return list,a=test(a)就好
    raistlin916
        40
    raistlin916  
       2020-04-24 15:11:59 +08:00
    slice 是生成新的,splice 是改原值。这里有困惑很正常,api 自己都没统一行为
    zhouS9
        41
    zhouS9  
       2020-04-24 15:14:00 +08:00
    list 和 a 指向同一个地址,使用 splice 这种方法,就会同时改变两者,但是 slice 不行
    AnnaXia
        42
    AnnaXia  
       2020-04-24 17:56:33 +08:00
    写了个解释希望有说清楚,v2ex 的回复不显示别人回答内容,看起来太累了。不 @楼上正确解答的朋友了

    a = [0,1,2,3,4,5] // a 指向数组 [0,1,2,3,4,5]
    function test(list) 并调用 test(a) // test 也指向数组 [0,1,2,3,4,5]
    list.slice(1) // slice 方法不改变原数组,生成一个新的数组 [1,2,3,4,5]
    list = list.slice(1) // list 指向改变,变为指向数组[1,2,3,4,5]
    test(a)后 console.log(a) // a 指向未变,原数组也没变,所以依然是 [0,1,2,3,4,5]

    楼里有人建议 splice 或 pop,这个方案可行是因为改变了原数组 [0,1,2,3,4,5],
    a 指向未变,原数组改变为[1,2,3,4,5],所以可以得到想要的结果。
    AnnaXia
        43
    AnnaXia  
       2020-04-24 17:58:53 +08:00
    @AnnaXia 啊,中间写错了,第 2 行是“list”也指向数组 [0,1,2,3,4,5]。

    本来注释有空格保持些距离的,忘了 V2EX 编辑框不是所见即所得了,难受,也不能撤回再编辑
    autoxbc
        44
    autoxbc  
       2020-04-24 18:58:03 +08:00
    JS 里只讲原始值和对象值,不讲指针,内存地址,乱用概念只会造成歧义和误解
    lewinlan
        45
    lewinlan  
       2020-04-25 00:28:15 +08:00 via Android
    没遇到过用 slice 拷贝数组的写法吗?
    特别是现在前端各种数据绑定设计,类似的拷贝用法应该很常见才对。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   858 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 21:39 · PVG 05:39 · LAX 13:39 · JFK 16:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.