js函式的隱式arguments
1.1 背景
JavaScript 允許函式在呼叫時傳入的實參個數和函式定義時的形參個數不一致, 比如函式在定義時聲明瞭 n 個引數, 在呼叫函式時不一定非要傳入 n 個引數,例如:
1 2 3 4 5 |
// 1. 定義有一個形參的函式fn()
function fn(arg){}
// 2. 在呼叫時傳入 0 個或 多個引數,並不會報錯
fn(); // 傳入 0 個引數
fn(1, 'a' ,3); // 傳入多個引數
|
1.2 arguments 與 形參的對應關係
arguments是個類陣列結構,它儲存了函式在呼叫時傳入的所有實參, 通過訪問它的length屬性可以得到其中儲存的實參的個數,並可以通過arguments[n]按順序取出傳入的每個引數(n=1,2,..,arguments.length-1)。
引數在arguments中儲存的順序和傳入的順序相同, 同時也和形參宣告的順序相同,例如:
1 2 3 4 5 6 |
function fn(arg1, arg2, arg3){
console.log(arg1 === arguments[0]); // true
console.log(arg2 === arguments[1]); // true
console.log(arg3 === arguments[2]); // true
}
fn(1,2,3); // 呼叫
|
當傳入的實參多於形參個數時,想要獲得多餘出的實參,就可以用arguments[n]來獲取了, 例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 定義只有一個形參的函式
function fn(arg1){
console.log( 'length of arguments is:' ,arguments.length);
console.log( 'arguments[0] is:' , arguments[0]); // 獲取傳入的第一個實參, 也就是形參 arg1 的值
console.log( 'arguments[1] is:' , arguments[1]); // 獲取第二個實參的值, 沒有形參與其對應
console.log( 'arguments[2] is:' , arguments[2]); // 獲取第二個實參的值, 沒有形參與其對應 }
fn(1,2,3); // 傳入 3 個實參
// 可以得到實際上傳入的實參的個數並取出所有實參
// length of arguments is: 3
// arguments[0] is: 1
// arguments[1] is: 2
// arguments[2] is: 3
|
1.3 arguments 與 形參的值相互對應
在非嚴格模式下, 修改arguments中的元素值會修改對應的形參值;同樣的,修改形參的值也會修改對應的arguments中儲存的值。下面的實驗可以說明:
1 2 3 4 5 6 7 8 9 10 11 |
function fn(arg1, arg2){
// 1. 修改arguments元素,對應的形參也會被修改
arguments[0] = '修改了arguments' ;
console.log(arg1);
// 2. 修改形參值,對應的arguments也會被修改
arg2 = '修改了形參值' ;
console.log(arguments[1]);
}
fn(1,2);
// '修改了arguments'
// '修改了形參值'
|
但是,在嚴格模式下不存在這種情況, 嚴格模式下的arguments和形參的值之間失去了對應的關係:
1 2 3 4 5 6 7 8 9 10 11 12 |
'use strict' ; // 啟用嚴格模式
function fn(arg1, arg2){
// 修改arguments元素,對應的形參也會被修改
arguments[0] = '修改了arguments' ;
console.log(arg1);
// 修改形參值,對應的arguments也會被修改
arg2 = '修改了形參值' ;
console.log(arguments[1]);
}
fn(1,2);
// 1
// 2
|
注意: arguments 的行為和屬性雖然很像陣列, 但它並不是陣列,只是一種類陣列結構:
1 2 3 4 5 |
function fn(){
console.log( typeof arguments); // object
console.log(arguments instanceof Array); // false
}
fn();
|
1.4 為什麼要了解 arguments
在ES6中, 可以用靈活性更強的解構的方式(...符號)獲得函式呼叫時傳入的實參,而且通過這種方式獲得的實參是儲存在真正的陣列中的,例如:
1 2 3 4 5 6 7 |
function fn(...args){ // 通過解構的方式得到實參
console.log(args instanceof Array); // args 是真正的陣列
console.log(args); // 而且 args 中也儲存了傳入的實參
}
fn(1,2,3);
// true
// Array(3) [1, 2, 3]
|
那麼在有了上面這種更加靈活的方式以後,為什麼還要了解arguments呢? 原因是在維護老程式碼的時候可能不得不用到它。
2. 函式上下文: this
在函式呼叫時, 函式體內也可以訪問到 this 引數, 它代表了和函式呼叫相關聯的物件,被稱為函式上下文。
this的指向受到函式呼叫方式的影響, 而函式的呼叫方式可以分成以下4種:
- 直接呼叫, 例如: fn()
- 作為物件的方法被呼叫, 例如: obj.fn()
- 被當做一個建構函式來使用, 例如: new Fn()
- 通過函式 call() 或者 apply() 呼叫, 例如: obj.apply(fn) / obj.call(fn)
下面分別討論以上 4 種呼叫方式下 this 的指向.
2.1 直接呼叫一個函式時 this 的指向
有些資料說在直接呼叫一個函式時, 這個函式的 this 指向 window, 這種說法是片面的, 只有在非嚴格模式下而且是瀏覽器環境下才成立, 更準確的說法是:在非嚴格模式下, this值會指向全域性上下文(例如在瀏覽器中是window, Node.js環境下是global)。而在嚴格模式下, this 的值是 undefined。實驗程式碼如下:
1 2 3 4 5 |
// 非嚴格模式
function fn(){
console.log( this );
}
fn(); // global || Window
|
嚴格模式下:
1 2 3 4 5 |
'use strict' ;
function fn(){
console.log( this );
}
fn(); // undefined
|
總結: 在直接呼叫一個函式時, 它的 this 指向分成兩種情況: 在非嚴格模式下指向全域性上下文, 在嚴格模式下指向 undefined.