你還沒搞懂this?

1、前言

this關鍵字是JavaScript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在全部函數的做用域中。對於那些沒有投入時間學習this機制的JavaScript開發者來講,this的指向一直是一件很是使人困惑的事。javascript

2、瞭解this

學習this的第一步是明白this既不指向函數自身也不指向函數的詞法做用域,你也許被這樣的解釋誤導過,但其實它們都是錯誤的。隨着函數使用場合的不一樣,this的值會發生變化。但總有一條原則就是JS中的this表明的是當前行爲執行的主體,在JS中主要研究的都是函數中的this,但並非說只有在函數裏纔有this,this其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。如何的區分this呢?java

3、this究竟是誰

這要分狀況討論,常見有五種狀況:git

一、函數執行時首先看函數名前面是否有".",有的話,"."前面是誰,this就是誰;沒有的話this就是window

function fn(){
  console.log(this);
}
var obj={fn:fn};
fn();//this->window
obj.fn();//this->obj
function sum(){
     fn();//this->window
}
sum();
var oo={
 sum:function(){
 console.log(this);//this->oo
       fn();//this->window
  }
};
oo.sum();

二、自執行函數中的this永遠是window

(function(){ //this->window })();
  ~function(){ //this->window }();

三、給元素的某一個事件綁定方法,當事件觸發的時候,執行對應的方法,方法中的this是當前的元素,除了IE6~8下使用attachEvent(IE一個著名的bug)

  • DOM零級事件綁定
oDiv.onclick=function(){
     //this->oDiv
  };
  • DOM二級事件綁定
oDiv.addEventListener("click",function(){
     //this->oDiv
  },false);
  • 在IE6~8下使用attachEvent,默認的this就是指的window對象
oDiv.attachEvent("click",function(){
       //this->window
  });

咱們大多數時候,遇到事件綁定,以下面例子這種,對於IE6~8下使用attachEvent沒必要太較真github

function fn(){
  console.log(this);
}
document.getElementById("div1").onclick=fn;//fn中的this就是#divl
document.getElementById("div1").onclick=function(){
console.log(this);//this->#div1
fn();//this->window
};

四、在構造函數模式中,類中(函數體中)出現的this.xxx=xxx中的this是當前類的一個實例

function CreateJsPerson(name,age){
//瀏覽器默認建立的對象就是咱們的實例p1->this
this.name=name;//->p1.name=name
this.age=age;
this.writeJs=function(){
console.log("my name is"+this.name +",i can write Js");
   };
//瀏覽器再把建立的實例默認的進行返回
}
var p1=new CreateJsPerson("尹華芝",48);

必需要注意一點:類中某一個屬性值(方法),方法中的this須要看方法執行的時候,前面是否有".",才能知道this是誰。你們不妨看下接下來的這個例子,就可明白是啥意思。數組

function Fn(){
this.x=100;//this->f1
this.getX=function(){
console.log(this.x);//this->須要看getX執行的時候才知道
   }
}
var f1=new Fn;
f1.getX();//->方法中的this是f1,因此f1.x=100
var ss=f1.getX;
ss();//->方法中的this是window ->undefined

5.call、apply和bind

咱們先來看一個問題,想在下面的例子中this綁定obj,怎麼實現?瀏覽器

var obj={name:"浪裏行舟"};
function fn(){
console.log(this);//this=>window
}
fn();
obj.fn();//->Uncaught TypeError:obj.fn is not a function

若是直接綁定obj.fn(),程序就會報錯。這裏咱們應該用fn.call(obj)就能夠實現this綁定obj,接下來咱們詳細介紹下call方法:app

  • call方法的做用:

①首先咱們讓原型上的call方法執行,在執行call方法的時候,咱們讓fn方法中的this變爲第一個參數值obj;而後再把fn這個函數執行。函數

②call還能夠傳值,在嚴格模式下和非嚴格模式下,獲得值不同。post

//在非嚴格模式下
var obj={name:"浪裏行舟 "};
function fn(num1,num2){
console.log(num1+num2);
console.log(this);
}
fn.call(100,200);//this->100 num1=200 num2=undefined
fn.call(obj,100,200);//this->obj num1=100 num2=200
fn.call();//this->window
fn.call(null);//this->window
fn.call(undefined);//this->window
//嚴格模式下 
fn.call();//在嚴格模式下this->undefined
fn.call(null);// 在嚴格模式 下this->null
fn.call(undefined);//在嚴格模式下this->undefined
  • **apply和call方法的做用是如出一轍的,都是用來改變方法的this關鍵字而且把方法

執行,並且在嚴格模式下和非嚴格模式下對於第一個參數是null/undefined這種狀況的規
律也是同樣的。**學習

二者惟一的區別:call在給fn傳遞參數的時候,是一個個的傳遞值的,而apply不是一個個傳,而是把要給fn傳遞的參數值統一的放在一個數組中進行操做。可是也至關子一個個的給fn的形參賦值。總結一句話:call第二個參數開始接受一個參數列表,apply第二個參數開始接受一個參數數組

fn.call(obj,100,200);
fn.apply(obj,[100,200]);
  • bind:這個方法在IE6~8下不兼容,和call/apply相似都是用來改變this關鍵字的,可是和這二者有明顯區別:
fn.call(obj,1,2);//->改變this和執行fn函數是一塊兒都完成了
fn.bind(obj,1,2);//->只是改變了fn中的this爲obj,而且給fn傳遞了兩個參數值一、2,
                     可是此時並無把fn這個函數執行
var tempFn=fn.bind(obj,1,2);
tempFn(); //這樣才把fn這個函數執行

bind體現了預處理思想:事先把fn的this改變爲咱們想要的結果,而且把對應的參數值也準備好,之後要用到了,直接的執行便可。

call和apply直接執行函數,而bind須要再一次調用。

var a ={
        name : "Cherry",
        fn : function (a,b) {
            console.log( a + b)
        }
    }
  var b = a.fn;
  b.bind(a,1,2)

上述代碼沒有執行,bind返回改變了上下文的一個函數,咱們必需要手動去調用:

b.bind(a,1,2)() //3

必需要聲明一點:遇到第五種狀況(call apply和bind),前面四種所有讓步。

4、箭頭函數this指向

箭頭函數正如名稱所示那樣使用一個「箭頭」(=>)來定義函數的新語法,但它優於傳統的函數,主要體現兩點:更簡短的函數而且不綁定this

var obj = {
    birth: 1990,
    getAge: function () {
        var b = this.birth; // 1990
        var fn = function () {
            return new Date().getFullYear() - this.birth; // this指向window或undefined
        };
        return fn();
    }
};

如今,箭頭函數徹底修復了this的指向,箭頭函數沒有本身的this,箭頭函數的this不是調用的時候決定的,而是在定義的時候處在的對象就是它的this

換句話說,箭頭函數的this看外層的是否有函數,若是有,外層函數的this就是內部箭頭函數的this,若是沒有,則this是window

<button id="btn1">測試箭頭函數this_1</button>
    <button id="btn2">測試箭頭函數this_2</button>
    <script type="text/javascript">   
        let btn1 = document.getElementById('btn1');
        let obj = {
            name: 'kobe',
            age: 39,
            getName: function () {
                btn1.onclick = () => {
                    console.log(this);//obj
                };
            }
        };
        obj.getName();
    </script>

上例中,因爲箭頭函數不會建立本身的this,它只會從本身的做用域鏈的上一層繼承this。其實能夠簡化爲以下代碼:

let btn1 = document.getElementById('btn1');
        let obj = {
            name: 'kobe',
            age: 39,
            getName: function () {
                console.log(this)
            }
        };
   obj.getName();

那假如上一層並不存在函數,this指向又是誰?

<button id="btn1">測試箭頭函數this_1</button>
    <button id="btn2">測試箭頭函數this_2</button>
    <script type="text/javascript">   
        let btn2 = document.getElementById('btn2');
        let obj = {
            name: 'kobe',
            age: 39,
            getName: () => {
                btn2.onclick = () => {
                    console.log(this);//window
                };
            }
        };
        obj.getName();
    </script>

上例中,雖然存在兩個箭頭函數,其實this取決於最外層的箭頭函數,因爲obj是個對象而非函數,因此this指向爲Window對象

因爲this在箭頭函數中已經按照詞法做用域綁定了,因此,用call()或者apply()調用箭頭函數時,沒法對this進行綁定,即傳入的第一個參數被忽略

var obj = {
    birth: 1990,
    getAge: function (year) {
        var b = this.birth; // 1990
        var fn = (y) => y - this.birth; // this.birth還是1990
        return fn.call({birth:2000}, year);
    }
};
obj.getAge(2018); // 28

文章於2018.9.25從新修改,若是文章對你有些許幫助,歡迎在個人GitHub博客點贊和關注,感激涕零!

參考文章

廖雪峯的官方網站

JS中的箭頭函數與this

this、apply、call、bind

相關文章
相關標籤/搜索