解析javascript中的this

咱們幾乎天天都在和this打交道,先不去管他的概念,看一個小栗子,預熱一下javascript

this初探

⚠️約定:
由於使用let和const聲明的變量沒有掛載到全局變量下,因此在全局下聲明的變量咱們使用var前端

var heroName = '黃蓉'
function hero() {
  const heroName = "黃藥師"
  console.log(this)//window
  console.log(this.heroName);//=>黃蓉
}

hero()
複製代碼

咱們執行hero(),經過打印咱們發現java

this指向的是window全局,因此this.heroName,應當在全局下查找heroName,因此輸出=>黃蓉
當全局下沒有heroName呢?this指向的是全局,全局沒有就是沒有,那就是undefined數組

function hero() {
  const heroName = "黃藥師"
  console.log(this)//window
  console.log(this.heroName);//=>undefined
}

hero()
複製代碼

咱們繼續看一個栗子app

var heroName = '黃蓉'
function hero() {
  const heroName = "黃藥師"
  console.log(this)//oHero
  console.log(this.heroName);//=>歐陽鋒
}
const oHero={
  heroName:'歐陽鋒',
  hero
}
oHero.hero()
複製代碼

提示:在聲明oHero中的hero中,使用了ES6標準,當key和value同名是,能夠像上面這樣簡寫
上面的栗子中,調用hero()的環境變了 ,根據輸出咱們發現,this的指向變了
this的指向是oHero對象,因此this.heroName=>歐陽鋒,一樣的當oHero下沒有heroName,毫無懸念,就會輸出=>undefined
若是前面兩個小栗子搞懂了,咱們就繼續
一個栗子函數

var heroName = '黃蓉'

function hero() {
  console.log(this.heroName);
}
const oHero1 = {
  heroName:'郭靖',
  hero
};

hero()//=>黃蓉
oHero1.hero()//=>郭靖
複製代碼

如今咱們嘗試着給this下一個定義:學習

this指的是函數運行時所在的環境,若是一個函數內部有this,this就會指向一個對象,指向哪一個對象呢?取決於這個函數的執行環境。 補充(若是在全局下調用這個函數,則this指向全局,若是在某個對象下調用this,則this指向這個對象)ui

再看一個栗子this

var heroName = '黃蓉'
function hero1() {
  const heroName = "黃藥師"
  this.hero2()
}
function hero2() {
  console.log(this.heroName);
}
hero1()
複製代碼

先不關心輸出 咱們在hero2中的console.log前加一個debugger,分別看下兩個this的指向 spa

能夠發現。兩個this均指向window 在腦中跑一遍這段代碼,但願你還清醒 咱們一塊兒捋一下: 全局下執行hero1() 在hero1中,this指向的是全局=>window, 全局下存在hero2(), 在hero2中,this指向哪裏?取決因而哪裏調用的hero2(),是在hero1()中調用的hero2(), 因此hero2中的this指向hero1?輸出=>黃藥師? 可是咱們經過debugger得出,hero2中的this指向的是window,因此輸出必然是=>黃蓉 爲何呢? 咱們再看一個栗子

function hero(){
  console.log(this.heroName);
}
var HERO1 = {
  heroName : '黃蓉',
  hero
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
var HERO3 = {
  heroName : '郭靖',
  HERO2
}
HERO3.HERO2.HERO1.hero()//=>黃蓉
複製代碼

我相信不會有人寫出這樣的代碼,除了我! 這依然是個調用鏈 咱們在輸出前打一個斷點

this指向了誰?不是最初的調用方HERO3,而是上一次,或者所最後一次=>HERO1 出現上面這種狀況,是由函數調用方式決定的

  1. 做爲一個函數進行的調用
  2. 做爲一個對象的方法進行的調用
  3. 做爲構造器進行的調用
  4. 經過apply()、call()、bind()函數進行的調用

做爲一個函數調用this指向window,做爲一個對象的方法調用,this指向當前調用的對象

做爲一個函數進行調用

回顧以前的栗子

var heroName = '黃蓉'

function hero() {
  console.log(this.heroName);
}
const oHero1 = {
  heroName:'郭靖',
  hero
};

hero()//=>黃蓉
oHero1.hero()//=>郭靖
複製代碼

hero(),是做爲一個函數進行調用的,因此this指向window oHero1.hero(), 是做爲一個函數的方法調用的,因此this指向oHero1對象

做爲一個對象的方法進行調用

繼續看一個栗子

function hero1(){
  this.hero2()
}
function hero2(){
  console.log(this.heroName);//Uncaught TypeError: this.hero2 is not a function
}
var HERO1 = {
  heroName : '黃蓉',
  hero1
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
HERO2.HERO1.hero1()
複製代碼

報錯了

HERO1對象下調用的hero1(),因此hero1()中this指向的是HERO1,可是HERO1中不存在方法hero2(),因此報錯 稍做修改

function hero1(){
  HERO3.hero2()
}
function hero2(){
  console.log(this.heroName);//=>郭靖
}
var HERO1 = {
  heroName : '黃蓉',
  hero1
}
var HERO2 = {
  heroName : '郭靖',
  HERO1
}
var HERO3 = {
  heroName : '郭靖',
  hero2
}
HERO2.HERO1.hero1()
複製代碼

咱們彷佛總結出來一點小竅門,咱們從最後的輸出反推執行順序 是誰調用的hero2(),是HERO3,因此hero2,中的this指向HERO3,因此輸出是=>郭靖

上面說了前兩種狀況,其實(1)是(2)的一種特殊狀況, 即看成爲一個函數調用的時候,就是做爲window對象的一個方法進行調用

一個小栗子進行簡單回顧

var heroName = "郭靖";

function hero1() {
  console.log(this.heroName);
}
var HERO1 = {
  heroName: "黃蓉",
  hero1:hero1
};
HERO1.hero1();//=>黃蓉
hero1();//=>郭靖
複製代碼

都是執行hero1()方法,一樣都是輸出this.heroName,卻獲得了不一樣的結果

this的指向是在函數執行的時候定義的,而不是在函數建立時定義的

基於上面的栗子,咱們再看一個栗子

var heroObj = {
  heroName: "郭靖",
  heroFoo: {
    heroName: "黃蓉",
    hero: function() {
      console.log(this.heroName); 
    }
  }
};

heroObj.heroFoo.hero();//=>黃蓉

var h = heroObj.heroFoo.hero;
h();//=>undefined
複製代碼

爲了直觀表達咱們作一下改動

//部分代碼省略
window.heroObj.heroFoo.hero();//=>黃蓉

var h = heroObj.heroFoo.hero;
window.h();//=>undefined
複製代碼

輸出的結果是相同的,我要表達的內容也很就很直觀明顯了, 這些對象和方法,最終都會掛載到window對象下,咱們看這一句

window.heroObj.heroFoo.hero();//=>黃蓉
複製代碼

hero()中的this是指向調用它的對象,那是哪一個對象調用的hero()呢?

這是個問題

這是個問題?

這不是個問題

this指向的是最後調用它的對象

上面的栗子

window.heroObj.heroFoo.hero();//=>黃蓉
複製代碼

最後調用hero()的是heroFoo,因此hero()中this指向了heroFoo對象

var h = heroObj.heroFoo.hero;
window.h();//=>undefined
複製代碼

最後調用h()的是window對象,因此hero()中this指向了window對象

綜上

this的指向是在函數執行的時候定義的,而不是在函數建立時定義的,this指向的是最後調用它的對象

下面討論剩下的兩種狀況

做爲構造器進行調用

以前在介紹面向對象的時候,談一談javascript面向對象,討論過,使用構造函數來建立對象

function Hero(name, nickname, skill) {
  this.name = name;
  this.nickname = nickname;
  this.doSth = function() {
   return skill
  };
}
const hero = new Hero("黃藥師", "東邪", "碧海潮生曲");
console.log(hero);
複製代碼

使用自定義構造器構造一個對象須要四步

  1. 建立一個新對象
const hero = new Hero("黃藥師", "東邪", "碧海潮生曲");
複製代碼
  1. 將構造函數的做用域賦給新對象,設置原型鏈(所以this就指向了這個新對象hero)
hero.__proto__=Hero.prototype;
複製代碼
  1. 執行構造函數Hero中的代碼(爲這個新對象hero添加屬性和方法)
  2. 返回新對象hero

說this指向hero,Hero內全部針對this的操做,都會發生在hero上面

console.log(hero.name);//=>黃藥師
console.log(hero.nickname);//=>東邪
console.log(hero.doSth());//=>碧海潮生曲
複製代碼

構造函數是不須要return的, return在普通函數中也不是必須存在的,咱們知道,在普通函數中,若是沒有手動return ,會默認return undefined 可是若是在構造函數中使用了return,會存在一些坑 咱們一塊兒來填坑 咱們把上面的栗子進行簡化

function Hero(name) {
  this.heroName = name;
  return 123
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
複製代碼
function Hero(name) {
  this.heroName = name;
  return null
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
複製代碼
function Hero(name) {
  this.heroName = name;
  return undefined
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>郭靖
複製代碼

再也不進行一一列舉,上面return的都是基本數據類型 繼續看 數組類型

function Hero(name) {
  this.heroName = name;
  return []
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>undefined
複製代碼

object類型

function Hero(name) {
  this.heroName = name;
   return {
          heroName: "黃蓉"
        };
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>黃蓉
複製代碼

funciton類型

function Hero(name) {
  this.heroName = name;
   return  function(){}
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>undefined
複製代碼

funciton升級

function Hero(name) {
  this.heroName = name;
  return (function() {
    return  {heroName:'黃蓉'}
  })()
}
const hero = new Hero("郭靖");
console.log(hero.heroName);//=>黃蓉
複製代碼

總結一下 構造函數會改版this的指向,指向經過new實例化出來的對象 構造函數中不須要return,當存在return時,如下幾點須要注意

  1. 當return的是基本數據類型時,返回不變
  2. 當return的沒有返回時,默認返回undefined,返回不變
  3. 當return的是引用類型時,返回的是這引用類型

經過apply()、call()、bind()進行調用

以前在講面向對象的時候,對call進行過討論,javascript 面向對象之一篇文章搞定call()方法 它們兩個也是用來改變this的指向的,

爲何要改變this的指向呢?

call

看一個栗子

const hero1 = {
    name: "歐陽鋒",
    doSth: function (skill) {
        console.log(`${this.name}學習${skill}`);
    }
};

const hero2 = {
    name: "洪七公"
};
hero1.doSth('九陰真經')//=>歐陽鋒學習九陰真經
複製代碼

執行了hero1對象下面的doSth方法,並傳入參數「九陰真經」,最後輸出=>歐陽鋒學習九陰真經 結合上面的介紹講解,咱們知道,doSth中的this是指向的hero1,這個沒問題吧

好了,如今hero2下面有一個「洪七公」,「洪七公」也想調用hero1下面的doSth方法,怎麼辦呢?

const hero1 = {
    name: "歐陽鋒",
    doSth: function (skill) {
        console.log(`${this.name}學習${skill}`);
    }
};

const hero2 = {
    name: "洪七公"
};
hero1.doSth.call(hero2, "降龍十八掌");//=>洪七公學習降龍十八掌
複製代碼

doSth是由hero1調用的,默認狀況下doSth中的this指向的是hero1,可是使用了call,因此,this的指向變了,指向了call方法中的第一個參數hero2,

apply

apply呢?apply和call非常相似

const hero1 = {
  name: "歐陽鋒",
  doSth: function(skill, favourite) {
    console.log(`${this.name}學習${skill}`);
    console.log(`${this.name}喜歡${favourite}`);
  }
};

const hero2 = {
  name: "洪七公"
};
hero1.doSth.apply(hero2, ["降龍十八掌", "吃雞"]);
hero1.doSth.call(hero2, "降龍十八掌", "吃雞");
複製代碼

這兩種寫法等效,只是傳入的參數格式略有不一樣

hero1.doSth.apply(hero2, ["降龍十八掌", "吃雞"]);
hero1.doSth.call(hero2, "降龍十八掌", "吃雞");
複製代碼

bind

bind也能夠改變this的指向,在用法上和call和apply略有不一樣,bind的使用更加靈活

const hero1 = {
	name: "歐陽鋒",
	doSth: function(skill, favourite) {
		console.log(`${this.name}學習${skill}`);
		console.log(`${this.name}喜歡${favourite}`);
	}
};

const hero2 = {
	name: "洪七公"
};
const foo = hero1.doSth.bind(hero2, "降龍十八掌", "吃雞");
foo();
複製代碼

以前說的call和apply都是當即執行,而bind不會當即執行,須要手動執行,因此bind的使用更加靈活 不止於此 上面的foo方法在調用的時候能夠額外的傳入參數

const hero1 = {
  name: "歐陽鋒",
  doSth: function(skill, favourite, q, n, b) {
    console.log(`${this.name}學習${skill}`);//=>洪七公學習降龍十八掌
    console.log(`${this.name}喜歡${favourite}`);//=>洪七公喜歡吃雞
    console.log(q, n, b);//=>1 2 3
  }
};

const hero2 = {
  name: "洪七公"
};
const foo = hero1.doSth.bind(hero2, "降龍十八掌", "吃雞");
foo(1, 2, 3);
複製代碼

我是陌上寒,咱們一塊兒學前端

END

相關文章
相關標籤/搜索