我們幾乎每天都在和this打交道,先不去管他的概念,看一個小栗子,預熱一下
this初探
⚠️約定:
因為使用let和const宣告的變數沒有掛載到全域性變數下,所以在全域性下宣告的變數我們使用var
var heroName = '黃蓉'
function hero() {
const heroName = "黃藥師"
console.log(this)//window
console.log(this.heroName);//=>黃蓉
}
hero()
複製程式碼
我們執行hero(),通過列印我們發現
this指向的是window全域性,所以this.heroName,應當在全域性下查詢heroName,所以輸出=>黃蓉
當全域性下沒有heroName呢?this指向的是全域性,全域性沒有就是沒有,那就是undefined
function hero() {
const heroName = "黃藥師"
console.log(this)//window
console.log(this.heroName);//=>undefined
}
hero()
複製程式碼
我們繼續看一個栗子
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指向這個物件)
再看一個栗子
var heroName = '黃蓉'
function hero1() {
const heroName = "黃藥師"
this.hero2()
}
function hero2() {
console.log(this.heroName);
}
hero1()
複製程式碼
先不關心輸出 我們在hero2中的console.log前加一個debugger,分別看下兩個this的指向
可以發現。兩個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 出現上面這種情況,是由函式呼叫方式決定的- 作為一個函式進行的呼叫
- 作為一個物件的方法進行的呼叫
- 作為構造器進行的呼叫
- 通過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);
複製程式碼
使用自定義構造器構造一個物件需要四步
- 建立一個新物件
const hero = new Hero("黃藥師", "東邪", "碧海潮生曲");
複製程式碼
- 將建構函式的作用域賦給新物件,設定原型鏈(因此this就指向了這個新物件hero)
hero.__proto__=Hero.prototype;
複製程式碼
- 執行建構函式Hero中的程式碼(為這個新物件hero新增屬性和方法)
- 返回新物件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時,以下幾點需要注意
- 當return的是基本資料型別時,返回不變
- 當return的沒有返回時,預設返回undefined,返回不變
- 當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