DIV CSS 佈局教程網

 DIV+CSS佈局教程網 >> 網頁腳本 >> JavaScript入門知識 >> 關於JavaScript >> JavaScript教程:令人迷惑的關鍵字this
JavaScript教程:令人迷惑的關鍵字this
編輯:關於JavaScript     

今天的教程來自天才的Cody Lindley的新書:《JavaScript啟蒙教程 / JavaScript Enlightenment》。他討論了令人迷惑的關鍵字this,以及確定和設置this的值的方法。

概念性的概述this
當一個函數創建後,一個關鍵字this就隨之(在後台)創建,它鏈接到一個對象,而函數正是在這個對象中進行操作。換句話說,關鍵字this可在函數中使用,是對一個對象的引用,而函數正是該對象的屬性或方法。

讓我們來看這個對象:
  1. <!DOCTYPE html><html lang="en"><body><script>
  2. var cody = {
  3. living:true,
  4. age:23,
  5. gender:'male',
  6. getGender:function(){return cody.gender;}
  7. };

  8. console.log(cody.getGender()); // logs 'male'

  9. </script></body></html>

注意在函數getGender裡,由於在cody對象內部,我們可以通過.來獲取gender屬性(也就是cody.gender)。也可以用this來獲取cody對象,因為this正是指向cody對象。
  1. <!DOCTYPE html><html lang="en"><body><script>
  2. var cody = {
  3. living:true,
  4. age:23,
  5. gender:'male',
  6. getGender:function(){return this.gender;}
  7. };

  8. console.log(cody.getGender()); // logs 'male'

  9. </script></body></html>

this.gender中this指向cody對象,而getGender函數可以操作cody對象。
關於this的主題可能有點讓人感到困惑,其實不必如此。僅記住,通常,this指向的對象正是包含函數的對象,而不是函數本身(當然也有例外,例如采用關鍵字new或者call()和apply())。

重要提示

- 關鍵字this就像其他的變量,唯一不同就是你不能更改它。
- 不同於傳給函數的其他參數和變量,在調用函數的對象中,this是一個關鍵字(而不是屬性)。

如何確定this的值?

this傳遞給所有的函數,它的值取決於函數運行時何時被調用。這裡請注意,因為這是你需要記住的一個很特別的地方。
下面的代碼中myObject對象有個屬性sayFoo,它指向函數sayFoo。當在全局域中調用sayFoo函數時,this指向window對象。當myObject調用函數時,this指向的是myObject。

因為myObject有個叫foo的屬性,在這裡被使用。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var foo = 'foo';
  3. var myObject = {foo: 'I am myObject.foo'};

  4. var sayFoo = function() {
  5. console.log(this['foo']);
  6. };

  7. // give myObject a sayFoo property and have it point to sayFoo function
  8. myObject.sayFoo = sayFoo;
  9. myObject.sayFoo(); // logs 'I am myObject.foo' 12

  10. sayFoo(); // logs 'foo'

  11. </script></body></html>

很清楚,this的值取決於函數什麼時候被調用。myObject.sayFoo和sayFoo都指向同樣的函數,但sayFoo()調用的上下文不同,this的值也就不同。下面是類似的代碼,head對象(window)顯式使用,希望對你有用。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. window.foo = 'foo';
  3. window.myObject = {foo: 'I am myObject.foo'};
  4. window.sayFoo = function() { ! console.log(this.foo); };
  5. window.myObject.sayFoo = window.sayFoo;
  6. window.myObject.sayFoo();
  7. window.sayFoo();

  8. </script></body></html>

確保當你有多個引用指向同一個函數的時候,你清楚的知道this的值是隨調用函數的上下文的不同而改變。

重要提示

- 除了this以外的所有變量和參數都屬於靜態變量范圍(lexical scope)。

在嵌入函數內this指向head對象

你可能想知道在嵌入在另外一個函數的函數中使用this會發生什麼事。不幸的是在ECMA 3中,this不遵循規律,它不指向函數屬於的對象,而是指向head對象(浏覽器的window對象)。

在下面的代碼,func2和func3中的this不再指向myObject,而是head對象。
  1. <!DOCTYPE html><html lang="en"><body><script>
  2. var foo = {
  3. func1:function(bar){
  4. bar(); //logs window, not foo
  5. console.log(this);//the this keyword here will be a reference to foo object
  6. }
  7. };

  8. foo.func1(function(){console.log(this)});
  9. </script></body></html>

然而在ECMAScript 5中,這個問題將會得到修正。現在,你應該意識到這個問題,尤其是當你將一個函數的值傳遞到另一個函數時。

看看下面的代碼,將一個匿名函數傳給foo.func1,當在foo.func1中調用匿名函數(函數嵌套在另一個函數中),匿名函數中this將會指向是head對象。

現在你不會忘了,如果包含this的函數在另一個函數中,或者被另一個函數調用,this的值將會指向的是head對象(再說一次,這將在ECMAScript 5中被修正。)

解決嵌套函數的問題

為了使this的值不丟失,你可以在父函數中使用一個作用域鏈(scope chain)來保存對this進行引用。下面的代碼中,使用一個叫that的變量,利用它的作用域,我們可以更好的保存函數上下文。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var myObject = {
  3. myProperty:'Icanseethelight',
  4. myMethod:function() {
  5. var that=this; //store a reference to this (i.e.myObject) in myMethod scope varhelperFunctionfunction(){//childfunction
  6. var helperFunction function() { //childfunction
  7. //logs 'I can see the light' via scope chain because that=this
  8. console.log(that.myProperty); //logs 'I can see the light'
  9. console.log(this); // logs window object, if we don't use "that"
  10. }();
  11. }
  12. }

  13. myObject.myMethod(); // invoke myMethod

  14. </script></body></html>

控制this的值
this的值通常取決於調用函數的上下文(除非使用關鍵字new,稍後會為你介紹),但是你可以用apply()或call()指定觸發一個函數時this指向的對象,以改變/控制this的值。用這兩種方法就好像再說:“嘿,調用X函數,但讓Z對象來作this的值。”這樣做,JavaScript默認的this的值將被更改。

下面,我們創建了一個對象和一個函數,然後我們通過call()來觸發函數,所以函數中的this指向的是myOjbect。在myFunction函數中的this會操作myObject而不是head對象,這樣我們就改變了在myFunction中this指向的對象。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var myObject = {};

  3. var myFunction = function(param1, param2) {
  4. //setviacall()'this'points to my Object when function is invoked
  5. this.foo = param1;
  6. this.bar = param2;
  7. console.log(this); //logs Object{foo = 'foo', bar = 'bar'}
  8. };

  9. myFunction.call(myObject, 'foo', 'bar'); // invoke function, set this value to myObject

  10. console.log(myObject) // logs Object {foo = 'foo', bar = 'bar'}

  11. </script></body></html>

在上面的例子,我們用了call(),apply()也可適用於同樣用法,二者的不同之處在於參數如何傳給函數。用call(),參數用逗號分開,而用apply(),參數放在一個數組中傳遞。下面是同樣的代碼,但是用apply()。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var myObject = {};

  3. var myFunction = function(param1, param2) {
  4. //set via apply(), this points to my Object when function is invoked
  5. this.foo=param1;
  6. this.bar=param2;
  7. console.log(this); // logs Object{foo='foo', bar='bar'}
  8. };

  9. myFunction.apply(myObject, ['foo', 'bar']); // invoke function, set this value
  10. console.log(myObject); // logs Object {foo = 'foo', bar = 'bar'}

  11. </script></body></html>

在自定義構造函數中用this
當函數用關鍵字new來觸發,this的值–由於在構造函數中聲明–指向實例本身。換種說法:在構造函數中,我們可以在對象真正創建之前,就用this來指定對象。這樣看來,this值的更改和call()或apply()相似。

下面,我們構造了一個構造函數Person,this指向創建的對象。當Person的對象創建後,this指向這個對象,並將屬性name放在對象內,值為傳給這個構造函數的參數值(name)。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var Person = function(name) {
  3. this.name = name || 'johndoe'; // this will refer to the instanc ecreated
  4. }

  5. var cody = new Person('Cody Lindley'); // create an instance, based on Person constructor

  6. console.log(cody.name); // logs 'Cody Lindley'

  7. </script></body></html>

這樣,當用關鍵字new觸發構造函數時,this指向“要創建的對象”。那麼如果我們沒有用關鍵字new,this的值將會指向觸發Person的上下文——這時是head對象。讓我們來看看下面的代碼。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var Person = function(name) {
  3. this.name=name||'johndoe';
  4. }

  5. var cody = Person('Cody Lindley'); // notice we did not use 'new'
  6. console.log(cody.name); // undefined, the value is actually set at window.name
  7. console.log(window.name); // logs 'Cody Lindley'

  8. </script></body></html>

在prototype方法內的this指向構造實例
當一個方法作為一個構造函數的prototype屬性時,這個方法中的this指向觸發方法的實例。這裡,我們有一個Person()的構造函數,它需要person的全名(full name),為了獲得全名(full name),我們在Person.prototype中加入了一個whatIsMyFullName方法,所有的Person實例都繼承該方法。這個方法中的this指向觸發這個方法的實例(以及它的屬性)。

下面我創建了兩個Person對象(cody和lisa),繼承的whatIsMyFullName方法包含的this就指向這個實例。
  1. <!DOCTYPE html><html lang="en"><body><script>

  2. var Person = function(x){
  3. if(x){this.fullName = x};
  4. };

  5. Person.prototype.whatIsMyFullName = function() {
  6. return this.fullName; // 'this' refers to the instance created from Person()
  7. }

  8. var cody = new Person('cody lindley');
  9. var lisa = new Person('lisa lindley');

  10. // call the inherited whatIsMyFullName method, which uses this to refer to the instance
  11. console.log(cody.whatIsMyFullName(), lisa.whatIsMyFullName());

  12. /* The prototype chain is still in effect, so if the instance does not have a
  13. fullName property, it will look for it in the prototype chain.
  14. Below, we add a fullName property to both the Person prototype and the Object
  15. prototype. See notes. */

  16. Object.prototype.fullName = 'John Doe';
  17. var john = new Person(); // no argument is passed so fullName is not added to instance
  18. console.log(john.whatIsMyFullName()); // logs 'John Doe'

  19. </script></body></html>

在prototype對象內的方法裡使用this,this就指向實例。如果實例不包含屬性的話,prototype查找便開始了。
提示

- 如果this指向的對象不包含想要查找的屬性,那麼這時對於任何屬性都適用的法則在這裡也適用,也就是,屬性會沿著prototype鏈(prototype chain)上“尋找”。所以在我們的例子中,如果實例中不包含fullName屬性,那麼fullName就會查找Person.prototype.fullName,然後是Object.prototype.fullName。

XML學習教程| jQuery入門知識| AJAX入門| Dreamweaver教程| Fireworks入門知識| SEO技巧| SEO優化集錦|
Copyright © DIV+CSS佈局教程網 All Rights Reserved