以前在《JavaScript系列之this是什麼》這篇文章中,咱們曾談過"this"這個關鍵字,本章將會談到另外一個JavaScript中的關鍵字,叫作"arguments"。javascript
那arguments究竟是什麼呢?下面我將盡量地用簡潔明瞭的言語來描述 arguments。前端
在瞭解arguments以前,咱們必需要先了解一下什麼是參數(parameter)。參數其實就是咱們會帶入函數的變量,如下面例子來講,"house"、"car"、"money",就是咱們在執行函式的時候能夠任意填入的參數。java
function MyFavorite(house, car, money) {
console.log(house);
console.log(car);
console.log(money);
}
MyFavorite();
複製代碼
首先,當我創建好這樣的函數,我能夠不帶任何參數值就去執行這個函數,只要輸入MyFavorite()
這樣就能夠了!git
通常若是有參數卻又沒有給它參數值,函數的執行上每每會有錯誤!但在JavaScript中不太同樣的地方在於,即便你沒有給它任何參數值就加以執行,也不會報錯,而是會返回undefined
。github
爲何會獲得"undefined
"呢?面試
之因此會這樣是由於當JavaScript在執行這個函數的時候,因爲提高機制,它會先把咱們的參數(house
, car
, money
)存到內存中了,而且賦予它的值是undefined
。express
參數值會由左至右讀取,若是我依序執行這樣的代碼就理解了:編程
MyFavorite();
MyFavorite("別墅");
MyFavorite("別墅", "法拉利");
MyFavorite("別墅", "法拉利", "一億元");
複製代碼
會分別讀到如下的結果,表示JavaScript會由左至右來讀取參數值,並且即便某些參數值有缺值的狀況,JavaScript仍是能夠正常執行。數組
因爲目前的幾款瀏覽器使用的JavaScript版本都尚不支持直接在參數的地方設置預設值(ES6的將能夠),因此不少的框架都還不會用這種方式設置預設值。瀏覽器
方法一:在ES6的JavaScript中,能夠直接經過這種方式設置參數預設值:
function MyFavorite(house, car, money = '一億元') {
console.log(house);
console.log(car);
console.log(money);
console.log("----------------");
}
複製代碼
方法二:利用強制轉換的概念設置預設值
因爲版本兼容的差別,現今多數的編程都是使用這種方式設置參數的預設值,利用簡單的"="和"||"就能夠達到參數預設值的效果:
function MyFavorite(house, car, money) {
money = money || '一億元';
console.log(house);
console.log(car);
console.log(money);
console.log("----------------");
}
複製代碼
這時候即便在沒有給值的狀況下,money
同樣能夠獲得預設值爲"一億元":
瞭解了parameters的概念後,讓咱們回來談談arguments,MDN將它叫作類數組對象,那麼什麼是類數組對象呢?
所謂的類數組對象:
就是擁有一個
length
屬性和若干索引屬性的對象
舉個例子:
var array = ['house', 'car', 'money'];
var arrayLike = {
0: 'house',
1: 'car',
2: 'money',
length: 3
}
//讀取
console.log(array[0]); // house
console.log(arrayLike[0]); // house
array[0] = 'new house';
arrayLike[0] = 'new house';
//長度
console.log(array.length); // 3
console.log(arrayLike.length); // 3
//遍歷
for(var i = 0, len = array.length; i < len; i++) {
……
}
for(var i = 0, len = arrayLike.length; i < len; i++) {
……
}
複製代碼
上面咱們能夠看得出來,類數組對象與數組在讀取、獲取長度、遍歷三個方面同樣,都能取到,那爲何還叫作類數組對象呢?
由於類數組對象不可使用數組的方法,好比:
arrayLike.push('name');
複製代碼
然而上述代碼會報錯: arrayLike.push is not a function,因此終歸仍是類數組吶……
若是類數組想用數組的方法怎麼辦呢?
直接調用是不可取的,那咱們能夠經過 Function.call
的方法進行間接調用:
var arrayLike = {0: 'house', 1: 'car', 2: 'money', length: 3 }
console.log(Array.prototype.join.call(arrayLike, '&')); // house&car&money
console.log(Array.prototype.slice.call(arrayLike, 0)); // ["house", "car", "money"]
// slice能夠作到類數組轉數組
Array.prototype.map.call(arrayLike, function(item){
return item.toUpperCase();
});
// ["HOUSE", "CAR", "MONEY"]
複製代碼
在上面已經提到了一種類數組轉數組的方法,再補充三個:
var arrayLike = {0: 'house', 1: 'car', 2: 'money', length: 3 }
// 1. slice
console.log(Array.prototype.slice.call(arrayLike)); // ["house", "car", "money"]
// 2. splice
console.log(Array.prototype.splice.call(arrayLike, 0)); // ["house", "car", "money"]
// 3. ES6 Array.from
console.log(Array.from(arrayLike)); // ["house", "car", "money"]
// 4. apply
console.log(Array.prototype.concat.apply([], arrayLike));
複製代碼
接下來重點講講 Arguments 這個類數組對象。
arguments比起this來講,要容易理解的多,arguments對象只定義在函數體中,包括了函數的參數和其餘屬性。
一樣地經過上面的例子加以理解,咱們直接在函數中去打印出"arguments"這個關鍵字:
function MyFavorite(house, car, money) {
money = money || '一億元';
console.log(arguments);
console.log("----------------");
}
MyFavorite();
MyFavorite("別墅");
MyFavorite("別墅", "法拉利");
MyFavorite("別墅", "法拉利", "一億元");
複製代碼
打印結果以下:
咱們能夠看到除了類數組的索引屬性和length
屬性以外,還有一個callee
屬性,並且咱們能夠看到arguments對象的__ proto __
是指向object
的,這也說明了他是個類數組對象,而不是一個數組。下面咱們進行一一介紹。
arguments.length
爲函數實參個數,舉個例子:
function foo(house, car, money){
console.log("實參的長度爲:" + arguments.length)
}
console.log("形參的長度爲:" + foo.length)
foo(1)
// 形參的長度爲:3
// 實參的長度爲:1
複製代碼
每一個參數實例都有一個callee
屬性,經過它能夠調用函數自身。 ES5的嚴格模式不容許訪問arguments.callee
。
講個閉包經典面試題使用 callee
的解決方法:
var data = [];
for (var i = 0; i < 3; i++) {
(data[i] = function () {
console.log(arguments.callee.i)
}).i = i;
}
data[0]();
data[1]();
data[2]();
// 0
// 1
// 2
複製代碼
除了arguments這個關鍵字,在新版ES6的JavaScript中另外提供了一個展開運算符(spread),它就是「...
」三個點,這個...
有什麼用呢?
根據MDN對於展開運算符spread的描述以下:
The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.
簡單來講,就是它能夠把函數中許多的參數(arguments)或數組中許多的元素(elements)造成一個新的變量。
舉例來講:
在函數的部分,在參數的地方咱們用"...other
",other
是你想要儲存成的數組變量名稱,能夠本身取。
在執行函數的地方,本來咱們只有三個參數(house
, car
, money
),也只能填寫三個參數;但使用了展開運算符"...
"後,咱們在執行函數的地方就能夠帶入不僅三個參數(例如,我在最後面又加了"老婆"和"孩子"),這些多的參數值最後都會被放到other
這個數組當中。
function MyFavorite(house, car, money, ...other){
console.log(other);
console.log('What my favorite are' + house + ',' + car + ',' + money + ',' + other);
}
MyFavorite("別墅", "法拉利", "一億");
MyFavorite("別墅", "法拉利", "一億", "老婆", "孩子");
複製代碼
結果就會長的像這樣子:
展開運算符還有其餘的使用方式,像是把數組元素作鏈接等等,使用靈活的話至關方便,若是有須要的話,能夠參考引1。
引1:Microsoft Developer Netword:展開運算符(...)
1.利用arguments實現方法的重載
下面咱們利用arguments對象來實現一個參數相加的函數,不論傳入多少參數都行,將傳入的參數相加後返回。
function add() {
var len = arguments.length,
sum = 0;
for(;len--;){
sum += arguments[len];
}
return sum;
}
console.log( add(1,2,3) ); //6
console.log( add(1,3) ); //4
console.log( add(1,2,3,5,6) ); //17
複製代碼
因爲JS是一種弱類型的語言,沒有重載機制,當咱們重寫函數時,會將原來的函數直接覆蓋,這裏咱們能利用arguments,來判斷傳入的實參類型與數量進行不一樣的操做,而後返回不一樣的數值。
2.利用arguments.callee實現遞歸
先來看看日常咱們是怎麼實現遞歸的,這是一個結算階乘的函數。
function foo(num) {
if(num<=1) {
return 1;
}else {
return num * foo(num-1);
}
}
複製代碼
可是當這個函數變成了一個匿名函數時,咱們就能夠利用callee
來遞歸這個函數。
function foo(num) {
if(num<=1) {
return 1;
}else {
return num * arguments.callee(num-1);
}
}
複製代碼
這個方法雖然好用,可是有一點值得注意,ECMAScript4中爲了限制JS的靈活度,讓JS變得嚴格,新增了嚴格模式,在嚴格模式中咱們被禁止不使用var
來直接聲明一個全局變量,固然這不是重點,重點是arguments.callee
這個屬性也被禁止了。不過這都不是事兒,ES6爲咱們新增了不少好用的變量聲明方式和新的語法糖,做爲一個時髦的前端,咱們趕忙學習一些ES6的新語法吧。
若是以爲文章對你有些許幫助,歡迎在個人GitHub博客點贊和關注,感激涕零!