一篇文章搞懂this

開篇說明

隨着科技的發展和歷史的變遷,我對 this 的理解也越來越深入了,還記得之前寫的一篇關於 this 的文章,自己看起來都感覺“味同嚼蠟”😂,所以更新一篇,將功補過,哈哈~~,我們拿五種情況來說明 this 的指向問題,優先級從低到高,但是在這之前我們先說一下 js 的作用域鏈的問題。

作用域鏈

案例一

function outer() {
  var name = 'outer';
  function inner() {
    console.log( 'name: ', name );
  }
  inner();
}

outer();
  1. 大家想一下打印出來的 name 是啥呢?
  • 此時假裝過大家思考了10秒~
  • 打印出來的是 outer
  • 原因是什麼呢?
  • 明明 inner 函數中是沒有 name 這個屬性的,他怎麼會打印出外層的 outer 函數中定義的 name 呢?
  • 因爲 js 在執行函數的過程中,如果某個變量找不到,他會沿着「!!!:函數定義時」的作用域鏈去外層找是否有這個變量,如果外層沒有,繼續向外層的外層去找找找。。。最終找到全局對象上(一般情況下是window)如果還沒有,那麼就會報錯;在我們這個案例中,查找的順序爲:inner ? -> outer ? -> window ?
  • 如果按照上面的作用域鏈最終到 window 對象上也沒有找到這個變量,那麼瀏覽器就會拋出錯誤Error: name is not defiend

五種情況的 this 指向

一、函數的自然執行

先解釋下什麼叫自然執行?

function func() {
  console.log( 'this: ', this );
};

let obj = {
  a: func
};

func(); // 自然執行
obj.a(); // 不是自然執行
func.call( obj ); // 不是自然執行

總結:自然執行:爲函數直接執行,這樣: func();

1.直接執行,不通過 '.' 操作符執行
2.不使用 new 操作符
3.不使用 call、apply、bind

總之:就是光突突地執行:func();

函數自然執行的情況下:

  1. 在非嚴格模式下的 js 中,指向window
  2. 在嚴格模式「“use strict”」下,指向 undefined
  3. 在 node 環境下,指向 node 的全局對象 global

二、被對象的訪問操作符訪問出來,並執行

什麼是對象的訪問操作符?其實就是訪問對象中屬性的兩種方式,我們平時肯定也很常用,請看下面

function func() {
  console.log( 'this: ', this );
}

let obj = {
  // a 屬性的值指向了 func 函數的引用,這裏不清楚可以查閱下 js 中的基本數據類型和複雜數據類型的區別
  name: 'obj',
  a: func 
}

obj.a(); // 被 obj 通過「對象.鍵名」的方式訪問出來並執行
obj[a](); // 被 obj 通過「對象[鍵名]」的方式訪問出來執行

上面兩種執行方式中,this 的指向被隱式(偷偷)綁定到 obj 上了

這種情況我們只需要記住一句話:函數不管被多少層訪問符,層層訪問到,他總是指向離他最近的調用對象上,下面舉例說明:

function func() {
  console.log( 'this ', this );
}

let obj = {
  name: 'obj',
  father: {
    name: 'father',
    son: {
      a: func,
      name: 'son'
	}
  }
}

obj.father.son.a(); // this 指向 son ,因爲是離他最近的調用者

三、通過 new 操作符執行函數

也就是調用構造函數的方式了,也就是我們用 javaScript 造出來的“類”的概念。

function func(name) {
  this.name = name;
  console.log( 'this: ', this );
}

let a = new func('a'); // this 指向 a 對象
let b = new func('b'); // this 指向 b 對象

只要通過 new 操作符調用函數,那麼 this 會隱式(偷偷地)指向返回的實例對象。

下面出一道題,大家可以嘗試思考一下,打印出的 this 是什麼?

可以運行輸出驗證一下自己的答案哦!

function outer() {
  let name = 'outer';
  function inner() {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  return inner;
}

let a = outer();
a();

四、call、apply、bind

先來說下 call 和 apply

call 和 apply 都是可以把其他函數借過來自己用一下,而且還不用在自己身上擴展這個方法,舉例說明:

let a = {
  name: '我是a'
}

let b = {
  name: 'b',
  sayName: function() {
    console.log( 'name: ', this.name );
  }
}

b.call( a ); // name: '我是a'
b.apply( a ); // name: '我是a'

// call 和 apply 的區別就是傳參方式的不同
// 這裏不做過多說明
// b.call( a, 1, 2, 3 )  b.apply( a, [1, 2, 3] );

我們的兩個對象 a 和 b,他們都有名爲 name 的屬性,但是 a 中沒有 sayName 的方法,但是他可以借用 b 的 sayName 方法,但是在借用 b 的 sayName 方法的過程中,this 被隱式(偷偷地)指向了被借用的對象上,在這裏就是 a

bind

只講解最簡單的使用方式,也是借用函數,並隱式改變 this 的指向,但跟 call 和 apply 不同的是:bind 返回一個新函數,舉例說明:

let a = {
  name: 'a',
  log: function () {
    console.log( 'this: ', this );
  }
}

let b = {
  name: 'b',
}

let c = a.log.bind( b );
c(); // this 指向對象 b
// `c()`  相當於執行  `a.log.call( b )`

五、ES6 的箭頭函數

我們平時寫代碼的時候經常聽到一種說法:你的項目中要不就都用箭頭函數,要不就都不用,別把 this 搞亂,對吧,但是我們搞清楚 this 在不同執行情景下的指向之後,哪種寫法我們就可以自己斟酌了,或者混着用😏(不推薦)

我們先看下概念:箭頭函數的 this 指向「定義時」離他最近的一個非箭頭函數的 this 指向的對象,舉例說明:

function outer() {
  let name = 'outer';
  let inner = () => {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  inner();
}

outer(); // this: window
// 1.箭頭函數的 this 指向「`定義時`」離他最近的一個非箭頭函數的 this 指向的對象
// 2.那麼我們從 inner 向外層看,外層 outer 就是離 inner 最近的非箭頭函數,
//   那麼他的 this 指向就是箭頭函數 inner 的 this 指向
// 3.我們分析 outer,outer 是自然執行的(上面說過自然執行了),那麼 outer 的 this 
//   指向爲 window,所以 inner 的 this 執行也是 window
function outer() {
  let name = 'outer';
  let inner = () => {
    let name = 'inner';
    console.log( 'this: ', this );
  }
  inner();
}

outer.call( outer ); // this: ?
// 這個留給大家自己分析了,歡迎提問哦~

this 的優先級問題

ES6箭頭函數 > call、apply、bind > 自然執行、對象訪問操作符執行、new 調用
!:大於號中間的多種情況爲並列關係

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章