從原理上詳解JS中的this

前言

this做爲JavaScript中一個大難點,不少初學者一開始都理解不了,或者理解不許確之類的。我在看了《javascript高級程序設計》以及一些大佬的博客後,對this有了新的認識,因而寫下來供你們參考。若是該博客有什麼錯誤的地方,請讀者指正。javascript

網傳的一句話:

this的指向在函數定義的時候是肯定不了的,只有函數執行的時候才肯定this到底指向誰,實際上this的最終指向的是最後調用它的對象java

因而就延伸出了肯定this指向的幾種方法:數組

  • this是上下文的屬性
  • 全局環境執行函數時,this是全局對象
  • 調用對象的方法時,this是指向該對象
  • 函數裏調用函數時,this是全局對象
  • 還有人說箭頭函數的this指向的是外面的this
  • 用new構造函數時,this指向的是實例對象
  • 能夠用call/apply/bind指向this

雖然上面說的並無錯,可是這麼多種狀況,在實際應用中也不必定可以準確的對應上,咱們不如來學一下this的原理,這樣遇到問題也能夠本身分析。bash

函數返回值的影響因素

  • 函數調用時傳入的參數(非箭頭函數)
  • 函數定義時的所在的做用域(箭頭函數)

在接下來的實例講解中,我將函數分爲 箭頭函數非箭頭函數。在非箭頭函數中,this是做爲一個隱式的參數傳入,而在箭頭函數中,this與其餘變量一視同仁,不會被特殊處理。app

經過例子學知識

非箭頭函數

例子1:

function a(){
    var name='ming';
    var age=13;
    console.log(this.name,age);
}
var name='ye';
var age=15;

a();    //輸出的結果是ye   13
複製代碼

咱們來分析一下:函數

a()做爲一個普通的函數,this是做爲一個隱式的參數傳入到函數參數中。它完整的寫法應該是window.a.call(window);因爲咱們沒有顯式的調用call來指定this的指向,那麼JS就會幫咱們完成這一步,讓調用該函數的對象做爲this。因此this在這裏指向的是window; 而函數的參數是在調用時候才肯定的,因此this.name輸出的是ye;可是對於age,他在函數裏面只是一個普通的變量,加上做用域鏈的查找規則,會使用離他最近的變量。與會這裏的age會輸出13。學習

例子2:

var name='outter';
let obj={
  name:'jack',
  say(){
    console.log(this.name)
  };
};

(1) obj.say();    //輸出的結果爲'jack'

let c=obj.say
(2) c()    //這裏輸出的是outter;
複製代碼

咱們先來看一下第一個輸出結果。 經過對象obj來調用say方法,這段代碼至關於obj.say.call(obj)。由於不經過call強行指定this指向,那麼JS會默認幫咱們把this指向調用它的對象,因此輸出的結果是jackui

第二個輸出結果咱們來看看上面的內存圖,變量C保存的是say的地址,也就是說咱們調用C的時候,並不通過obj的影響,那麼這種狀況也是至關於window.c.call(window),因此JS默認狀況下會讓this指向調用它的對象,也就是全局對象windowthis

注意!!!spa

注意!!!

注意!!!

這裏有一個須要注意的地方,當var name='outter' 改成let name='outter'的時候,狀況卻變了。

let name='outter'
let obj={
  name:'jack',
  say(){
    console.log(this.name)
  }
}

let c=obj.say
c()     //輸出的結果是undefined
複製代碼

這是由於let聲明的變量並無成爲window的變量,而var聲明的變量成爲了window的變量,因此this指向的對象window並無name這個屬性

例子3

var obj1={
    name:'pony',
    say(num=''){
        console.log(this.name+num)
    }
}

var obj2={
    name:'jack'
}

(1) obj1.say()         //輸出pony
(2) obj1.say.call(obj2,2)      //輸出jack2
(3) obj1.say.apply(obj2,[2])     //輸出jack2
複製代碼

callapply的最大區別就是apply傳入參數須要放在一個數組裏面。 這裏經過call顯式的指定this的指向,因而這裏就把obj2做爲say的調用對象了。那麼你可能會有疑問,爲何個人say寫在obj1裏面,用call就能夠改變指向呢。這個只是剛好say寫在了obj1裏面而已。

例子4

var name='one'
let a=function f1(){
  console.log(this.name)
}

function f1(){
  var name='inner'
  a()
}

f1()    //輸出的結果爲one,而不是inner
複製代碼

箭頭函數

箭頭函數不綁定this,他會捕捉定義的位置或者做用域鏈上最近的this,做爲本身的this。因此 call() / apply() / bind() 方法對於箭頭函數來講只是傳入參數,對它的 this 毫無影響。

注意,{}並不會造成一個做用域,在JS中只有全局做用域與函數生成的局部做用域。

例子1

var i=10;
var a={
  i:20,
  b:()=>{
    console.log(this.i)
  },
  c:function(){
  console.log(this.i)
}
}

a.b()      //輸出10
a.b.call(a)     //輸出10
a.c()      //輸出20
複製代碼

由於{}並不會造成做用域,箭頭函數沿着做用域鏈向上查找,在全局做用域找到了i,因而返回全局做用域的i,因爲箭頭函數並無綁定this,因此使用call指定也是沒有辦法改變this,由於它壓根沒有this。而對於a.c(),做爲非箭頭函數,上面的例子已經解析了。

var i=10
function f1(){
  i=12;
  var f=()=>{
    console.log(this.i)
  }
  f()
}

f1()      //輸出12
複製代碼

由於函數會造成局部做用域,箭頭函數查找的時候,在f1的做用域就能找到i了。

以上爲我的學習整理的內容,所有的例子都是通過我的檢驗的,歡迎一塊兒交流學習。

參考文獻

this - JavaScript | MDN

箭頭函數 - JavaScript | MDN

this指向

相關文章
相關標籤/搜索