沐鳴代理:_JavaScript this 關鍵字詳解

一、前言

this關鍵字是JavaScript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函數的作用域中。對於那些沒有投入時間學習this機制的JavaScript開發者來說,this的綁定一直是一件非常令人困惑的事。

二、了解this

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

三、this到底是誰

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

1、函數執行時首先看函數名前面是否有”.”,有的話,”.”前面是誰,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();

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

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

3、給元素的某一個事件綁定方法,當事件觸發的時候,執行對應的方法,方法中的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不必太較真

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
};

4、在構造函數模式中,類中(函數體中)出現的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方法:

call方法的作用:

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

②call還可以傳值,在嚴格模式下和非嚴格模式下,得到值不一樣。

//在非嚴格模式下
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傳遞了兩個參數值1、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),前面四種全部讓步。


四、箭頭函數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

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/10078