形参与实参

    今天遇到了一个问题,以前自己以为形参是不占内存的,并且以为形参就是跟实参保持一致,今天经过了一下系统分析后,才发现自己原先的认识真实太浅薄了,下面由一个小demo引出问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj1 = {
foo: 'bar'
};

var obj2 = obj1;
function changeObj(o) {
o = 1;
}

changeObj(obj2);

console.log(obj1); // => { foo: 'bar' }
console.log(obj2); // => { foo: 'bar' }

    是不是有些人对这个结果很诧异,以为为什么控制台输出的obj2还是原先的{ foo: ‘bar’ }而不是1呢,我刚一开始也认为错了,有些时候认识到错误之后,及时去改正即可,所以接下来我就查阅了下相关资料,但是发现也都是写的不太全面,索性自己记录下来,到时候翻阅起来也方便,好了,下面开始分析过程:
    其实对于上面的函数调用( changeObj(obj2))来说,首先在函数内部会进行一下这个操作
function changeObj(o) {
    o = obj2
    o = 1;
}
也就是只要一进去函数内部,首要进行的是形参等于实参, 在这里obj2是引用类型,所以等于将obj2的引用(这里是也可以用地址做个比喻,就是实际不存储,只是有一个指向而已,其实并不严谨,但是现在大家可以先这样理解)传给了o,此时o也指向了obj2,这时候如果对o进行一些添加属性或方法的操作,都会体现到obj2所指向的对象上面,下面会列上详细代码,先说这个,是直接对o进行了一次赋值操作,这时候等于是切断了o与obj2的联系,所以obj2并未改变;
再对上面的demo稍加修改,变式1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var obj1 = {
foo: 'bar'
};

var obj2 = obj1;
function changeObj(o) {
o.foo = 'baz';
o = {};
}

changeObj(obj2);

console.log(obj1); // => { foo: 'baz' }
console.log(obj2); // => { foo: 'baz' }

    此时通过o.foo = ‘baz’;的修改对obj1和obj2都得到了体现,这个不难理解,因为对于obj1 = obj2这一步来说,本身变量值存储引用类型的引用,所以经历这一步解析之后,obj1和obj2会指向同一个对象,也可以说是同一块内存,那通过changeObj(obj2)之后,这个函数内部的解析在上面已经提及,在o还未赋值之前,对o进行的属性操作都会体现在obj2上,因为在调用的函数内部,o和obj2指向同一个对象,当然obj1也会跟着改变;
再对上面的demo稍加修改,变式2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var obj1 = {
foo: 'bar'
};

var obj2 = obj1;
function changeObj(o) {
o = {};
o.foo = 'baz';

}

changeObj(obj2);

console.log(obj1); // => { foo: 'bar' }
console.log(obj2); // => { foo: 'bar' }

这次只是让changeobj内部o的赋值操作放到了上面,这次再对o进行的属性操作并未对obj1和obj2产生影响,因为在解析完 o={} 之后o和obj2的引用链就被切断了,也可以说接下来o进行的任何操作都与obj2无关了,所以控制台输出的结果也正印证了;

    综上所述,总结出以下几点:

  1. 没有调用函数时,形参是不占内存的。调用函数时,会给形参分配内存单元,在调用结束时,即刻释放所分配的内存单元。也就是说形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。
  2. 实参出现在主调函数中,进入被调函数后,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。 因此应提前用赋值,输入等办法使实参获得确定值。另外实参占用的内存直到程序结束释放。
  3. 使用值传递时,实参和形参是各自独立的。一进入函数进行的 o = obj2 这样是拷贝了obj2的值给o,这时o 与 obj2占用两个不同的内存单元;而使用 引用传递时,一进入函数进行的 o = obj2 这样实际上是拷贝了obj2的引用(地址)给o,这时o 与 obj2指向同一个对象(内存)。
  4. 以前我们都没有注意到形参,以为形参不占内存,现在回过头来看,其实我们对于传的形参都是为了使用它而不是为了改变它的,所以没有出现一些错误,当试图改变的时候,就不一样了
    ,这时候只要你一旦对这个形参进行了赋值操作,不管是简单数据类型还是复杂数据类型,都不会对传进去的对象产生作用了,所以以后在使用函数时千万不要在内部对形参进行赋值操作!