1. 程式人生 > >javascript中function(){}(),new function(),new Function(),Function

javascript中function(){}(),new function(),new Function(),Function

和java比起來,javascript真的是鬆散的無以復加,不過這也讓我們在無聊之餘,有精力去探討一些複雜的應用,從而在開發之路上,獲得一些新的想法。

javascript中的類的構造

javascript中有物件的概念,卻沒有類的概念。對於基礎不牢的同學,很難在類和物件之間加以區分,這裡簡單的將它們的關係概況為:類是一種抽象的概念,例如瓶子、人、笨蛋;而物件,則是指這種概念中的實體,比如“那個美女手中的那隻瓶子”“村長是一個地道的農民”“她的男朋友是個笨蛋”;例項化,就是指以類為基礎構建一個實體。類所擁有的特徵,其例項化物件,也一定擁有這些特徵,而且例項化後可能擁有更多特徵。

javascript在用到物件時,完全沒有類的概念,但是程式設計的世界裡,無奇不有,我們卻可以通過function構造出一種假想的類,從而實現javascript中類的構造。

比如,我們通過下面的方法來構造一個類:

//java
class Book {
    private String name;
    private double price;
    public Book(name,price) {this.name=name;this.price=price;}
    public void setName(String name) { this.name = name;}
    public void setPrice(double price) {this.price = price;}
    public String getInfo() {...} 
}
Book book1 = new Book('java',13.3);

//javascript
function Book(name,price) {
    this.name = name;
    this.price = price;
    this.setName = function(name) {this.name = name;};
    this.setPrice = function(price) {this.price = price};
    this.getInfo = function() {return this.name + '  ' + this.price;};
}
var book1 = new Book('java',13.3);

這是很好玩兒的一種嘗試。這也是本文要討論的所有問題的起源。

function(){}()讓變數快速初始化結果

在《javascript立即執行某個函式:外掛中function(){}()再思考》一文中,我詳細闡述了function(){}()的作用及理解思路。這裡不再贅述,現在,我們面臨的新問題是,知道了它的作用,我們如何使用它?

讓我們來看一段程式碼:

var timestamp = function(){
    var timestamp = Date.parse(new Date());
    return timestamp/1000;
}();

當我們要使用一個變數時,我們希望這個變數在一個環節完成我們的賦值,使用上面的這種方法,可以減少程式碼上下文執行邏輯,如果按照我們以前的方法,程式碼可能會寫成:

var timestamp = Date.parse(new Data());
timestamp = timestamp/1000;

看上去好像比上面的操作簡潔多了,只需要兩行程式碼。但是我們仔細去觀察,就會發現第一段程式碼其實本身僅是一個賦值操作,在function中完成的所有動作將會在function執行完後全部釋放,整個程式碼看上去好像只執行了一條語句一樣。

而實際上更重要的意義在於它可以讓一個變數在初始化時,就具備了運算結果的效果。

使用new function初始化一個可操作物件

第一部分講到了javascript中的類,而使用new function就可以例項化這個類。但是我們實際上有的時候在為一個變數賦值的時候,希望直接將它初始化為一個可操作的物件,比如像這樣:

// 這裡的資料庫操作是我虛擬出來的一種資料庫操作形式
var $db = new function(){
    var $db = db_connect('127.0.0.1','root','');
    $db.use('database');
    this.select = function(table,where) {
        var result = $db.query('select from ' + table + ' where ' + where);
        return $db.fetchAll(result);
    }
};

而當我們要對資料庫database進行查詢時,只需要通過 var list = $db.select('table','1=1');
進行操作即可,資料庫的初始化結果已經在$db這個變數中了。

Function是由function關鍵字定義的函式物件的原型

在javascript中,多出了一個原型的概念。所謂原型,其實就是一個物件的本質,但複雜就複雜在,原型本身也是物件,因此,任何一個物件又可以作為其他物件的原型。Function就相當於一個系統原型,可以把它理解為一種“基本物件型別”,是“物件”這個概念範疇類的基本資料型別。除了Function之外,其實還有很多類似的首字母大寫的物件原型,例如Object, Array, Image等等。有一種說法是:javascript中所有的一切都是物件(除了基本資料型別,其他的一切全是物件),所有的物件都是Object衍生出來的。(按照這種說法,我們應該返回去再思考,上面說的類的假設是否成立。)

極其重要的prototype概念

prototype的概念在javascript中極其重要,它是javascript中完成上面說的“一切皆物件”的關鍵。有了prototype,才有了原型,有了原型,才有了javascript五彩繽紛的世界(當然,也有人說是雜亂的)。我們可以這樣去理解prototype:世界上本沒有javascript,上帝說要有Object,於是有了Object,可是要有Function怎麼辦?只需要對Object進行擴充套件,可是如何擴充套件?只需要用prototype……當然,這是亂扯的,不過在javascript中,只要是function,就一定會有一個prototype屬性。實際上確實是這樣

Function.prototype.show = function() {...}

在原型的基礎上通過prototype新增屬性或方法,則以該物件為原型的例項化物件中,必然存在新增的屬性或方法,而且它的內容是靜態不可過載的。原型之所以被稱為原型,可能正是因為這種不可過載的特質。

比如上面的這段程式碼,會導致每一個例項化的function,都會具備一個show方法。而如果我們自己建立了一個類,則可以通過prototype將之轉化為原型:

function Cat() {...}
Cat.prototype.run = function() {};
var cat1 = new Cat();

這時,對於cat1而言,Cat就是原型,而該原型擁有一個run的原始方法,所以無論例項化多少個Cat,每一個例項化物件都有run方法,而且該方法是不能被過載的,通過cat1.run = function(){}是無效的。

為了和其他語言的類的定義方法統一,我們可以將這種原型屬性在定義類的時候,寫在類的構造裡面:

function Cat() {
    ....
    Cat.prototype.run = function() {};
}

new Function()是函式原型的一個例項化

在理解了Function原型的概念之後,再來看new Function()就顯得很容易了。首先來看下我們是怎麼使用這種奇特的寫法的:

var message = new Function('msg','alert(msg)');
// 等價於:
function message(msg) {
    alert(msg);
}

new Function(引數1,引數2,…,引數n,函式體),它的本意其實是通過例項化一個Function原型,得到一個數據型別為function的物件,也就是一個函式,而該變數就是函式名。

this在這類function中的指向

this在javascript中真的是無法讓我們捉摸透徹。但是有一個小竅門,就是:一般情況下,this指向的是當前例項化物件,如果沒有找到該物件,則是指向window。從使用上來講,我們應該排除new Function的討論,因為它和我們常用的函式宣告是一致的。

普通的函式中this的指向

函式宣告的時候,如果使用了this,那麼就要看是把該函式當做一個物件加以返回,還是以僅執行函式體。普通函式執行時,我們完全沒有引入物件、類這些概念,因此,this指向window。通過程式碼來看下:

var msg;
function message(msg) {
    this.msg = msg;
}
message('ok');
alert(msg);

首先是宣告一個函式message,在函式中this.msg實際上就是window.msg,也實際上就是程式碼開頭的msg。因此,當執行完message(‘ok’)的時候,開頭的全域性變數msg也被賦值為ok。

通過function構造類時this的指向

如果function被構造為一個類,那麼必然存在該類被例項化的一個過程,如果沒有例項化,那麼該類實際上並沒有在程式中被使用。而一旦例項化,那麼this將指向例項化的物件。

var age = 3;
var cat1 = new function() {
    this.name = 'Tom';
    this.age = 2;
    this.weight = function(age) {
        var age = age * 2;
        var _age = this.age * 2;
        return 'weight by age:' + age + '; weight by this.age:' + _age;
    }(this.age);
    this.eye = new function() {
        this.size = '1.5cm';
        this.color = 'red';
    };
    this.catching = function(mouse) {
        return this.name + ' is catching ' + mouse;
    };
};
alert(cat1.weight);
alert(cat1.eye.color);
alert(cat1.catching('Jerry'));

上面程式碼中標記了4處紅色的this的使用。根據我們的原則,this指向例項化物件,我們來對每一個this進行分解。

首先是cat1.weight,我使用了function(){}(),直接利用貓咪的年齡進行計算得出體重返回給weight屬性。

第一個this.age出現在function(){}(this.age),這個this.age實際上是一個傳值過程,如果你對我之前分析function(){}()比較瞭解的話,應該知道,this.age實際上是和前面this.age = 2指同一個,這裡的this.age的this,首先要去找它所在的function,然後看這個function是否被例項化,最後確認,確實被例項化為cat1,因此this=cat1。

第二個this.age出現在function(){this.age}()。同樣,你先需要對function(){}()再次深入瞭解,實際上,function(){}()就是執行一個函式而已,我們前面提到了,普通函式執行中this=window,所以,這裡的this.age實際上是var age = 3。

第三個this.color出現在new function(){this.color},這裡就比較好玩,由於有一個new,實際上也被例項化了,只不過是對匿名類的例項化,沒有類名,而且例項化僅可能出現這一次。因此,this.color的this要去找new function的主人,也就是this.eye,而this.eye的this=cat1,所以cat1.eye.color=’red’。

第四個this.name出現在function(){this.name},它出現在cacthing方法中,它既不是普通的函式執行,也不是例項化為物件,而是正常的類中的方法的宣告,因此this指向要去找它所在的function被例項化的物件,也就是cat1。

小結

本文雖然講了很多,但核心點實際上還是落在javascript的面向物件這個點上。javascript中雖然沒有明確的class的概念,那是因為它首先基於type這樣的基礎概念,並強調一切皆物件的這種思想。如果從哲學上講,javascript真正符合道生一,一生二,二生萬物的原則。不過本文沒有提到和繼承相關的內容,所有解釋也是我自己的一種理解方式,不代表真正的原理,讀者請自己通過其他途徑進行學習。