分类
js

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

爱编程,爱旅游,爱吐槽。
今年的目标是完成并运营至少一个 Side Project。
《Electron + Vue 实战开发》龟速创作中……

“JavaScript奇葩的参数机制”上的11条回复

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

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

例如下面代码,很像”引用计数”方式吧..
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己经完全不相干了..
}

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

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

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

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

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

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

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

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

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

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

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

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

原来那个相当于
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],清空

欢迎吐槽,请勿装死

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据