JavaScript奇葩的参数机制

今天实习生遇到一个问题,以前没碰到过,笔记一下。

今天实习生遇到一个问题:有一个数组,想在一个函数里将它清空,结果办不到。代码大概是这样的:

function empty(arr) {
  arr = [];
}
var array = [1, 2];
empty(array);
console.log(array); // 预期 [],实际 [1, 2]

对于我这种半路出家根基不实的人来说面对这种问题总是很挠头。于是只有翻书,在《JavaScript高级程序设计(第三版)》上看到,原来JavaScript的函数参数设计这么奇葩。

首先,JS里的变量分为值类型和引用类型,这点我是知道的。基础类型只有NaN、null、undefined、String、Number这5个,其他都是引用类型,也就是赋值时传引用不复制值的类型。区分如下:

// 值类型
var a = 'test',
    b = a;
b = 'temp';
console.log(a); // 'test'

// 引用类型
var a = {id: 1},
    b = a;
b.id = 2;
console.log(a.id); // 2

但是在用在函数参数的时候,又会有所不同。参数并不是传递的引用,而是传递的引用的引用。所以即使用“===”判断,也会返回true,因为最终指向的对象是一样的。但是如果在参数中对参数重新赋值的话,就相当于改变了引用地址,重新创建了一个对象,也就无法操作外面的对象了。这可能也是“运行时环境对象”造成的结果吧。

作者: meathill

爱编程,爱旅游,爱吐槽。
今年的第一目标是成为一名优秀的讲师,做够 25 场直播,收集 1000 位听众! (12/25)
《Electron + Vue 实战开发》创作中……

《JavaScript奇葩的参数机制》有11个想法

  1. function empty(arr) {
    arr = []; //这里你误解了,实际上这里会变成 var arr = []; //原参数arr己经被你覆盖了
    }

    1. 这样说一样有问题啊。按理说应该从当前环境开始寻找变量,找不到就往上层继续找,直到全局情况下都找不到变量,才会var arr = []。
      而这里明显是有arr存在的。

      1. 例如下面代码,很像”引用计数”方式吧..
        var a=[2,3,5];
        var b=a;
        a=999;
        b[1]=666;
        alert(a);//999
        alert(b); //[2,666,5]

        //js所有东西都是对象,函数的参数不管是字符还是数组都不会传值,传递的总是对象,对象就是类似引用之类的东西吧
        //而函数里面参数和变量之间的关系有点奇葩.
        function sb(i)
        {//var i=i; //这里展示了函数的参数与变量之间的关系
        i=168; //所以这样赋值的话,变量i和参数i己经完全不相干了..
        }

    1. 我觉得你比较奇葩,大多数书籍都会说,函数参数只有字符和整数是传值,其它是传址.
      既然是传址,那么通过改变函数参数以实现改变外部变量,这情况对低级语言是常有的事,例如c语言.又或者php,c#等

      另外,c语言若要传址,得在参数前加&符号,但js根本就没有类似这样的符号,也没有相似的实现方式,那为什么还偏要说js函数参数可以是传址?

      先入为主的观念很严重,传统的传址传值并不适合用来描述js.

      js是一门语言,而实现该语言并不只有微软一家,别忘记还有谷哥的v8引擎.firefox 有 J什么monkey

      “函数参数字符是传值,字符类型变量间传递也是传值?”,都不知道是哪个年代的js引擎,如此低效率的传递方式恐怕只有十年前的ie6了吧.

      写时复制,引用计数等等概念都是用于提升脚本语言的效率.

      1. 楼上这位大师太高深了,我读了3遍没读懂你想表达啥意思,比如引用计数不是gc里作用的么……
        建议直接放代码吧,比如gist

        1. 我的意思是说,书籍是学习的途径,但并不全是正确.
          另外,时间的行走会改变一些旧技术或理念.
          var a=’girl’; var b=a; 有些书上会说这是传值,而不是传址.
          但我告诉你,大多数主流js引擎不会那么蠢.
          从几G内存中找出几KB未被使用的内存空间,这很废时间在某些情况下会很浪费内存,说白了效率低下,这是传值概念.
          主流js引擎都是传址,而不是传值.
          传址时一般会使用引用计数,浅复制等技术概念,这些概念会帮助gc更好的,更有效率的管理内存.(这句话人尽皆知,也许我没必须提及,现在有点后悔)

          所以说,主流js引擎都是传址,但是”传址”这种古老说法用在js这种语言身上,很容易让人迷惑,因为js并不完全遵循传统的”传址”理念.所以有人会说:js既不传址也不传值,传递的理”对象”.

          说了那么多,我只是想否定一些旧书籍里某些说法:
          有些书说函数参数除了obj会传址外,其它全是传值.这种说法在很久以前是正确的,现在,则不是.

          1. 哦,明白了。你的意思是如今的主流js引擎是传址,然后在传址的基础上,对某些类型的变量重新赋值时,可能会给它新的对象,也可能会修改它的值,模拟以前简单的传址传值做法。对吧?

            听起来很棒,但似乎不影响立文啊,因为最终表现是一样的,还是那么奇葩

  2. 原来那个相当于
    var a = [1,2,3],
    b = a;
    a = [4,5,6];

    console.log(a) //[4,5,6]
    console.log(b) //[1,2,3]
    直接将值赋给a是不行的,原来的数组还是原来那个。

    如果这样:
    var a = [1,2,3],
    b = a;
    a[1] = ‘hehe’;

    console.log(a) //[1,’hehe’,3]
    console.log(b) //[1,’hehe’,3]
    对属性操作是可以的

    所以这样写就行了:
    function empty( target ){
    target.length = 0;
    }
    var a = [1,2,3];
    empty(a);
    console.log(a) //[]

    //为了验证a有没有真的清空
    a.length = 3;
    console.log(a) //[undefined,undefined,undefined],清空

欢迎吐槽,请勿装死