javascript是一門動態語言,最明顯就是那個dynamic this。它一般都是作為函數調用者存在。在javascript,所有關係都可以作為對象的一個關聯陣列元素而存在。那麼函數就是被掰成兩部分儲存於對象,一是其函數名(鍵),一是函數體(值),那麼函數中的this一般都指向函數所在的對象。但這是一般而已,在全域調用函數時,我們並沒有看到調用者,或者這時就是window。不過,函式宣告後,其實並沒有綁定到任何對象,因此我們可以用call apply這些方法設定調用者。
一個簡單的例子:
window.name = "window"; var run = function() { alert("My name is " + this.name); } run();
<br /> window.name = "window";<br /> var run = function() {<br /> alert("My name is " + this.name);<br /> }<br /> run()<br />
運行代碼
這裡你不能說run是作為window的一個屬性而存在,但它的確是被window屬性調用了。實質上大多數暴露在最上層的東西都則window接管了。在它們需要調用時被拷貝到window這個對象上(不過在IE中window並不繼承對象),於是有了window['xxx']與window.xxx效能上的差異。這是內部實現,不深究了。
另一個例子,綁定到一個明確的對象上
window.name = "window"; object = { name: "object", run: function() { alert("My name is " + this.name); } }; object.run();
<br /> window.name = "window";<br /> object = {<br /> name: "object",<br /> run: function() {<br /> alert("My name is " + this.name);<br /> }<br /> };<br /> object.run();<br />
運行代碼
答案顯然易見,this總是為它的調用者。但如果複雜一點呢?
window.name = "window"; object = { name: "object", run: function() { var inner = function(){ alert("My name is " + this.name); } inner(); } }; object.run();
<br /> window.name = "window";<br /> object = {<br /> name: "object",<br /> run: function() {<br /> var inner = function(){<br /> alert("My name is " + this.name);<br /> }<br /> inner();<br /> }<br /> };<br /> object.run();<br />
運行代碼
儘管它是定義在object內部,儘管它是定義run函數內部,但它彈出的既不是object也不是run,因為它既不是object的屬性也不是run的屬性。它鬆散在存在於run的範圍用,不能被前兩者調用,就只有被window拯救。window等原生對象浸透於在所有指令碼的內部,無孔不入,只要哪裡需要到它做貢獻的地方,它都義不容辭。但通常我們不需要它來幫倒忙,這就需要奠出call與apply兩大利器了。
window.name = "window"; var object = { name: "object", run: function() { inner = function() { alert( this.name); } inner.call(this); } } object.run();
<br /> window.name = "window";<br /> var object = {<br /> name: "object",<br /> run: function() {<br /> inner = function() {<br /> alert( this.name);<br /> }<br /> inner.call(this);<br /> }<br /> }<br /> object.run();<br />
運行代碼
call與apply的區別在於第一個參數以後的參數的形式,call是一個個,aplly則都放到一個數組上,在參數不明確的情況,我們可以藉助arguments與Array.slice輕鬆搞定。
window.name = "Window"; var cat = { name: "Cat" }; var dog = { name: "Dog", sound: function(word) { alert(this.name + word); } }; dog.sound(" is pooping"); dog.sound.call(window, " is banking"); dog.sound.call(dog, " is banking"); dog.sound.apply(cat, [" miaowing"]);
<br /> window.name = "Window";</p><p> var cat = {<br /> name: "Cat"<br /> };<br /> var dog = {<br /> name: "Dog",<br /> sound: function(word) {<br /> alert(this.name + word);<br /> }<br /> };</p><p> dog.sound(" is pooping");<br /> dog.sound.call(window, " is banking");<br /> dog.sound.call(dog, " is banking");<br /> dog.sound.apply(cat, [" miaowing"]);<br />
運行代碼
由此Prototype開發人員搞了一個非常有名的函數出來,bind!以下是它的一個最簡單的版本:
var bind = function(context, fn) { return function() { return fn.apply(context, arguments); } }
<br /> window.name = "Window";</p><p> var cat = {<br /> name: "Cat"<br /> };<br /> var dog = {<br /> name: "Dog",<br /> sound: function(word) {<br /> alert(this.name + word);<br /> }<br /> };<br /> var bind = function(context, fn) {<br /> return function() {<br /> return fn.apply(context, arguments);<br /> }<br /> }</p><p> var sound2 = bind(window,dog.sound);<br /> sound2(" is banking");<br /> var sound3 = bind(cat,dog.sound);<br /> sound3(" miaowing")<br />
運行代碼
不過為了面對更複雜的情況建議用以下版本。
function bind(context,fn) { var args = Array.prototype.slice.call(arguments, 2); return args.length == 0 ? function() { return fn.apply(context, arguments); } : function() { return fn.apply(context, args.concat.apply(args, arguments)); }; };
它還有一個孿生兄弟叫bindAsEventListener ,綁定事件對象,沒什麼好說的。
var bindAsEventListener = function(context, fn) {return function(e) {return fn.call(context, (e|| window.event));}}
Prototype的版本
Function.prototype.bind = function() { if (arguments.length Function.prototype.bind = function() { if (arguments.length <p><button type="button" title="runcode6" class="runcode direct">運行代碼</button></p><p>bind函數是如此有用,google早早已把它加入到Function的原型中了(此外還有inherits,mixin與partial)。</p><textarea id="runcode7" style="width:80%" rows="10"> //在chrome中var a = function(){};alert(a.bind)運行代碼
有綁定就有反綁定,或者叫剝離更好!例如原生對象的泛化方法我們是無法通過遍曆取出它們的。
for(var i in Array){ alert(i + " : "+ Array[i]) } for(var i in Array.prototype){ alert(i + " : "+ Array.prototype[i]) }<br /> for(var i in Array){<br /> alert(i + " : "+ Array[i])<br /> }<br /> for(var i in Array.prototype){<br /> alert(i + " : "+ Array.prototype[i])<br /> }<br />
運行代碼
要取出它們就需要這個東西:
var _slice = Array.prototype.slice; function unbind(fn) {//第一步取得泛化方法 return function(context) {//第二部用對應原生對象去重新調用! return fn.apply(context, _slice.call(arguments, 1)); }; };樣本以前也給過了,請見這裡
總結:
this 的值取決於 function 被調用的方式,一共有四種,
- 如果一個 function 是一個對象的屬性,該 funtion 被調用的時候,this 的值是這個對象。如果 function 調用的運算式包含句點(.)或是 [],this 的值是句點(.)或是 [] 之前的對象。如myObj.func 和myObj["func"] 中,func 被調用時的 this 是myObj。
- 如果一個 function 不是作為一個對象的屬性,那麼該 function 被調用的時候,this 的值是全域對象。當一個 function 中包含內部 function 的時候,如果不理解 this 的正確含義,很容易造成錯誤。這是由於內部 function 的 this 值與它外部的 function 的 this 值是不一樣的。解決辦法是將外部 function 的 this 值儲存在一個變數中,在內部 function 中使用它來尋找變數。
- 如果在一個 function 之前使用 new 的話,會建立一個新的對象,該 funtion 也會被調用,而 this 的值是新建立的那個對象。如function User(name) {this.name = name}; var user1 = new User("Alex"); 中,通過調用new User("Alex") ,會建立一個新的對象,以user1 來引用,User 這個 function 也會被調用,會在user1 這個對象中設定名為name 的屬性,其值是Alex 。
- 可以通過 function 的 apply 和 call 方法來指定它被調用的時候的 this 的值。 apply 和 call 的第一個參數都是要指定的 this 的值。由於它們存在,我們得以建立各種有用的函數。