你還沒搞懂this?
小編推薦:Fundebug專注於JavaScript、微信小程式、微信小遊戲,Node.js和Java實時BUG監控。真的是一個很好用的bug監控費服務,眾多大佬公司都在使用。
一、前言
this關鍵字是JavaScript中最複雜的機制之一。它是一個很特別的關鍵字,被自動定義在所有函式的作用域中。對於那些沒有投入時間學習this機制的JavaScript開發者來說,this的指向一直是一件非常令人困惑的事。
二、瞭解this
學習this的第一步是明白this既不指向函式自身也不指向函式的詞法作用域,你也許被這樣的解釋誤導過,但其實它們都是錯誤的。隨著函式使用場合的不同,this的值會發生變化。但總有一條原則就是JS中的this代表的是當前行為執行的主體
三、this到底是誰
這要分情況討論,常見有五種情況:
1、函式執行時首先看函式名前面是否有".",有的話,"."前面是誰,this就是誰;沒有的話this就是window
function fn(){ console.log(this); } var obj={fn:fn}; fn();//this->window obj.fn();//this->obj function sum(){ fn();//this->window } sum(); var oo={ sum:function(){ console.log(this);//this->oo fn();//this->window } }; oo.sum();
2、自執行函式中的this永遠是window
(function(){ //this->window })();
~function(){ //this->window }();
3、給元素的某一個事件繫結方法,當事件觸發的時候,執行對應的方法,方法中的this是當前的元素,除了IE6~8下使用attachEvent(IE一個著名的bug)
- DOM零級事件繫結
oDiv.onclick=function(){
//this->oDiv
};
- DOM二級事件繫結
oDiv.addEventListener("click",function(){ //this->oDiv },false);
- 在IE6~8下使用attachEvent,預設的this就是指的window物件
oDiv.attachEvent("click",function(){
//this->window
});
我們大多數時候,遇到事件繫結,如下面例子這種,對於IE6~8下使用attachEvent不必太較真
function fn(){
console.log(this);
}
document.getElementById("div1").onclick=fn;//fn中的this就是#divl
document.getElementById("div1").onclick=function(){
console.log(this);//this->#div1
fn();//this->window
};
4、在建構函式模式中,類中(函式體中)出現的this.xxx=xxx中的this是當前類的一個例項
function CreateJsPerson(name,age){
//瀏覽器預設建立的物件就是我們的例項p1->this
this.name=name;//->p1.name=name
this.age=age;
this.writeJs=function(){
console.log("my name is"+this.name +",i can write Js");
};
//瀏覽器再把建立的例項預設的進行返回
}
var p1=new CreateJsPerson("尹華芝",48);
必須要注意一點:類中某一個屬性值(方法),方法中的this需要看方法執行的時候,前面是否有".",才能知道this是誰。大家不妨看下接下來的這個例子,就可明白是啥意思。
function Fn(){
this.x=100;//this->f1
this.getX=function(){
console.log(this.x);//this->需要看getX執行的時候才知道
}
}
var f1=new Fn;
f1.getX();//->方法中的this是f1,所以f1.x=100
var ss=f1.getX;
ss();//->方法中的this是window ->undefined
5.call、apply和bind
我們先來看一個問題,想在下面的例子中this繫結obj,怎麼實現?
var obj={name:"浪裡行舟"};
function fn(){
console.log(this);//this=>window
}
fn();
obj.fn();//->Uncaught TypeError:obj.fn is not a function
如果直接繫結obj.fn(),程式就會報錯。這裡我們應該用fn.call(obj)就可以實現this繫結obj,接下來我們詳細介紹下call方法:
- call方法的作用:
①首先我們讓原型上的call方法執行,在執行call方法的時候,我們讓fn方法中的this變為第一個引數值obj;然後再把fn這個函式執行。
②call還可以傳值,在嚴格模式下和非嚴格模式下,得到值不一樣。
//在非嚴格模式下
var obj={name:"浪裡行舟 "};
function fn(num1,num2){
console.log(num1+num2);
console.log(this);
}
fn.call(100,200);//this->100 num1=200 num2=undefined
fn.call(obj,100,200);//this->obj num1=100 num2=200
fn.call();//this->window
fn.call(null);//this->window
fn.call(undefined);//this->window
//嚴格模式下
fn.call();//在嚴格模式下this->undefined
fn.call(null);// 在嚴格模式 下this->null
fn.call(undefined);//在嚴格模式下this->undefined
- **apply和call方法的作用是一模一樣的,都是用來改變方法的this關鍵字並且把方法
執行,而且在嚴格模式下和非嚴格模式下對於第一個引數是null/undefined這種情況的規
律也是一樣的。**
兩者唯一的區別:call在給fn傳遞引數的時候,是一個個的傳遞值的,而apply不是一個個傳,而是把要給fn傳遞的引數值統一的放在一個數組中進行操作。但是也相當子一個個的給fn的形參賦值。總結一句話:call第二個引數開始接受一個引數列表,apply第二個引數開始接受一個引數陣列
fn.call(obj,100,200);
fn.apply(obj,[100,200]);
- bind:這個方法在IE6~8下不相容,和call/apply類似都是用來改變this關鍵字的,但是和這兩者有明顯區別:
fn.call(obj,1,2);//->改變this和執行fn函式是一起都完成了
fn.bind(obj,1,2);//->只是改變了fn中的this為obj,並且給fn傳遞了兩個引數值1、2,
但是此時並沒有把fn這個函式執行
var tempFn=fn.bind(obj,1,2);
tempFn(); //這樣才把fn這個函式執行
bind體現了預處理思想:事先把fn的this改變為我們想要的結果,並且把對應的引數值也準備好,以後要用到了,直接的執行即可。
call和apply直接執行函式,而bind需要再一次呼叫。
var a ={
name : "Cherry",
fn : function (a,b) {
console.log( a + b)
}
}
var b = a.fn;
b.bind(a,1,2)
上述程式碼沒有執行,bind返回改變了上下文的一個函式,我們必須要手動去呼叫:
b.bind(a,1,2)() //3
必須要宣告一點:遇到第五種情況(call apply和bind),前面四種全部讓步。
四、箭頭函式this指向
箭頭函式正如名稱所示那樣使用一個“箭頭”(=>)來定義函式的新語法,但它優於傳統的函式,主要體現兩點:更簡短的函式並且不繫結this。
var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
};
現在,箭頭函式完全修復了this的指向,箭頭函式沒有自己的this,箭頭函式的this不是呼叫的時候決定的,而是在定義的時候處在的物件就是它的this。
換句話說,箭頭函式的this看外層的是否有函式,如果有,外層函式的this就是內部箭頭函式的this,如果沒有,則this是window。
<button id="btn1">測試箭頭函式this_1</button>
<button id="btn2">測試箭頭函式this_2</button>
<script type="text/javascript">
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
btn1.onclick = () => {
console.log(this);//obj
};
}
};
obj.getName();
</script>
上例中,由於箭頭函式不會建立自己的this,它只會從自己的作用域鏈的上一層繼承this。其實可以簡化為如下程式碼:
let btn1 = document.getElementById('btn1');
let obj = {
name: 'kobe',
age: 39,
getName: function () {
console.log(this)
}
};
obj.getName();
那假如上一層並不存在函式,this指向又是誰?
<button id="btn1">測試箭頭函式this_1</button>
<button id="btn2">測試箭頭函式this_2</button>
<script type="text/javascript">
let btn2 = document.getElementById('btn2');
let obj = {
name: 'kobe',
age: 39,
getName: () => {
btn2.onclick = () => {
console.log(this);//window
};
}
};
obj.getName();
</script>
上例中,雖然存在兩個箭頭函式,其實this取決於最外層的箭頭函式,由於obj是個物件而非函式,所以this指向為Window物件
由於this在箭頭函式中已經按照詞法作用域綁定了,所以,用call()或者apply()呼叫箭頭函式時,無法對this進行繫結,即傳入的第一個引數被忽略:
var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);
}
};
obj.getAge(2018); // 28
文章於2018.9.25重新修改,如果文章對你有些許幫助,歡迎在我的GitHub部落格點贊和關注,感激不盡!
參考文章
廖雪峰的官方網站
JS中的箭頭函式與this
this、apply、call、bind
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了9億+錯誤事件,得到了Google、360、金山軟體、百姓網等眾多知名使用者的認可。歡迎免費試用!