你不知道的JavaScript--Item8 函式,方法,建構函式呼叫
1、函式呼叫
Function絕對是JavaScript中的重中之重。在JavaScript中,Function承擔了procedures, methods, constructors甚至是classes以及modules的功能。
在物件導向程式設計中,functions,methods以及class constructor往往是三件不同的事情,由不同的語法來實現。但是在JavaScript中,這三個概念都由function來實現,通過三種不同的模式。
最簡單的使用模式就是function 呼叫:
function hello(username) {
return "hello, " + username;
}
hello("Keyser Söze"); // "hello, Keyser Söze"
2、方法的呼叫
而methods這一概念在JavaScript中的表現就是,一個物件的屬性是一個function:同樣的是函式,將其賦值給一個物件的成員以後,就不一樣了。將函式賦值給物件的成員後,那麼這個就不在稱為函式,而應該叫做方法。
var obj = {
hello: function() {
return "hello, " + this.username;
},
username: "Hans Gruber"
};
obj.hello(); // "hello, Hans Gruber"
真正的行為是,呼叫本身才會決定this會繫結到哪個物件,即:
obj1.hello()會將this繫結到obj1,obj2.hello()則會將this繫結到obj2。記住一句話,誰呼叫,this就指向誰
正因為this繫結的這種規則,在下面的用法也是可行的:
function hello() {
return "hello, " + this.username;
}
var obj1 = {
hello: hello,
username: "Gordon Gekko"
};
obj1.hello(); // "hello, Gordon Gekko"
var obj2 = {
hello: hello,
username: "Biff Tannen"
};_
obj2.hello(); // "hello, Biff Tannen"
但是,在一個普通的函式中,如上面的hello函式,使用this關鍵字是不太好的方式,當它被直接呼叫的時候,this的指向就成了問題。在這種情況下,this往往被指向全域性物件(GlobalObject),在瀏覽器上一般就是window物件。
而這種行為是不確定和沒有意義的。
所以在ES5標準中,如果使用了strict mode,那麼this會被設定為undefined:
function hello() {
"use strict";
return "hello, " + this.username;
}
hello(); // error: cannot read property "username" of undefined
以上這種做法是為了讓潛在的錯誤更快的暴露出來,避免了誤操作和難以找到的bug。
區別普通函式呼叫和方法呼叫,直接看這個例子就明確了。
var func = function() {
alert(this);
};
var o = {};
o.fn = func;
// 比較
alert(o.fn === func);//true
// 呼叫
func();//[object Window]
o.fn();//[object Object]
這裡的執行結果是,兩個函式是相同的,因此列印結果是 true。但是由於兩個函式的呼叫是不一樣的,func 的呼叫,列印的是 [object Window],而 o.fn 的列印結果是 [object Object]。
這裡便是函式呼叫與方法呼叫的區別,函式呼叫中,this 專指全域性物件 window,而在方法中 this 專指當前物件,即 o.fn 中的 this 指的就是物件o。
3、建構函式的呼叫
function的第三種使用模式就是講它作為constructor:
構造器中的this
我們需要分析建立物件的過程,方能知道this的意義. 如下面程式碼:
var Person = function() {
this.name = "小平果";
};
var p = new Person();
這裡首先定義了函式Person,下面分析一下整個執行:
- 程式在執行到這一句的時候,不會執行函式體,因此 JavaScript的直譯器並不知道這個函式的內容.
- 接下來執行new關鍵字,建立物件,直譯器開闢記憶體,得到物件的引用,將新物件的引用交給函式.
- 緊接著執行函式,將傳過來的物件引用交給this. 也就是說,在構造方法中,this就是剛剛被new建立出來的物件.
- 然後為this 新增成員,也就是為物件新增成員.
- 最後函式結束,返回this,將this交給左邊的變數.
分析過建構函式的執行以後,可以得到,建構函式中的this就是當前物件.
構造器中的 return
在建構函式中return的意義發生了變化,首先如果在建構函式中,如果返回的是一個物件,那麼就保留原意. 如果返回的是非物件,比如數字、布林和字串,那麼就返回this,如果沒有return語句,那麼也返回this. 看下面程式碼:
// 返回一個物件的 return
var ctr = function() {
this.name = "趙曉虎";
return {
name:"牛亮亮"
};
};
// 建立物件
var p = new ctr();
// 訪問name屬性
alert(p.name);
//執行程式碼,這裡列印的結果是"牛亮亮". 因為構造方法中返回的是一個物件,那麼保留return的意義,返回內容為return後面的物件. 再看下面程式碼:
// 定義返回非物件資料的構造器
var ctr = function() {
this.name = "趙曉虎";
return "牛亮亮";
};
// 建立物件
var p = new ctr();
// 使用
alert(p);
alert(p.name);
程式碼執行結果是,先彈窗列印[object Object],然後列印”趙曉虎”. 因為這裡 return 的是一個字串,屬於基本型別,那麼這裡的return語句無效,返回的是this物件. 因此第一個列印的是[object Object]而第二個不會列印undefined.
function User(name, passwordHash) {
this.name = name;
this.passwordHash = passwordHash;
}
var u = new User("sfalken",
"0ef33ae791068ec64b502d6cb0191387");
u.name; // "sfalken"
使用new關鍵將function作為constructor進行呼叫。和function以及method呼叫不一樣的是,constructor會傳入一個新的物件並將它繫結到this,然後返回該物件作為constructor的返回值。而constructor function本身的作用就是為了初始化該物件。
建構函式呼叫常犯的一個錯誤
興致勃勃地定義了下面這麼個建構函式:
var Coder = function( nick ){
this.nick = nick;
};
定義建構函式結束後呢?沒錯,趕緊例項化:
var coder = Coder( 'casper' );
這個coder兄弟叫什麼名字?趕緊列印下:
console.log( coder.nick ); //undefined
= =b 竟然是undefined!!再回過頭看看例項化的那個語句,不難發現問題出在哪裡:少了個new
var coder = Coder( 'casper' ); //當作普通的函式來呼叫,故內部的this指標其實指向window物件
console.log( window.nick); //輸出:casper
var coder = new Coder( 'casper' ); //加上new,一切皆不同,this正確地指向了當前建立的例項
console.log( coder.nick ); //輸出:casper
這樣的錯誤貌似挺低階的,但出現的概率挺高的,腫麼去避免或減少這種情況的發生呢?
可以在內部實現裡面動下手腳:
var Coder = function( nick ){
if( !(this instanceof Coder) ){
return new Coder( nick );
}
this.nick = nick;
};
其實很簡單,例項化的時候,內部判斷下,當前this指向的物件的型別即可,如果非當前建構函式的型別,強制重新呼叫一遍建構函式。
突然覺得Coder這名字不夠洋氣?想用Hacker,好吧,我改。。。數了下,一共有三處要改,這不科學,有沒有辦法只把建構函式的名字改了就行?
當然有:
var Coder = function( nick ){
if( !(this instanceof arguments.callee) ){
return new arguments.callee( nick );
}
this.nick = nick;
};
tips:據說在ES 5的嚴格模式下面arguments.callee會被禁用,不過僅當ES 5普及同時你指定了要使用嚴格模式,否則還是可以用的發散下思維。
相關文章
- 建構函式之間的呼叫函式
- 預設建構函式、引數化建構函式、複製建構函式、解構函式函式
- 類的建構函式和解構函式函式
- C++建構函式和解構函式呼叫虛擬函式時使用靜態聯編C++函式
- 建構函式與解構函式函式
- 建構函式,拷貝賦值函式的N種呼叫情況函式賦值
- C++ 建構函式和解構函式C++函式
- ## 建構函式函式
- C++:建構函式的分類和呼叫C++函式
- 建構函式與普通函式的區別函式
- C++中建構函式,拷貝建構函式和賦值函式的詳解C++函式賦值
- 關於建構函式與解構函式的分享函式
- 你不知道的JavaScript--Item7 函式和(命名)函式表示式JavaScript函式
- PHP筆記:建構函式與解構函式PHP筆記函式
- JavaScript 建構函式JavaScript函式
- Golang建立建構函式的方法詳解Golang函式
- 【譯】JavaScript 工廠函式 vs 建構函式JavaScript函式
- C++入門記-建構函式和解構函式C++函式
- 【C++】初始化列表建構函式VS普通建構函式C++函式
- 11-建構函式函式
- 初識建構函式函式
- JavaScript Date()建構函式JavaScript函式
- 建構函式建立物件函式物件
- 建構函式詳解函式
- 建構函式和類函式
- 外部函式的呼叫函式
- java8新特性之函式式介面、lambda表示式、介面的預設方法、方法和建構函式的引用Java函式
- 如何使用函式指標呼叫類中的函式和普通函式函式指標
- 類的解構函式自動呼叫函式
- 構造和解構函式呼叫順序函式
- 子函式呼叫函式
- 函式呼叫棧函式
- 繼承中的建構函式繼承函式
- swoole 服務的建構函式函式
- js建構函式的繼承JS函式繼承
- webgl內建函式--通用函式Web函式
- 預設建構函式和帶預設值的建構函式不能同時存在函式
- [C#解惑] #1 在建構函式內呼叫虛方法C#函式