javascript的函數調用和構造函數調用

https://blog.csdn.net/sunshine940326/article/details/52222430

 

函數調用、方法調用以及構造函數調用

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" 
  • 1
  • 2
  • 3
  • 4

2 方法調用

而methods這一概念在JavaScript中的表現就是,一個對象的屬性是一個function:同樣的是函數,將其賦值給一個對象的成員以後,就不一樣了。將函數賦值給對象的成員後,那麼這個就不在稱爲函數,而應該叫做方法。

var obj = { 
  hello: function() { 
    return "hello, " + this.username; 
  }, 
  username: "Hans Gruber"
};
obj.hello(); // "hello, Hans Gruber"  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

真正的行爲是,調用本身才會決定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" 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

但是,在一個普通的函數中,如上面的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 
  • 1
  • 2
  • 3
  • 4
  • 5

以上這種做法是爲了讓潛在的錯誤更快的暴露出來,避免了誤操作和難以找到的bug。 
區別普通函數調用和方法調用,直接看這個例子就明確了。

var func = function() {
  alert(this);
};
var o = {};
o.fn = func;
// 比較
alert(o.fn === func);//true
// 調用
func();//[object Window]
o.fn();//[object Object]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

這裏的運行結果是,兩個函數是相同的,因此打印結果是 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();
  • 1
  • 2
  • 3
  • 4

這裏首先定義了函數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);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

//執行代碼,這裏打印的結果是”牛亮亮”. 因爲構造方法中返回的是一個對象,那麼保留return的意義,返回內容爲return後面的對象. 再看下面代碼:

// 定義返回非對象數據的構造器
var ctr = function() {
 this.name = "趙曉虎";
 return "牛亮亮";
};
// 創建對象
var p = new ctr();
// 使用
alert(p);
alert(p.name);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

代碼運行結果是,先彈窗打印[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" 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用new關鍵將function作爲constructor進行調用。和function以及method調用不一樣的是,constructor會傳入一個新的對象並將它綁定到this,然後返回該對象作爲constructor的返回值。而constructor function本身的作用就是爲了初始化該對象。 
構造函數調用常犯的一個錯誤 
興致勃勃地定義了下面這麼個構造函數:

var Coder = function( nick ){ 
this.nick = nick; 
}; 
  • 1
  • 2
  • 3

定義構造函數結束後呢?沒錯,趕緊實例化:

var coder = Coder( 'casper' );
  • 1

這個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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這樣的錯誤貌似挺低級的,但出現的概率挺高的,腫麼去避免或減少這種情況的發生呢? 
可以在內部實現裏面動下手腳:

var Coder = function( nick ){ 
  if( !(this instanceof Coder) ){ 
    return new Coder( nick ); 
  } 
    this.nick = nick; 
}; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

其實很簡單,實例化的時候,內部判斷下,當前this指向的對象的類型即可,如果非當前構造函數的類型,強制重新調用一遍構造函數。 
突然覺得Coder這名字不夠洋氣?想用Hacker,好吧,我改。。。數了下,一共有三處要改,這不科學,有沒有辦法只把構造函數的名字改了就行? 
當然有:

var Coder = function( nick ){ 
  if( !(this instanceof arguments.callee) ){ 
    return new arguments.callee( nick ); 
  } 
  this.nick = nick; 
}; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4 間接調用

區分apply,call就一句話,

foo.call(this, arg1,arg2,arg3) == foo.apply(this, arguments)==this.foo(arg1, arg2, arg3) 

例如: 
A, B類都有一個message屬性(面向對象中所說的成員), 
A有獲取消息的getMessage方法, 
B有設置消息的setMessage方法,

var b = new B();
//給對象a動態指派b的setMessage方法,注意,a本身是沒有這方法的!
b.setMessage.call(a, "a的消息");
//下面將顯示"a的消息"
alert(a.getMessage());
//給對象b動態指派a的getMessage方法,注意,b本身也是沒有這方法的!
alert(b.setMessage());
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

這就是動態語言 JavaScript call的威力所在!

簡直是”無中生有”,對象的方法可以任意指派,而對象本身一直都是沒有這方法的,注意是指派,通俗點就是,方法是借給另一個對象的調用去完成任務,原理上是方法執行時上下文對象改變了.

所以 b.setMessage.call(a, “a的消息”); 就等於用a作執行時上下文對象調用b對象的setMessage方法,而這過程中與b一點關係都沒有, 作用等效於a.setMessage( “a的消息”);

call, apply作用就是借用別人的方法來調用,就像調用自己的一樣.

好,理解了call, apply相同處—–作用後,再來看看它們的區別,看過上面例子,相信您大概知道了.

從 b.setMessage.call(a, “a的消息”) 等效於 a.setMessage( “a的消息”) 可以看出, “a的消息”在call中作爲一個參數傳遞,

call, apply方法區別是,從第二個參數起, call方法參數將依次傳遞給借用的方法作參數, 而apply直接將這些參數放到一個數組中再傳遞, 最後借用方法的參數列表是一樣的.當參數明確時可用call, 當參數不明確時可用apply給合arguments

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章