在JavaScript中,函數其實就是對象。使函數不同於其他對象的決定性特點是函數存在一個被稱為[[Call]]的內部屬性。內部屬性無法通過代碼訪問而是定義了代碼執行時的行為。ECMAScript為JavaScript的對象定義了多種內部屬性,這些內部屬性都用雙重中括弧來標註。 [[Call]]屬 ...
在JavaScript中,函數其實就是對象。使函數不同於其他對象的決定性特點是函數存在一個被稱為[[Call]]的內部屬性。內部屬性無法通過代碼訪問而是定義了代碼執行時的行為。ECMAScript為JavaScript的對象定義了多種內部屬性,這些內部屬性都用雙重中括弧來標註。
[[Call]]屬性是函數獨有的,表明該對象可以被執行。由於僅函數又有該屬性,ECMAScript定義typeof
操作符對任何具有[[Call]屬性的對象返回"function"。
創建函數的方式
- 函數聲明,以function關鍵字開頭,後面跟著函數的名字。函數的內容放在大括弧內;
function add(v1, v2) { return v1 + v2; }
- 函數表達式,function關鍵字後面不需要加上函數的名字。這種函數被稱為匿名函數,因為函數對象本身沒有名字。取而代之的函數表達式通常會被一個變數或屬性引用。函數表達式的賦值通常在最後有一個分號,就如同其他對象的賦值一樣;
var add = function(v1, v2) { return v1 + v2; };
- 函數的構造函數(不建議使用,體驗差,調試困難)。
var add = new Function("v1", "v2", "return v1 + v2;");
函數就是值
我們可以像使用對象一樣使用函數,也可以將它們賦給變數,在對象中添加它們,將它們當成參數傳遞給別的函數,或從別的函數中返回。基本上只要是可以使用其他引用值的地方,就可以使用函數。
function sayHi() { console.log("Hi!"); } sayHi(); // outputs "Hi!" var sayHi2 = sayHi; sayHi2(); // outputs "Hi!"
參數
JavaScript函數的另一個獨特之處在於你可以給函數傳遞任意數量的參數卻不造成錯誤。那是因為函數參數實際上被保存在一個被稱為arguments的類似數組(arguments對象不是一個數組的實例)的對象中;
arguments對象自動存在於函數中。也就是說,函數的命名參數不過是為了方便,並不真的限制了該函數可接受參數的個數;
函數期望的參數個數保存在函數的length屬性中。
function reflect(value) { return value; } console.log(reflect("Hi!")); // "Hi!" console.log(reflect("Hi!", 25)) // "Hi!" console.log(reflect.length) // 1 reflect = function() { return arguments[0]; } console.log(reflect("Hi!")); // "Hi!" console.log(reflect("Hi!", 25)) // "Hi!" console.log(reflect.length) // 0
重載
大多數面向對象語言支持函數重載,它能讓一個函數具有多個簽名。JavaScript函數可以接受任意數量的參數且參數類型完全沒有限制。這說明JavaScript函數其實根本沒有簽名,因此也不存在重載。在JavaScript里,當試圖定義多個同名的函數時,只有最後定義的有效,之前的函數聲明被完全刪除,只使用最後那個。
function say(msg) { console.log(msg); } function say() { console.log("Default message"); } say("Hello"); // 輸出"Default message"
JavaScript函數沒有簽名這個事實不意味著不能模仿函數重載,可以使用arguments對象獲取傳入的參數個數並決定怎麼處理。
function say(msg) { if(arguments.length === 0) { msg = "Default message"; } console.log(msg); } say("Hello"); //輸出"Hello!"
對象方法
如果對象屬性的值是函數,則該屬性被稱為方法。
this對象
JavaScript所有的函數作用域內都有一個this對象代表調用該函數的對象。在全局作用域中,this代表全局對象。當一個函數作為對象的方法被調用時,預設this的值等於那個對象。
var person = { name : "Tom", sayName : function() { console.log(this.name); } }; person.sayName(); //輸出"Tom"
改變this
在JavaScript中,函數會在各種不同上下文中被使用,它們必須到哪都能正常工作。一般this會被自動設置,但是可以改變它的值來完成不同的目標。有3種函數方法允許你改變this的值(記住函數的對象,而對象可以有方法,所以函數也有)。
- call()方法
第一個參數指定函數執行時this的值,其後的所有參數都是需要被傳入函數的參數。
function sayName(label) { console.log(label + ":" + this.name); } var person1 = { name : "Tom" }; var person2 = { name : "Bob" } var name = "Peter"; sayName.call(this, "global"); //global:Peter sayName.call(person1, "person1");//person1:Tom sayName.call(person2, "person2");//person2:Bob
- apply()方法
工作方式和
call()
完全一樣,只接受兩個參數:this的值,一個數組。function sayName(label) { console.log(label + ":" + this.name); } var person1 = { name : "Tom" }; var person2 = { name : "Bob" } var name = "Peter"; sayName.apply(this, "global"); //global:Peter sayName.apply(person1, "person1"); //person1:Tom sayName.apply(person2, "person2"); //person2:Bob
- bind()方法
bind
方法用於將函數體內的this綁定到某個對象,然後返回一個新函數。function sayName(label) { console.log(label + ":" + this.name); } var person = { name : "Tom" }; //create a function just for person1 var sayNameForPerson = sayName.bind(person1); sayNameForPerson1("person"); // "person:Tom"