今天在写代码(见这里)的过程中,在一个验证表单的问题上,对于函数传参的过程发生了一点混乱,这引发了我对函数传参过程的思考
1. 首先理解什么是参数
ECMAScript函数的参数与大多数其它语言中函数的参数有所不同。ECMAScript函数不介意传递进来多少个参数,也不在乎传进来参数是什么数据类型。也就是说,即便你定义的函数只接收两个参数, 在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不会有什么怨言,之所以会这样,原因是ECMAScript中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数。在函数体内可以通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数。
2. 数据类型
ECMAScript规定了函数有6种数据类型,number, string, boolean, undefined, null, object , ES6新增了symbol类型,object是复杂类型,其余均为基本类型,object又包含function,与array类型。
在声明变量时,
基本类型的值储存在栈(stack)内存的简单数据段中,
复杂类型的值储存在堆(heap)中的对象,也就是说,储存在变量的值是一个指针,指向储存变量的地址,这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响。
在javascript中在访问一个对象时,首先访问这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,即按引用访问。而原始类型的值则是可以直接访问到的。
3. 传递参数
《JavaScript高级程序设计》中 ,明确参数传递是按值传递的,但在涉及基本类型和复杂类有所不同,这是由于内存分配而导致的。
举个例子
function changeStuff(a, b, c)
{
a = a * 10;
b.item = "changed";
c = {item: "changed"};
}
var num = 10;
var obj1 = {item: "unchanged"};
var obj2 = {item: "unchanged"};
changeStuff(num, obj1, obj2);
console.log(num); // 10
console.log(obj1.item); // "changed"
console.log(obj2.item); // "unchanged"
如图:
num自己的值10传递给参数a,之后两者互不影响,如果参数按引用传递,那么num应该为100。obj1将自己的地址传递给b,此时b指向obj1,由于b修改了对象内部的属性,使指向的对象发生了改变,因此obj1发生了改变,同理c也指向obj2,但由于在函数内部给这个参数c赋值为另一个对象,此时c与obj2互相独立,互不干扰,因此obj2为发生改变。
因此如果在函数内部给这个参数赋值另一个对象时,这个参数就会更改它的值为新对象的内存地址指向新的对象,但此时原来的变量仍然指向原来的对象,这时候他们是相互独立的;但如果这个参数是改变对象内部的属性的话,这个改变会体现在外部,因为他们共同指向的这个对象被修改了。
另一个例子:
function setName(obj){
obj.name = 'Nicholas';
obj = new Object();
obj.name = 'Grey'
}
var person = {name: 'plainnany'};
setName(person);
console.log(person.name) // 'Nicholas'
本例中,在把person传递给setName()后,其name属性被设为"Nicholas"。然后,又将一个新对象赋值给变量obj,同时将name属性设置为"Grey",如果person是按引用传递的,那么person就会自动被修改为指向其name属性值为"Grey"的新对象,但是person.name显示的值仍然是"Nicholas"。这说明即使在函数内部修改了参数的值,但原始的引用仍未改变。实际上,当在函数内部重写obj时,这个变量引用就是一个局部对象了,而这个局部对象会在函数执行完毕后立即被销毁。
参考文章:
javascript传递参数如果是object的话,是按值传递还是按引用传递?
Is JavaScript a pass-by-reference or pass-by-value language?
《Javascript高级程序设计》第四章