深刻學習js之——this#6

深刻學習js系列是本身階段性成長的見證,但願經過文章的形式更加嚴謹、客觀地梳理js的相關知識,也但願可以幫助更多的前端開發的朋友解決問題,期待咱們的共同進步。javascript

若是以爲本系列不錯,歡迎點贊、評論、轉發,您的支持就是我堅持的最大動力。html


開篇

this關鍵字是JavaScript中最複雜的機制之一,它是一個很特別的關鍵字,被自動定義在全部函數的做用域 中。前端

跟別的語言截然不同的是:js的this老是指向一個對象,而具體指向哪一個對象是在運行時基於函數的執行環境動態綁定的,而非函數被聲明時候的環境。java

爲何要使用this

若是學習this的代價很大,可是對於咱們平時工做並不大,咱們幹嗎要付出這麼大的代價學習呢?的確,在介紹怎麼作以前咱們須要先明白爲何。chrome

除去不經常使用的with和eval的狀況,具體到實際應用中,this的指向大體能夠分爲如下四種:數組

  • 1.做爲對象的方法調用。
  • 2.做爲普通函數調用。
  • 3.構造器調用。
  • 4.Function.prototype.call 或者 Function.prototype.apply 下面分爲這四種狀況分別進行調用:

1.做爲對象的方法調用:

當函數做爲對象的方法被調用的時候,this指向該對象:瀏覽器

var obj = {
  a: 1,
  getA: function () {
    console.log(this === obj); // true 
    console.log(this.a); // 1;
  }
}
obj.getA();
複製代碼

2.做爲普通函數被調用:

當函數不做爲對象的屬性被調用時候,也就是咱們所說的普通函數方式,此時的this老是指向全局的對象。在瀏覽器的js裏面,這個全局對象是window對象。微信

// 建立全局的name對象 掛載在window上面
window.name = "globalName"; 
var getName = function () {
  return this.name;
}
console.log(getName()); // 輸出的是 globalName

複製代碼

或者閉包

window.name = "globalName";
var myObject = {
  name: "louis",
  getName: function () {
    return this.name;
  }
}
var getName = myObject.getName;
console.log(getName()); // "globalName"

複製代碼

有時候咱們會遇到一些困擾,好比在事件節點的div函數內部,有一個局部的callback方法,callback被做爲普通的函數被調用時,callback內部的this指向了window,但咱們每每想讓它的指向div節點.app

<div id = "div1">我是一個div</div>
複製代碼
window.id = "window";
document.getElementById('div1').onclick = function () {
  alert(this.id); // 輸出:'div1'
  var callback = function () {
    alert(this.id); // 輸出:'window'
  }
  callback();
};
複製代碼

此時有一種簡單的解決方案,能夠用一個變量保存div節點的引用:

document.getElementById('div1').onclick = function () {
  var that = this;    // 保存div的引用
  var callback = function () {
    alert(that.id);    // 輸出:'div1'
  }
  callback();
}

複製代碼

在ECMAScript 2015 中的嚴格模式下,這種狀況下的this指向已經被規定爲不會指向全局對象,而是undefined:

function func() {
 "use strict"
  alert(this); // undefined
}
func();

複製代碼

3.構造器的調用:

js中沒有類,可是能夠從構造器中建立對象,同時也提供了new運算符,使得構造器看起來像是一個類,

除了宿主提供的一些內置函數,大部分js函數均可以當成構造器使用,構造器的外表看起來和普通的函數沒有什麼區別,他們的區別在於調用方式,當使用new運算符調用函數的時候,該函數老是返回一個對象,一般狀況下,構造器裏面的this就是指向返回的這個對象。

var MyClass = function () {
  this.name = "louis";
}

var obj = new MyClass();
console.log(obj.name);
複製代碼

可是new調用構造器時候,還要注意一個問題,若是構造器顯式的返回了一個object對象那麼這次運算結果最終會返回這個對象,而不是咱們以前期待的this:

var MyClass = function () {
  this.name = 'sven';
  return {    // 顯式地返回一個對象
    name: 'anne'
  }
};

var obj = new MyClass();
alert(obj.name);     // 輸出:anne」
複製代碼

若是構造器不顯式的返回任何數據,或是返回一個非對象類型的數據,就不會形成上述問題。

var MyClass = function () {
  this.name = 'sven'
  return 'anne';    // 返回string類型
};

var obj = new MyClass();
alert(obj.name);     // 輸出:sven」

複製代碼

4.Function.prototype.call 或者 Function.prototype.apply調用

跟普通函數調用相比,用 Function.prototype.call 或者 Function.prototype.apply能夠動態的改變傳入函數的this:

var obj1 = {
  name: "louis",
  getName: function () {
    return this.name;
  }
}

var obj2 = {
  name: "kerry";
}

console.log(obj1.getName());     // 輸出: louis
console.log(obj1.getName.call(obj2));    // 輸出: kerry

複製代碼

call 和 apply 方法可以很好的體現 js的函數式語言特性 在js中幾乎每一次編寫函數式語言風格的代碼都離不開call和apply

5.丟失的this

下面看一個常常遇到的問題:

var obj = {
  myName: "louis",
  getName: function () {
    return this.name;
  }
}

console.log(obj.getName()); // louis;
var getName2 = obj.getName;
console.log(getName2()) // undefined
複製代碼

當調用obj.getName時,getName方法是做爲obj對象的屬性被調用的,根據上文提到的規律,此時的this指向obj對象,因此obj.getName()輸出'louis'。

當用另一個變量getName2來引用obj.getName,而且調用getName2時, 此時是普通函數調用方式,this是指向全局window的,window上面並無掛載任何屬性因此程序的執行結果是undefined。

再看另外一個例子,document.getElementById這個方法名實在有點過長,咱們大概嘗試過用一個短的函數來代替它,如同prototype.js等一些框架所作過的事情:

var getId = function (id) {
  return document.getElementById(id);
};

getId('div1');

複製代碼

咱們也許思考過爲何不能用下面這種更簡單的方式

var getId = document.getElementById;
getId( 'div1' );
複製代碼

如今不妨花1分鐘時間,讓這段代碼在瀏覽器中運行一次

<div id="div1">我是一個div</div>

複製代碼
var getId = document.getElementById;
getId( 'div1' );

複製代碼

在chrome friefox IE10 中執行事後就會發現,這段代碼拋出一個異常,這是由於不少引擎的document.getElementById 方法的內部實現中須要用到this,這個this原本被指望指向document,當getElementById方法做爲document對象的屬性被調用時,方法內部的this確實是指向document的。

可是當getId來引用document,getElementById以後,再調用getId,此時就成了普通的函數調用了,函數內部的this指向了window,而不是原來的document。

咱們能夠嘗試利用apply把document當作this傳遞給getId函數,修正 this指向問題。

document.getElementById = (function(func){
  return function(){
    return func.apply(document,arguments);
  }
})(document.getElementById);

var getId = document.getElementById;
var div = getId('div1');

alert(div.id);

複製代碼

深刻學習JavaScript系列目錄

歡迎添加個人我的微信討論技術和個體成長。

歡迎關注個人我的微信公衆號——指尖的宇宙,更多優質思考乾貨

相關文章
相關標籤/搜索