关于 js 函数返回值的一个小问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ynohoahc
V2EX    问与答

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

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

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

    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     Solana     998 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 19:07 PVG 03:07 LAX 12:07 JFK 15:07
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86