js:this指向問題

var 作用域

先來看個簡單的例子:

source-js
var parent = function () {
  var name = "parent_name";
  var age = 13;

  var child = function () {
    var name = "child_name";
    var childAge = 0.3;

    // => child_name 13 0.3
    console.log(name, age, childAge);
  };

  child();

  // will throw Error
  // ReferenceError: childAge is not defined
  console.log(name, age, childAge);
};

parent();

直覺地,內部函數(shù)可以訪問外部函數(shù)的變量,外部不能訪問內部函數(shù)的變量。上面的例子中內部數(shù) child 可以訪問變量 age,而外部函數(shù) parent 不可以訪問 child 中的變量 childAge,因此會拋出沒有定義變量的異常。

有個重要的事,如果忘記var,那么變量就被聲明為全局變量了。

function foo() {
  value = "hello";
}
foo();
console.log(value); // 輸出hello
console.log(global.value) // 輸出hello

這個例子可以很正常的輸出hello,是因為value變量在定義時,沒有使用var關鍵詞,所以被定義成了全局變量。在 Node 中,全局變量會被定義在global對象下;在瀏覽器中,全局變量會被定義在window對象下。如果你確實要定義一個全局變量的話,請顯示地定義在global或者window對象上。這類不小心定義全局變量的問題可以被 jshint 檢測出來,如果你使用 sublime編輯器的話,記得裝一個SublimeLinter插件,這是插件支持多語言的語法錯誤檢測,js 的檢測是原生支持的。JavaScript 中,變量的局部作用域是函數(shù)級別的。不同于 C 語言,在 C 語言中,作用域是塊級別的。 JavaScript 中沒有塊級作用域。js 中,函數(shù)中聲明的變量在整個函數(shù)中都有定義。比如如下代碼段,變量 i 和 value 雖然是在 for 循環(huán)代碼塊中被定義,但在代碼塊外仍可以訪問 i 和value。

function foo() {
  for (var i = 0; i < 10; i++) {
    var value = "hello world";
  }
  console.log(i); //輸出10
  console.log(value);//輸出hello world
}
foo();

所以有種說法是:應該提前聲明函數(shù)中需要用到的變量,即,在函數(shù)體的頂部聲明可能用到的量,這樣就可以避免出現(xiàn)一些奇奇怪怪怪的 bug。但我個人不喜歡遵守這一點,一般都是現(xiàn)用現(xiàn)聲明的。這類錯誤的檢測交給 jshint 來做就好了。

閉包

閉包這個概念,在函數(shù)式編程里很常見,簡單的說,就是使內部函數(shù)可以訪問定義在外部函數(shù)中的變量。假如我們要實現(xiàn)一系列的函數(shù):add10,add20,它們的定義是int add10(int n)。為此我們構造了一個名為 adder 的構造器,如下:

var adder = function (x) {
  var base = x;
  return function (n) {
    return n + base;
  };
};

var add10 = adder(10);
console.log(add10(5));

var add20 = adder(20);
console.log(add20(5));

每次調用 adder 時,adder 都會返回一個函數(shù)給我們。我們傳給 adder 的值,會保存在一個名為base 的變量中。由于返回的函數(shù)在其中引用了 base 的值,于是 base 的引用計數(shù)被 +1。當返回函數(shù)不被垃圾回收時,則 base 也會一直存在。
我暫時想不出什么實用的例子來,如果想深入理解這塊,可以看看這篇

http://coolshell.cn/articles/6731.html

閉包的一個坑

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i);
  }, 5);
}

上面這個代碼塊會打印五個5出來,而我們預想的結果是打印 0 1 2 3 4。之所以會這樣,是因為 setTimeout 中的 i 是對外層 i 的引用。當 setTimeout 的代碼被解釋的時候,運行時只是記錄了 i 的引用,而不是值。而當 setTimeout 被觸發(fā)時,五個 setTimeout 中的 i 同時被取值,由于它們都指向了外層的同一個 i,而那個 i 的值在迭代完成時為 5,所以打印了五次5。為了得到我們預想的結果,我們可以把 i 賦值成一個局部的變量,從而擺脫外層迭代的影響。

for (var i = 0; i < 5; i++) {
  (function (idx) {
    setTimeout(function () {
      console.log(idx);
    }, 5);
  })(i);
}

this

在函數(shù)執(zhí)行時,this 總是指向調用該函數(shù)的對象。要判斷 this 的指向,其實就是判斷 this 所在的函數(shù)屬于誰。在《javaScript語言精粹》這本書中,把 this 出現(xiàn)的場景分為四類,簡單的說就是:

  • 有對象就指向調用對象
  • 沒調用對象就指向全局對象
  • 用new構造就指向新對象
  • 通過 apply 或 call 或 bind 來改變 this 的所指。

1)函數(shù)有所屬對象時:指向所屬對象函數(shù)有所屬對象時,通常通過.表達式調用,這時this自然指向所屬對象。比如下面的例子:

var myObject = {value: 100};
myObject.getValue = function () {
  console.log(this.value);  // 輸出 100

  // 輸出 { value: 100, getValue: [Function] },
  // 其實就是 myObject 對象本身
  console.log(this);

  return this.value;
};

console.log(myObject.getValue()); // => 100

getValue()屬于對象myObject,并由myOjbect進行.調用,因此this指向對象myObject。

  1. 函數(shù)沒有所屬對象:指向全局對象
var myObject = {value: 100};
myObject.getValue = function () {
  var foo = function () {
    console.log(this.value) // => undefined
    console.log(this);// 輸出全局對象 global
  };

  foo();

  return this.value;
};

console.log(myObject.getValue()); // => 100

在上述代碼塊中,foo函數(shù)雖然定義在getValue的函數(shù)體內,但實際上它既不屬于getValue也不屬于myObject。foo并沒有被綁定在任何對象上,所以當調用時,它的this指針指向了全局對象global。據(jù)說這是個設計錯誤。

3)構造器中的 this:指向新對象

js 中,我們通過new關鍵詞來調用構造函數(shù),此時 this 會綁定在該新對象上。

var SomeClass = function(){
  this.value = 100;
}

var myCreate = new SomeClass();

console.log(myCreate.value); // 輸出100

順便說一句,在 js 中,構造函數(shù)、普通函數(shù)、對象方法、閉包,這四者沒有明確界線。界線都在人的心中。

  1. apply 和 call 調用以及 bind 綁定:指向綁定的對象

apply() 方法接受兩個參數(shù)第一個是函數(shù)運行的作用域,另外一個是一個參數(shù)數(shù)組(arguments)。

call() 方法第一個參數(shù)的意義與 apply() 方法相同,只是其他的參數(shù)需要一個個列舉出來。

簡單來說,call 的方式更接近我們平時調用函數(shù),而 apply 需要我們傳遞 Array 形式的數(shù)組給它。它們是可以互相轉換的。

var myObject = {value: 100};

var foo = function(){
  console.log(this);
};

foo(); // 全局變量 global
foo.apply(myObject); // { value: 100 }
foo.call(myObject); // { value: 100 }

var newFoo = foo.bind(myObject);
newFoo(); // { value: 100 }

本文轉自《node.js包教不包會》---Lesson 11

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容