0%

JS核心-(35)-函式以及This的運作-參數

前言

這篇文章會對參數的各種狀態進行更詳細的介紹

函式在運行之前,會帶入哪些參數

var globalVariables = '全域變數';
var obj = {
    afunction: function (para) {
        var localVariables = '區域變數';
        console.log(para, localVariables, arguments, this, globalVariables);
    }
};

// 執行
obj.afunction('一段描述', 2, 3);

這段程式碼在執行的時候帶入的參數是3個,分別是 '一段描述'23 ,但接受的 function 只有 para

一個參數,也就是說 para 這個參數只會對應到 '一段描述' 這個字串,其他的23 沒有傳進去。

我們來看一下執行的結果

我們可以看到 localVariables 以及 globalVariables 都有對應到正確的值,而全域變數之前的章節也有討論過,如果找不到就

會向外尋找直到找到為止,不然就回傳 not defined

this 的部分後面會再詳細的討論,這裡就先跳過。

重點是又多了一個 arguments 的變數,而且他好像是一個陣列,裝著 '一段描述', 2, 3 的三個值。

但其實這個 arguments 被稱作 「類陣列」 ,但其實他不是陣列。雖然它可以被 for 迴圈處理內部的資料,但對於大部分陣列操

作的指令他都無法執行。


Hoisting 影響參數的運行

function callName (a) {
    console.log(a); // 小明
}

callName('小明');

上述執行的結果很明顯答案就是 小明

那麼如果改成下面這樣呢?

function callName (a) {
    console.log(a); // 小明

    var a;
    console.log(a); // 小明
}

callName('小明');

執行起來也還是 小明 喔!原因是因為,我們再宣告一個變數的時候,這個變數名稱已經存在的時候,這個宣告就是無效的,

所以 var a; 是無效的,自然 a 就還是保持在 小明 的狀態。

那接下來我們把 a 改成 '杰倫' 呢?

function callName (a) {
    console.log(a); // 小明

    var a;
    console.log(a); // 小明

    a = '杰倫';
    console.log(a); // 杰倫
}

callName('小明');

這時候就會是 '杰倫' 了,所以要改變內部的參數,直接覆蓋就可以了!

如果我今天在裡面定義的是函式陳述式呢?

根據之前學過的觀念是,函式陳述式會最早被寫入在記憶體空間中。我們來實際操作看看

function callName (a) {
    console.log(a);

    function a () {}

    var a;
    console.log(a);

    a = '杰倫';
    console.log(a);
}

callName('小明');

很明顯在 a 還沒被 '杰倫' 覆蓋之前,傳入的 '小明' 就被函式陳述式給附蓋了,所以雖然函式陳述式會最早被寫入在記憶體

空間中,但最早是指在這個函式內的 { } 的最早,因此函式陳述式並不會比 '小明' 這個參數還早,所以顯示的結果才是函式

陳述式。


定義的參數名稱的規則

定義函式接收參數名稱的時候,注意的只有參數的數量,參數的名稱都是可以自定義的,只要不要重複就好。

我們接下來來看看下面的例子

function callMore (d,c,b,a){
    console.log(d,c,b,a);
}
var a = 'a';
var b = 'b';
var c = 'c';
callMore(a, b ,c);

目前雖然傳了 3 個參數進去,也就是 callMore(a, b ,c); ,但函式接受的參數定義是 4 個,

也就是 function callMore (d,c,b,a) ,因此 function callMore (d,c,b,a) 最後一個 a

就會在函式內被定義為 undefined


傳入參數是物件類型的資料,依然會維持傳參考的特性

function callObject (obj) {
    obj.name = '杰倫家';
}
var family = {
    name: '小明家'
}
callObject(family);
console.log(family);

上面這個範例,執行後的結果很明顯就是 family.name 會被改成 '杰倫家'

這邊也要提醒一下,盡量避免傳入物件類型的資料後,又在函是內部修改資料內容,避免不預期的結果。當然也是有人利用這種

特性進行撰寫,但其實這樣的撰寫風格不易追蹤,維護成本也較高,因此不太建議這麼寫。


CallBack Function

function FnB (fn) {
    fn('小明');
}

FnB(function (a) {
    console.log(a);
});

這樣的函式直接執行,我們就先把參數預定在 function FnB 中,等到執行的時候,傳入ㄧ個 function

FnB (function( ){ }),而 FnB (function( ){ }) 就會接收到 function FnB 中的參數,並做ㄧ些處理。

我們也可以改寫成

function callSomeone (name) {
    console.log(name + '你好');
}
function FnB (fn) {
    fn('小明');
}
FnB(callSomeone);

這樣的寫法,我們把 callSomeone 賦予到 FnB 的參數 fn上。

我們是透過函式表達式的方式傳入到 FnBfunction 中,因此真正執行的地方是在 callSomeonefunction 裡面,

而這樣呼叫的方法呢,我們又稱作 callback function


類陣列 Arguments

剛剛有說到 arguments 是一個類陣列,每個函式在運行的時候都會有,他不需要特別宣告就可以取用,他會接受所有傳進來的

值,並以一個類似陣列的結構乘裝著,但他不是真正的陣列。

function callArg (a) {
    console.log(a, arguments);
}
callArg(1,2,3,'a','5');

我們可以對這個 argumentsfor 迴圈 的取值動作

function callArg (a) {
    console.log(a, arguments);

    for (var i = 0; i < arguments.length; i ++) {
        console.log(arguments[i]);
    }
}
callArg(1,2,3,'a','5');

但我們就沒辦法對她使用 forEach 的方法

function callArg (a) {
    console.log(a, arguments);

    arguments.forEach(function () {});
}
callArg(1,2,3,'a','5');

關於甚麼是類陣列,在這個章節還不需要先學習。

只要知道 arguments 是在函式運行的時候自帶的變數,並且會乘裝ㄧ切傳入的值,這樣就可以了。