使用 JavaScript 開發的時候,很多開發者多多少少會被 this
的指向搞蒙圈,但是實際上,關於 this
的指向,記住最核心的一句話:
哪個對象調用函數,函數里面的this指向哪個對象。
分幾種情況談論下:
一、普通函數調用
二、對象函數調用
三、構造函數調用
四、apply和call調用
五、箭頭函數調用
文章同步交流社區地址:http://www.mwcxs.top/page/427.html
一、普通函數調用
這個情況沒特殊意外,就是指向全局對象-window。
1、使用let
/*普通函數調用*/ let username = "程新松"; function fn(){ console.log(this.username); //undefined } fn();
使用node輸出的是:undefined
使用谷歌瀏覽器console輸出的是:undefined
2、使用var
var name = "程新松"; function fn(){ console.log(this.name); } fn();
使用node輸出的是:undefined
使用谷歌瀏覽器console輸出的是:程新松
3、使用window
window.username='程新松' function fn(){ console.log(this.username); } fn();
使用node輸出的是:報錯,window沒有定義
使用谷歌瀏覽器console輸出的是:
PS:為什么會出現這個錯誤呢,因為node里面沒有window 對象,瀏覽器中有window對象。
4、let和var區別:
(1)let
允許把變量的作用域限制在塊級域中;var 申明變量要么是全局的,要么是函數級的,而無法是塊級的。
let varClass = function(){ var name='程新松'; if(true){ var name='saucxs'; console.log(name); } console.log(name); } varClass(); // saucxs // saucxs
let letClass = function(){ let name='程新松'; if(true){ let name='saucxs'; console.log(name); } console.log(name); } letClass();
上面的結果說明了let只在{}內使用。
(2)先let后var
let subClass = function(){ let name='程新松'; if(true){ var name='saucxs'; console.log(name); } console.log(name); } subClass();
var 是函數級作用域,相當於一個作用域中有兩個n的變量了
var 作用於整個 subClass ,和let沖突了,let不能重復聲明,already been declared=已經被聲明
(3)先var后let
let subClass = function(){ var name='程新松'; if(true){ let name='saucxs'; console.log(name); } console.log(name); } subClass();
先申明var,再申明let,這個沒有問題。
二、對象函數調用
這個相信不難理解,就是哪個函數調用,this指向哪里。
/*對象函數調用*/ //window.name='程新松'; //var name='程新松'; let name='程新松'; let obj={ id:201102304, fn:function(){ console.log(this.name); //undefined console.log(this.id); //201102304 } } obj.fn();
,,
很明顯,第一次就是輸出 obj.name
,但是沒有這個name屬性,輸出的結果undefined。而第二次輸出obj.id,有這個id屬性,
輸出 201102304,因為 this
指向 obj
。
有一種情況需要注意:
/*需要注意的情況*/ let obj1={ a:111 } let obj2={ a:222, fn:function(){ console.log(this.a); } } obj1.fn=obj2.fn; obj1.fn(); //111
這個也不難理解,雖然 obj1.fn
是從 obj2.fn
賦值而來,但是調用函數的是 obj1
,所以 this
指向 obj1
。
三、構造函數調用
/*構造函數調用*/ let structureClass=function(){ this.name='程新松'; } let subClass1=new structureClass(); console.log(subClass1.name); let subClass=new structureClass(); subClass.name='成才'; console.log(subClass.name);
但是有一個坑,雖然一般不會出現,但是有必要提一下。
在構造函數里面返回一個對象,會直接返回這個對象,而不是執行構造函數后創建的對象
let structureClass=function(){ this.name='程新松'; return { username:'saucxs' } } let subClass1=new structureClass(); console.log(subClass1); console.log(subClass1.name);
四、apply和call調用
1、apply和call簡單來說就是會改變傳入函數的this。
/*apply和call調用*/ let obj1={ name:'程新松' }; let obj2={ name:'saucxs', fn:function(){ console.log(this.name); } } obj2.fn.call(obj1);
此時雖然是 obj2
調用方法,但是使用 了 call
,動態的把 this
指向到 obj1
。相當於這個 obj2.fn
這個執行環境是 obj1
。
call
和 apply
兩個主要用途:
1.改變 this
的指向(把 this
從 obj2
指向到 obj1
)
2.方法借用( obj1
沒有 fn
,只是借用 obj2
方法)
2、call與apply區別
call
和 apply
的作用,完全一樣,唯一的區別就是在參數上面。
call
接收的參數不固定,第一個參數是函數體內 this
的指向,第二個參數以下是依次傳入的參數。
apply接收兩個參數,第一個參數也是函數體內 this
的指向。第二個參數是一個集合對象(數組或者類數組)
/*apply和call區別*/ let fn=function(a,b,c){ console.log(a,b,c); } let arrArray=[1,2,3]; fn.call(window,arrArray); fn.apply(window,arrArray);
五、箭頭函數調用
首先不得不說,ES6 提供了箭頭函數,增加了我們的開發效率,但是在箭頭函數里面,沒有 this
,箭頭函數里面的 this
是繼承外面的環境。
/*箭頭函數調用*/ let obj={ name:'程新松', fn:function(){ setTimeout(function(){console.log(this.name)}) } } obj.fn();
不難發現,雖然 fn() 里面的 this 是指向 obj ,但是,傳給 setTimeout 的是普通函數, this 指向是 window , window 下面沒有 name ,所以這里輸出 underfind 。
換成箭頭函數
//換成箭頭函數 let obj={ name:"程新松", fn:function(){ setTimeout(()=>{console.log(this.name)}); } } obj.fn();
這次輸出 程新松 是因為,傳給 setTimeout 的是箭頭函數,然后箭頭函數里面沒有 this ,所以要向上層作用域查找,在這個例子上, setTimeout 的上層作用域是 fn 。而 fn 里面的 this 指向 obj ,所以 setTimeout 里面的箭頭函數的 this ,指向 obj 。所以輸出 程新松。