JS語言精粹--函數篇之this與調用模式

久違的博文,貌似距離我上一篇也算是有些年歲(加班的日子真是度日如年啊T^T)了,因此呢,如今是時候迴歸正道了,仍是歡迎各位IT道友多多交(tu)流(cao)哈!編程

正文

首先,說到 JavaScript 函數,咱們就要先理解下一些極可能被忽視的小概念:函數對象函數字面量數組

函數對象

咱們知道,在JavaScript中 函數 就是 對象。對象是「名/值」的集合,並擁有一個連到原型對象的隱藏鏈接。其中,對象字面量產生的對象鏈接到 Object.prototype,而函數對象鏈接到 Function.prototype (注:該原型對象自己鏈接到 Object.prototype )。每一個函數在建立時,都附有兩個附加的隱藏屬性: 函數的上下文 實現函數行爲的代碼 閉包

另外,每一個函數對象在建立時,也隨帶有一個 prototype 屬性,他的值是一個擁有 constructor 屬性,並且其值即爲該函數的對象。這和隱藏鏈接到 Function.prototype 徹底不一樣,而這個使人費解的構造過程的意義,我先埋個坑,之後在繼承篇的相關文章中再來填好了。app

由於函數是對象,因此它能夠像其餘值同樣被使用,好比,能夠存放在變量、對象和數組中,能夠被當作參數傳遞給其餘函數,也能夠在函數中返回函數,並且,更由於函數是對象,所以 函數也能夠擁有方法編程語言

函數字面量

函數對象能夠經過函數字面量來建立:函數

var add = function (a, b) {
    return a + b;
}

函數字面量包括四個部分:工具

第一部分,是 保留字 function開發工具

第二部分,是 函數名,它能夠省略不寫。函數能夠用它的名字來 遞歸 地調用本身。此名字也能被調試器和開發工具來識別函數(如:FireBugChrome console 等)。若是沒有給函數命名,好比上面的例子,它會認爲是 匿名函數this

第三部分,是包圍在圓括號中的一組 參數,其中每一個參數之間用逗號隔開,這些參數(也稱形式參數,即形參)將被定義爲函數中的變量,可是,它們不像普通變量那樣被初始化爲 undefined而是在該函數被調用時初始化爲實際提供的參數的值(也稱實際參數,即實參)。prototype

第四部分,是包圍在花括號中的一組語句,這些語句就是 函數主體,它們在函數被調用時執行。

函數字面量能夠出如今任何容許表達式出現的地方。固然,函數也能夠嵌套在其餘函數中,這樣的話,一個內部函數不只能夠訪問本身的參數和變量,同時也能夠方便地訪問它被嵌套的那個外部函數的參數和變量。

經過函數字面量建立的函數對象包含一個連到外部上下文的鏈接,這被稱爲 閉包它是 JavaScript 強大表現力的根基。而關於閉包的詳細原理和使用方法,之後會發布一些專門的文章進行說明,敬請期待 ( ^_^ ) ~~


葷割線以後,接下來就是本文的重頭戲 -- 關鍵字 this 上場。衆所周知,這個老(son)夥(of)計(bit ch)能夠說是JavaScript中的一大深坑,至於如何華麗麗地跳出這個坑,還請各位搬好板凳,備好瓜子,聽我慢慢道來。

調用

當咱們調用一個函數時,將暫停當前函數的執行,將傳遞控制器與參數給新函數。然而,除了聲明時定義的形參,每一個函數接收兩個附加的參數:thisarguments。參數 this 在面向對象編程中是很是重要的,它的值取決於調用的模式。在JavaScript中有四種調用模式:方法調用模式函數調用模式構造器調用模式apply調用模式

調用運算符,就是跟在任何一個函數值的表達式以後的一對圓括號,它能夠包含零個或者多個用逗號隔開的表達式,每一個表達式產生一個參數值,每一個參數值被賦予函數聲明時定義的形式參數名,而當實際參數(arguments)的個數與形式參數(parameters)的個數不匹配時,不會致使運行時報錯。好比說,若是實參值過多,超出的參數值將被忽略,若是實參值過少,缺失的值將會被替換爲 undefined。而且,對參數值不會進行類型檢查,即任何類型的值均可以被傳遞給參數。

方法調用模式

當一個函數被保存爲對象的一個屬性時,咱們稱之爲方法。當一個方法被調用時,this 會被綁定到該對象,即this就是該對象。若是一個調用表達式包含一個屬性存取表達式(即一個 . 點表達式 或者 [subscript] 下標表達式),那麼它將被當作一個方法來調用。

// 建立 myObject。它有一個 value 屬性 和一個 increment 方法
// increment 方法接收一個可選的參數,若參數不是數字型,則默認使用數字 1。
var myObject = {
    value: 0,
    increment: function (inc) {
        this.value += typeof inc === 'number' ? inc : 1;
    }
};
// 不傳參
myObject.increment();  
console.log(myObject.value);  // 1

// 傳非數字型
myObject.increment('a');  
console.log(myObject.value);  // 2 

// 傳數字型
myObject.increment(2);  
console.log(myObject.value);  // 4

方法可使用 this 去訪問對象,因此它能從對象中取值或者修改該對象。this 到對象的綁定,發生在調用的時候。這個「超級」遲綁定(very late binding)使得函數能夠對 this 高度複用。經過 this 可取得它們所屬對象的上下文的方法,稱爲公共方法

函數調用模式

當一個函數並不是一個對象的屬性(即方法)時,那麼它將被當作一個函數來調用。

function add (a, b) {
    return a + b;
}

var sum = add(3,4);
console.log(sum);  // 7

當函數以此模式調用時,this 被綁定到全局對象(即 window 對象),這是語言設計上的一個重大的錯誤啊!!若是設計正確的話,當內部函數被調用時,this 應該仍然綁定到外部函數的 this 變量纔對。這個錯誤設計的後果是,方法不能利用內部函數來幫助他工做,由於內部函數的 this 被綁定了錯誤的值,或者說綁定了咱們不想要的值,因此不能共享該方法對於對象的訪問權。不過,幸運的是,有一個很容易的解決方案:若是該方法定義一個變量並給它賦值爲this,那麼內部函數就能夠經過那個變量訪問到 this,而這個變量咱們一般命名爲 that

function add (a, b) {
    return a + b;
}

// 給 myObject 增長一個double方法
myObject.double = function () {
    var that = this;
    console.log(that);

    var helper = function () {
        that.value = add(that.value, that.value);
    };

    // 以函數的形式調用 helper
    helper();  
};

// 以方法的形式調用 double
myObject.double();  
console.log(myObject.getValue());  // 8

構造器調用模式

JavaScript 是一門基於原型繼承的語言,這就意味着對象能夠直接從其它對象繼承屬性或方法,而該語言也是無類別的。

若是在一個函數前面加上一個 new 來調用,那麼將會建立一個隱藏鏈接到該函數的 prototype 成員的新對象(或者稱之爲該對象的實例),同時,this 將會被綁定到那個新對象(實例)上。

然而,new 前綴也會改變 return 語句的行爲,這個咱們之後再作詳細解析。

Quo.prototype.get_status = function () {
    return this.status;
};

// 構造一個 Quo 的實例
var myQuo = new Quo('success');
console.log(myQuo.get_status());  // success

目標就是結合 new 前綴來調用的函數,被稱爲構造函數。按照約定,它們保存在以首字母大寫命名的變量裏。若是調用構造函數時,沒有在前面加上 new,可能會發生很是糟糕的事情(如,實例沒法調用該原型對象的方法,等),這樣既沒有編譯時警告,也沒有運行時警告,因此加 new 前綴和大寫約定,是很是、很是、很是重要的(重要話,說三遍)。

然並卵,實際使用中,咱們並不推薦這種形式的構造器函數,之後將在JavaScript的繼承篇爲各位提供更好的解決方案。

Apply 調用模式

由於 JavaScript 是一門函數式的面向對象的編程語言,因此函數能夠擁有方法

apply 方法讓咱們構建一個參數數組並用其去調用函數,它也容許咱們選擇 this 的取值。apply 方法接收兩個參數,第一個是將被綁定給 this 的值,第二個就是一個參數數組。

// 1.構造一個帶有兩個數字的數組,將之相加
var arr = [3, 4];
var sum = add.apply(null, arr);  

console.log(sum);  // 7

// 2.構造一個含有 status 成員的對象
var statusObj = {
    status: 'right'
};

var status = Quo.prototype.get_status.apply(statusObj);  
console.log(status);  // right

這裏第二個例子的代碼,經過了 apply 方法替換 Quo 對象中的 this 指針。

相關文章
相關標籤/搜索