函式也是物件,使物件不同於其它物件的決定性特點是函式存在一個被稱為 [[Call]]
的內部屬性。
類似於[[Call]]
這種內部屬性無法通過程式碼訪問,他只是定義了程式碼執行時的行為。
ECMAScript為JavaScript的物件定義了多種內部屬性,這些內部屬性都用雙重中括號來標註。
[[Call]] 屬性是函式獨有的,表明該物件可以被執行。由於僅函式擁有該屬性,ECMAScript 定義 typeof 操作符對任何具有 [[Call]] 屬性的物件返回 "function"。
過去因某些瀏覽器曾在正規表示式中包含 [[Call]]
屬性,導致正規表示式被錯誤鑑別為函式。
2.1 宣告還是表示式
函式有兩種字面形式:函式宣告跟函式表示式。
兩者的一個重要區別是:函式宣告會被提升至上下文(要麼是該函式被宣告時所在的函式範圍,要麼是全域性範圍)的頂部。
2.2 函式就是值
函式是JavaScript的一大重點。
基本上只要是可以使用其他引用值的地方,你就可以使用函式。也就是說,你可以像使用物件一樣使用函式(因為函式本來就是物件)。
2.3 引數
JavaScript函式另一個獨特之處在於你可以給函式傳遞任意數量的引數卻不造成錯誤。
這是因為函式引數儲存在類陣列物件 arguments
中。
如同一個普通JavaScript陣列,arguments可以自由增長來包含任意個數的值,這些值可通過數字索引來引用。
雖然arguments類似陣列,但是Array.isArray(arguments)
返回 false
最後需要注意兩點:
- arguments物件自動存在於函式中,也就是說,函式的命名引數不過是為了方便,並不真的限制了該函式可接受引數的個數。
- 但JavaScript也沒忽視那些命名引數,函式的
length
屬性表明其期望的引數個數。
2.4 過載
大多數面嚮物件語言支援函式過載,它能讓一個函式具有多個簽名。函式簽名由函式的名字、引數的個數及其型別組成。
而 JavaScript 可以接收任意數量的引數且引數型別完全沒有限制。這說明 JavaScript 函式根本就沒有簽名,因此也不存在過載。
function sayMessage(message){
console.log(message);
}
function sayMessage(){
console.log("Default Message");
}
sayMessage("Hello!"); // 輸出"Default Message";
複製程式碼
在 JavaScript 裡,當你試圖定義多個同名的函式時,只有最後的定義有效,之前的函式宣告被完全刪除(函式也是物件,變數只是存指標)。
var sayMessage = new Function("message", "console.log(message)");
var sayMessage = new Function("console.log(\"Default Message\");");
sayMessage("Hello!");
複製程式碼
當然,你可以根據傳入引數的數量來模仿過載。
2.5 物件方法
物件的值是函式,則該屬性被稱為方法。
2.5.1 this 物件
JavaScript 所有的函式作用域內都有一個 this
物件代表呼叫該函式的物件。
在全域性作用域中,this
代表全域性物件(瀏覽器裡的 window)。
當一個函式作為物件的方法呼叫時,預設 this
的值等於該物件。
this在函式呼叫時才被設定。
function sayNameForAll(){
console.log(this.name);
}
var person1 = {
name: "Nicholas",
sayName: sayNameForAll
}
var name = "Jack";
person1.sayName(); // 輸出 "Nicholas"
sayNameforAll(); // 輸出 "Jack"
複製程式碼
2.5.2 改變 this
有3種函式方法執行你改變 this
值。
- fun.call(thisArg[, arg1[, arg2[, ...]]]);
- fun.apply(thisArg, [argsArray]);
- fun.bind(thisArg[, arg1[, arg2[, ...]]])
使用 call
或 apply
方法,就不需要將函式加入每個物件——你顯示地指定了 this
的值而不是讓 JavaScript 引擎自動指定。
call
與 apply
的不同地方是,call
需要把所有引數一個個列出來,而 apply
的引數需要一個陣列或者類似陣列的物件(如 arguments
物件)。
bind
是 ECMAScript 5 新增的,它會建立一個新函式返回。其引數與 call
類似,而且其所有引數代表需要被永久設定在新函式中的命名引數(繫結了的引數(沒繫結的引數依然可以傳入),就算呼叫時再傳入其它引數,也不會影響這些繫結的引數)。
function sayNameForAll(label){
console.log(label + ":" + this.name);
}
var person = {
name: "Nicholas"
}
var sayNameForPerson = sayNameForAll.bind(person);
sayNameForPerson("Person"); // 輸出"Person:Nicholas"
var sayName = sayNameForAll.bind(person, "Jc");
sayName("change"); // 輸出"Jc:Nicholas" 因為繫結的形參,會忽略呼叫時再傳入引數
複製程式碼
2.6 總結
- 函式也是物件,所以它可以被訪問、複製和覆蓋。
- 函式與其他物件最大的區別在於它們有一個特殊的內部屬性
[[Call]]
,包含了該函式的執行指令。 - 函式宣告會被提升至上下文的頂部。
- 函式是物件,所以存在一個
Function
建構函式。但這會使你的程式碼難以理解和除錯,除非函式的真實形式要直到執行時才能確定的時候才會利用它。