理解了this指針,才算JavaScript入門,你入門了嗎?

本文共 2025 字,看完只需 8 分鐘

概述

前面的文章講解了 JavaScript 中的執(zhí)行上下文,作用域,變量對(duì)象,this 的相關(guān)原理,但是我后來在網(wǎng)上看到一些例題的時(shí)候,依然沒能全做對(duì),說明自己有些細(xì)節(jié)還沒能掌握,本文就結(jié)合例題進(jìn)行深入實(shí)踐,討論函數(shù)在不同的調(diào)用方式 this 的指向問題。
老規(guī)矩,先給結(jié)論 1 和 結(jié)論2:

this 始終指向最后調(diào)用它的對(duì)象

“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。

特別提示:

本文的例子,最好自己在瀏覽器控制臺(tái)中去試一遍,看完過兩天就會(huì)忘的,一定要實(shí)踐。

1、隱式綁定
var name = "window";

function foo() {
    var name = "inner";

    console.log(this.name);
}

foo();  // ?

復(fù)制代碼輸出:

window

例 1 中,非嚴(yán)格模式,由于 foo 函數(shù)是在全局環(huán)境中被調(diào)用,this 會(huì)被默認(rèn)指向全局對(duì)象 window;
所以符合了我們的結(jié)論一:

this 始終指向最后調(diào)用它的對(duì)象

2、一般函數(shù)和箭頭函數(shù)的對(duì)象調(diào)用

// 例 2

var name = "window";

var person = {
    name: "inner",
    show1: function () {
        console.log(this.name);
    },
    show2: () => {
        console.log(this.name);
    }
}

person.show1();  // ?
person.show2();  // ?

復(fù)制代碼輸出:

inner
window

person.show1() 輸出 inner 沒毛病,person.show2() 箭頭函數(shù)為什么會(huì)輸出 window 呢。MDN 中對(duì) this 的定義是:

箭頭函數(shù)不綁定 this, 箭頭函數(shù)不會(huì)創(chuàng)建自己的this,它只會(huì)從自己的作用域鏈的上一層繼承this。

再看本文前面給的結(jié)論:

“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。

由于 JS 中只有全局作用域和函數(shù)作用域,箭頭函數(shù)在定義時(shí)的上一層作用域是全局環(huán)境,全局環(huán)境中的 this 指向全局對(duì)象本身,即 window。

3、call
// 例 3
var name = 'window'

var person1 = {
  name: 'person1',
  show1: function () {
    console.log(this.name)
  },
  show2: () => console.log(this.name),
  show3: function () {
    return function () {
      console.log(this.name)
    }
  },
  show4: function () {
    return () => console.log(this.name)
  }
}
var person2 = { name: 'person2' }

person1.show1()  // ?
person1.show1.call(person2)  // ?

person1.show2()  // ?
person1.show2.call(person2)  // ?

person1.show3()()  // ?
person1.show3().call(person2)  // ?
person1.show3.call(person2)()  // ?

person1.show4()()  // ?
person1.show4().call(person2)  // ?
person1.show4.call(person2)()  // ?

復(fù)制代碼輸出:

person1
person2
window
window
window
person2
window
person1
person1
person2

上面 10 行打印,你對(duì)了幾個(gè)呢?

首先:
person1.show1()person1.show1.call(person2) 輸出結(jié)果應(yīng)該沒問題,call 的作用就是改變了調(diào)用的對(duì)象 為 person2。
其次:
person1.show2()person1.show2.call(person2),由于調(diào)用的是箭頭函數(shù),和本文例 2 中是一樣的,箭頭函數(shù)定義時(shí) this 指向的是上一層,也就是全局對(duì)象, 并且 箭頭函數(shù)不綁定自己的 this, 所以通過 call()apply() 方法調(diào)用箭頭函數(shù)時(shí),只能傳遞參數(shù),不能傳遞新的對(duì)象進(jìn)行綁定。故打印的值都是 window。

進(jìn)而:

function foo () {
    return function () {
      console.log(this.name)
    }
  }

foo()();

復(fù)制代碼博客前面的文章有講過閉包,上面這段代碼也是典型的閉包運(yùn)用,可以看作:

function foo () {
    return function () {
      console.log(this.name)
    }
  }
var bar = foo();
bar();

復(fù)制代碼所以,很明顯,被返回的內(nèi)部函數(shù)其實(shí)是在全局環(huán)境下被調(diào)用的?;氐角懊婵次覀兊慕Y(jié)論 1,this 始終指向最后調(diào)用函數(shù)的對(duì)象,這句話的關(guān)鍵詞應(yīng)該是什么?我覺得應(yīng)該是 調(diào)用,什么時(shí)候調(diào)用,誰調(diào)用。
再回過頭來看:
person1.show3()() 輸出 window,因?yàn)閮?nèi)部函數(shù)在全局環(huán)境中被調(diào)用。
person1.show3().call(person2) 輸出 person2, 因?yàn)閮?nèi)部函數(shù)被 person2 對(duì)象調(diào)用了。
person1.show3.call(person2)() 輸出 window,也是因?yàn)閮?nèi)部函數(shù)在全局環(huán)境中被調(diào)用。

最后:
重點(diǎn)理解結(jié)論 2:

“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。

show4: function () {
    return () => console.log(this.name)
  }

復(fù)制代碼這段代碼中,箭頭函數(shù)是在 外層函數(shù) show4 執(zhí)行后才被定義的。為什么?可以翻看我前面關(guān)于作用域鏈,執(zhí)行上下文,變量對(duì)象的文章,函數(shù)在進(jìn)入執(zhí)行階段時(shí),會(huì)先查找內(nèi)部的變量和函數(shù)聲明,將他們作為變量對(duì)象的屬性,關(guān)聯(lián)作用域鏈,并綁定 this 指向。
所以:
person1.show4()() 輸出 person1,因?yàn)橥獠亢瘮?shù)在執(zhí)行時(shí)的 this 為 person1, 此時(shí)定義了內(nèi)部函數(shù),而內(nèi)部函數(shù)為外部函數(shù)的 this。
person1.show4().call(person2) 輸出 person1,箭頭函數(shù)不會(huì)綁定 this, 所以 call 傳入 this 指向無效。
person1.show4.call(person2)() 輸出 person2,因?yàn)橥獠亢瘮?shù)在執(zhí)行時(shí)的 this 為 person2,此時(shí)定義了內(nèi)部函數(shù),而內(nèi)部函數(shù)為外部函數(shù)的 this。

4、構(gòu)造函數(shù)中的 this
// 例 4
var name = 'window'

function Person (name) {
  this.name = name;
  this.show1 = function () {
    console.log(this.name)
  }
  this.show2 = () => console.log(this.name)
  this.show3 = function () {
    return function () {
      console.log(this.name)
    }
  }
  this.show4 = function () {
    return () => console.log(this.name)
  }
}

var personA = new Person('personA')
var personB = new Person('personB')

personA.show1()  // 
personA.show1.call(personB)  // 

personA.show2()  // 
personA.show2.call(personB)  // 

personA.show3()()  // 
personA.show3().call(personB)  // 
personA.show3.call(personB)()  // 

personA.show4()()  // 
personA.show4().call(personB)  // 
personA.show4.call(personB)()  //

復(fù)制代碼輸出:

personA
personB

personA
personA

window
personB
window

personA
personA
personB

例 4 和 例 3 大致一樣,唯一的區(qū)別在于兩點(diǎn):

  1. 構(gòu)造函數(shù)中 this 指向被創(chuàng)建的實(shí)例
  2. 構(gòu)造函數(shù),也是函數(shù),所以存在作用域,所以里面的箭頭函數(shù),它們的 this 指向,來自于上一層,就不再是全局環(huán)境 window, 而是構(gòu)造函數(shù) 的 this。
5、setTimeout 函數(shù)
// 例 5
function foo(){
  setTimeout(() =>{
    console.log("id:", this.id)
      setTimeout(() =>{
        console.log("id:", this.id)
      }, 100);
  }, 100);
}

foo.call({id: 111});  // 

復(fù)制代碼輸出:

111
111

注意一點(diǎn):
setTimeout 函數(shù)是在全局環(huán)境被 window 對(duì)象執(zhí)行的,但是 foo 函數(shù)在執(zhí)行時(shí),setTimtout 委托的匿名箭頭函數(shù)被定義,箭頭函數(shù)的 this 來自于上層函數(shù) foo 的調(diào)用對(duì)象, 所以打印結(jié)果才為 111;

6、setTimeout 函數(shù) 2
// 例 6
function foo1(){
  setTimeout(() =>{
    console.log("id:", this.id)
      setTimeout(function (){
        console.log("id:", this.id)
      }, 100);
  }, 100);
}


function foo2(){
  setTimeout(function() {
    console.log("id:", this.id)
      setTimeout(() => {
        console.log("id:", this.id)
      }, 100);
  }, 100);
}

foo1.call({ id: 111 });  // ?
foo2.call({ id: 222 });  // ?

復(fù)制代碼輸出:

111
undefined
undefined
undefined

例 5 中已經(jīng)提到,setTimeout函數(shù)被 window 對(duì)象調(diào)用,如果
是普通函數(shù),內(nèi)部的 this 自然指向了全局對(duì)象下的 id, 所以為 undefined,如果是箭頭函數(shù),this 指向的就是外部函數(shù)的 this。

7、嵌套箭頭函數(shù)
// 例 7
function foo() {
  return () => {
    return () => {
      return () => {
        console.log("id:", this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()();  // 
var t2 = f().call({id: 3})();  // 
var t3 = f()().call({id: 4});  // 

復(fù)制代碼輸出:

1
1
1

這段代碼是為了鞏固我們的結(jié)論2:

“箭頭函數(shù)”的this,總是指向定義時(shí)所在的對(duì)象,而不是運(yùn)行時(shí)所在的對(duì)象。

  1. foo.call({}) 在執(zhí)行時(shí),內(nèi)部的第一層箭頭函數(shù)才被定義
  2. 箭頭函數(shù)無法綁定 this, 所以 call 函數(shù)指定 this 無效
  3. 箭頭函數(shù)的 this 來自于上一層作用域(非箭頭函數(shù)作用域)的 this

總結(jié)

有本書中有提到,當(dāng)理解 JavaScript 中的 this 之后,JavaScript 才算入門,我深以為然。

原因是,要徹底理解 this, 應(yīng)該是建立在已經(jīng)大致理解了 JS 中的執(zhí)行上下文,作用域、作用域鏈,閉包,變量對(duì)象,函數(shù)執(zhí)行過程的基礎(chǔ)上。
求點(diǎn)贊,求關(guān)注~


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 9月4號(hào),今日早讀文章由丁香園@相學(xué)長投稿分享。正文從這開始~日常開發(fā)中,我們經(jīng)常用到this。例如用Jquery...
    web_afei閱讀 312評(píng)論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,663評(píng)論 1 32
  • 1. this之謎 在JavaScript中,this是當(dāng)前執(zhí)行函數(shù)的上下文。因?yàn)镴avaScript有4種不同的...
    百里少龍閱讀 1,094評(píng)論 0 3
  • 堅(jiān)持分享第六十九天 今日感恩 感恩楊老師非常樂意和我調(diào)換一節(jié)課,我才有時(shí)間給孩子做午飯,孩子放學(xué)回來就能吃上熱飯。...
    夜色雨色閱讀 86評(píng)論 0 0
  • 萊特幣是一種點(diǎn)對(duì)點(diǎn)的電子加密貨幣。他的創(chuàng)造和轉(zhuǎn)讓是基于一種開源的加密協(xié)議,不受到任何中央機(jī)構(gòu)的管理。 萊特幣受到了...
    正版江湖走馬閱讀 311評(píng)論 0 0

友情鏈接更多精彩內(nèi)容