javascript Function()

this_heart_add_add發表於2014-08-31

javascript Function()

 
JavaScript中的Function物件是函式,函式的用途分為3類:
作為普通邏輯程式碼容器;
作為物件方法;
作為建構函式。

1.作為普通邏輯程式碼容器
function multiply(x, y) {
  return x * y;
}

函式multiply封裝了兩位數的乘法運算公式:
var product = multiply(128, 128);         // product = 16384

建立函式例項的方式有3種。第一種是宣告式,即像宣告變數一樣,將通過function(){}識別符號建立的匿名函式直接賦值給變數,以該變數作為呼叫時的函式名稱:
var multiply = function(x, y) {
    return x * y;
}

第二種是定義式,即以function關鍵字後跟函式名稱及(){}來直接定義命名函式,前面第一個multiply函式就是通過定義式建立的。

第三種是建構函式式,即通過new運算子呼叫建構函式Function來建立函式。這種方式極不常用,因此就不作介紹了。

在建立函式的3種方式中,宣告式和定義式還存在細微的差別。比如下列程式碼中的函式採用宣告式:
var example = function(){
    return 1;
}
example();

var example = function(){
    return 2;
}
example();

執行結果如下:
1
2

而如果採用定義式,即:
function example() {
    return 1;
}
example();

function example() {
    return 2;
}
example();

那麼會得到另一種結果:
2
2
即,在採用定義式建立同名函式時,後建立的函式會覆蓋先建立的函式。這種差別是由於JavaScript解釋引擎的工作機制所導致的。 JavaScript解釋引擎在執行任何函式呼叫之前,首先會在全域性作用域中註冊以定義式建立的函式,然後再依次執行函式呼叫。由於註冊函式時,後定義的函式重寫了先定義的函式,因此無論呼叫語句位於何處,執行的都是後定義的函式。相反,對於宣告式建立的函式,JavaScript解釋引擎會像對待任何宣告的變數一樣,等到執行呼叫該變數的程式碼時才會對變數求值。由於JavaScript程式碼是從上到下順序執行的,因此當執行第一個example()呼叫時,example函式的程式碼就是首先定義程式碼;而當執行第二個example()呼叫時,example函式的程式碼又變成了後來定義的程式碼。

2.作為物件方法
JavaScript在解析程式碼時,會為宣告或定義的函式指定呼叫物件。所謂呼叫物件,就是函式的執行環境。如果函式體內有以關鍵字this宣告的變數,則this引用的就是呼叫物件。

事實上,在普通的函式中,也存在呼叫物件,只不過這個呼叫物件是預設的全域性window物件而已。例如:
var product = window.multiply(128, 128); // product = 16384

這說明,預設情況下,在全域性作用域中定義或宣告的函式的呼叫物件就是window。

在物件導向程式設計中,通常將作為物件成員的函式稱為方法。例如:
var dog   = {};
dog.name  = "heibao";
dog.age   = "3 months";

dog.shout = function() {
  return "Hello, My name is" + this.name + "and I am" + this.age + "old!";
}
dog.shout(); // “Hello, My name is heibao and I am 3 months old!”

有意思的是,物件也可以借用其他物件的方法:
var cat   = {};
cat.name  = "xiaohua";
cat.age   = "2 years";
cat.greet = dog.shout;
cat.greet();          //“Hello, My name is xiaohua and I am 2 years old!”

另外,使用函式物件的call和apply方法,還可以動態指定函式或方法的呼叫物件:

dog.shout.call(cat);  //“Hello, My name is xiaohua and I am 2 years old!”
或者
dog.shout.apply(cat); //“Hello, My name is xiaohua and I am 2 years old!”

3.作為建構函式
JavaScript是通過建構函式來模擬面嚮物件語言中的類的。例如:
function Animal(sort, character) {
   this.sort = sort;
   this.character = character;
}

以Animal作為建構函式,就可以像下面這樣建立一個新物件:
var dog = new Animal("mammal", "four legs");

建立dog的物件的過程如下:首先,new運算子建立一個空物件({}),然後以這個空物件為呼叫物件呼叫函式Animal,為這個空物件新增兩個屬性sort和character,接著,再將這個空物件的預設constructor屬性修改為建構函式的名稱(即Animal;空物件建立時預設的 constructor屬性值是Object),並且將空物件的__proto__屬性設定為指向Animal.prototype——這就是所謂的物件初始化。最後,返回初始化完畢的物件。這裡將返回的新物件賦值給了變數dog。

dog.sort;        // mammal
dog.character;   // four legs
dog.constructor; // Animal

聰明的讀者結合前面介紹的內容,可能會認為使用new運算子呼叫建構函式建立物件的過程也可以像下面這樣來實現:

var dog = {};
Animal.call(dog, "mammal", "four legs");

表面上看,這兩行程式碼與var dog = new Animal("mammal", "four legs");是等價的,其實卻不是。雖然通過指定函式的執行環境能夠部分達到初始化物件的目的,例如空物件dog確實獲得了sort和character這兩個屬性:

dog.sort;        // mammal
dog.character;   // four legs
dog.constructor; // Object —— 注意,沒有修改dog物件預設的constructor屬性

但是,最關鍵的是新建立的dog物件失去了通過Animal.prototype屬性繼承其他物件的能力。只要與前面採用new運算子呼叫建構函式建立物件的過程對比一下,就會發現,new運算子在初始化新物件期間,除了為新物件新增顯式宣告的屬性外,還會對新物件進行了一番“暗箱操作”——即將新物件的constructor屬性重寫為Animal,將新物件的__proto__屬性設定為指向Animal.prototype。雖然手工“初始化物件”也可以將dog.constructor重寫為Animal,但根據ECMA262規範,物件的__proto__屬性對開發人員是隻讀的,對它的設定只能在通過new運算子建立物件時由JavaScript解釋引擎替我們完成。
JavaScript是基於原型繼承的,如果不能正確設定物件的__proto__屬性,那麼就意味著預設的繼承機制會失效:

Animal.prototype.greet = "Hi, good lucky!";
dog.greet; // undefined

事實上,在Firefox中,__proto__屬性也是可寫的:

Animal.prototype.greet = "Hi, good lucky!";
dog.__proto__ = Animal.prototype;
dog.greet; // Hi, good lucky!

但這樣做只能在Firefox中行得通。考慮到在相容多瀏覽器,必須依賴於new運算子,才能實現基於原型的繼承

相關文章