函數(shù)參數(shù)傳遞的過程實(shí)際上就是實(shí)參像形參復(fù)制值的過程。
- 在向參數(shù)傳遞基本類型的值時(shí),被傳遞(實(shí)參)的值會(huì)復(fù)制給一個(gè)局部變量(形參),形參值的變化不會(huì)對函數(shù)外的實(shí)參產(chǎn)生影響。
- 在向參數(shù)傳遞引用類型的值時(shí),會(huì)把這個(gè)值在內(nèi)存中的地址復(fù)制給形參。這時(shí)這個(gè)形參也指向了函數(shù)外的實(shí)參,因此這個(gè)形參的變化也會(huì)導(dǎo)致實(shí)參的變化。
function addTen(num) {
num += 10;
return num;
}
var count = 20, result = addTen(count);
alert(count); //20
這里函數(shù) addTen()的參數(shù)num,實(shí)際上是函數(shù)的局部變量。在調(diào)用函數(shù)時(shí),變量count作為參數(shù)傳遞給函數(shù)。由于 count 的值是20,所以數(shù)值20被復(fù)制給參數(shù)num。在函數(shù)內(nèi)部,這個(gè)參數(shù)被加了10,但這并不會(huì)影響函數(shù)外部的count變量。
當(dāng)向參數(shù)傳遞的值為對象時(shí),例如:
function setName(obj) {
obj.name = "Nicholas";
}
var person = new Object();
person.name = "Greg";
setName(person);
alert(person.name); //"Nicholas"
這里首先是創(chuàng)建了一個(gè)對象,保存在變量person中,并且給變量的 name屬性賦值為 "Greg"。然后這個(gè)變量被當(dāng)作參數(shù)傳遞給函數(shù)setName的參數(shù) obj。在函數(shù)內(nèi)部,obj和 person 指向同一個(gè)對象,因?yàn)閭鬟f的是對象的地址。所以給obj的 name屬性賦值后,也會(huì)改變 person 的name屬性值。
如果在函數(shù)內(nèi)部為obj新建一個(gè)對象實(shí)例,這個(gè)新對象實(shí)例會(huì)開辟新的內(nèi)存空間,導(dǎo)致 obj的地址和person不同。此時(shí),obj和 person 將指向兩個(gè)不同的對象,所以互不影響。例如:
function setName(obj) {
obj.name = "Nicholas"; // 這個(gè)obj和person指向的地址相同,即函數(shù)外person創(chuàng)建的對象。
obj = new Object(); // 新建實(shí)例對象,導(dǎo)致obj指向另一個(gè)地址
obj.name = "Greg";
}
var person = new Object();
person.name = "Jhon";
setName(person);
alert(person.name); //"Nicholas"
全局上下文中的變量對象
全局對象(Global object) 是在進(jìn)入任何執(zhí)行上下文之前就已經(jīng)創(chuàng)建了的對象;這個(gè)對象只存在一份,它的屬性在程序中任何地方都可以訪問,全局對象的生命周期終止于程序退出那一刻。
全局對象初始創(chuàng)建階段將Math、String、Date、parseInt 作為自身屬性,等屬性初始化,同樣也可以有額外創(chuàng)建的其它對象作為屬性(其可以指向到全局對象自身)。例如,在DOM中,全局對象的window屬性就可以引用全局對象自身(當(dāng)然,并不是所有的具體實(shí)現(xiàn)都是這樣):
global = {
Math: <...>,
String: <...>
...
...
window: global //引用自身
};
函數(shù)上下文中的變量對象
在函數(shù)執(zhí)行上下文中,變量對象(VO)是不能直接訪問的,此時(shí)由活動(dòng)對象(activation object,縮寫為AO)扮演 VO 的角色。
活動(dòng)對象是在進(jìn)入函數(shù)上下文時(shí)刻被創(chuàng)建的,它通過函數(shù)的arguments屬性初始化。arguments屬性的值是 Arguments對象:
Arguments 對象是活動(dòng)對象的一個(gè)屬性,它包括如下屬性:
-
callee:指向當(dāng)前函數(shù)的引用; -
length: 真正傳遞的參數(shù)個(gè)數(shù); -
properties-indexes(字符串類型的整數(shù)): 屬性的值就是函數(shù)的參數(shù)值(按參數(shù)列表從左到右排列)。properties-indexes內(nèi)部元素的個(gè)數(shù)等于arguments.length,properties-indexes的值和實(shí)際傳遞進(jìn)來的參數(shù)之間是共享的。
function foo(x, y, z) {
// 聲明的函數(shù)參數(shù)數(shù)量arguments (x, y, z)
alert(foo.length); // 3
// 真正傳進(jìn)來的參數(shù)個(gè)數(shù)(only x, y)
alert(arguments.length); // 2
// 參數(shù)的callee是函數(shù)自身
alert(arguments.callee === foo); // true
// 參數(shù)共享
alert(x === arguments[0]); // true
alert(x); // 10
arguments[0] = 20;
alert(x); // 20
x = 30;
alert(arguments[0]); // 30
// 不過,沒有傳進(jìn)來的參數(shù)z,和參數(shù)的第3個(gè)索引值是不共享的
z = 40;
alert(arguments[2]); // undefined
arguments[2] = 50;
alert(z); // 40
}
foo(10, 20);
現(xiàn)在在嚴(yán)格模式下,
arguments對象已與過往不同。arguments不再與函數(shù)的實(shí)際形參之間共享,同時(shí)caller屬性也被移除。
剩余參數(shù)、默認(rèn)參數(shù)和解構(gòu)賦值參數(shù)
在嚴(yán)格模式下,剩余參數(shù)、默認(rèn)參數(shù)和解構(gòu)賦值參數(shù)的存在不會(huì)改變 arguments對象的行為,但是在非嚴(yán)格模式下就有所不同了。
當(dāng)非嚴(yán)格模式中的函數(shù)沒有包含剩余參數(shù)、默認(rèn)參數(shù)和解構(gòu)賦值,那么arguments對象中的值會(huì)跟蹤參數(shù)的值(反之亦然)??聪旅娴拇a:
function func(a) {
arguments[0] = 99; // 更新了arguments[0] 同樣更新了a
console.log(a);
}
func(10); // 99
并且
function func(a) {
a = 99; // 更新了a 同樣更新了arguments[0]
console.log(arguments[0]);
}
func(10); // 99
當(dāng)非嚴(yán)格模式中的函數(shù)有包含剩余參數(shù)、默認(rèn)參數(shù)和解構(gòu)賦值,那么arguments對象中的值不會(huì)跟蹤參數(shù)的值(反之亦然)??聪旅娴拇a:
function func(a = 55) {
a = 99; // updating a does not also update arguments[0]
console.log(arguments[0]);
}
func(10); // 10
并且
function func(a = 55) {
console.log(arguments[0]);
}
func(); // undefined
delete
關(guān)于變量,還有一個(gè)重要的知識(shí)點(diǎn)。變量相對于簡單屬性來說,變量有一個(gè)特性(attribute):{ DontDelete },這個(gè)特性的含義就是不能用 delete 操作符直接刪除變量屬性。
a = 10;
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined
var b = 20;
alert(window.b); // 20
alert(delete b); // false
alert(window.b); // still 20
但是這個(gè)規(guī)則在有個(gè)上下文里不起走樣,那就是
eval上下文,變量沒有 {DontDelete} 特性。
eval('var a = 10;');
alert(window.a); // 10
alert(delete a); // true
alert(window.a); // undefined