ES6中新增了箭頭函數這種語法,箭頭函數以其簡潔性和方便獲取this的特性,俘獲了大批粉絲兒
它也可能是面試中的寵兒, 我們關鍵要搞清楚 箭頭函數和普通函數中的this
一針見血式總結:
普通函數中的this:
1. this總是代表它的直接調用者(js的this是執行上下文), 例如 obj.func ,那麼func中的this就是obj
2.在默認情況(非嚴格模式下,未使用 'use strict'),沒找到直接調用者,則this指的是 window (約定俗成)
3.在嚴格模式下,沒有直接調用者的函數中的this是 undefined
4.使用call,apply,bind(ES5新增)綁定的,this指的是 綁定的對象
箭頭函數中的this
箭頭函數沒有自己的this, 它的this是繼承而來; 默認指向在定義它時所處的對象(宿主對象),而不是執行時的對象, 定義它的時候,可能環境是window; 箭頭函數可以方便地讓我們在 setTimeout ,setInterval中方便的使用this
下面通過一些例子來研究一下 this的一些使用場景
要整明白這些, 我們需要首先了解一下作用域鏈:
當在函數中使用一個變量的時候,首先在本函數內部查找該變量,如果找不到則找其父級函數,
最後直到window,全局變量默認掛載在window對象下
1.全局變量默認掛載在window對象下
-
<script>
-
var aa = 2;
-
alert(window.aa);
-
(function () {
-
aa = 3;
-
})();
-
alert(window.aa);
-
</script>
我們僅僅聲明瞭一個全局變量aa,但是打印出window.aa卻和aa保持一致,爲什麼呢?
眼見爲實, 我們使用 console.dir(window) 打印 window對象看看
我們可以看到在window屬性中,看到 aa 屬性了;此外,函數也適用於此情況,全局函數也會掛在在window對象下
我們常見的window的屬性和方法有: alert, location,document,parseInt,setTimeout,setInterval等,window的屬性默認可以省略window前綴!
2.在普通函數中,this指向它的直接調用者;如果找不到直接調用者,則是window
我們來看一些例子
示例1:
-
<script>
-
function test() {
-
console.log(this);
-
}
-
test();
-
</script>
結果是: window
原因: test()是一個全局函數,也就是說是掛在window對象下的,所以 test()等價於 window.test() ,所以此時的this是window
示例2:
-
<script>
-
var obj = {
-
say: function () {
-
setTimeout(function () {
-
console.log(this)
-
});
-
}
-
}
-
obj.say();
-
</script>
結果是: window
匿名函數,定時器中的函數,由於沒有默認的宿主對象,所以默認this指向window
問題: 如果想要在setTimeout/setInterval中使用這個對象的this引用呢?
用一個 變量提前把正確的 this引用保存 起來, 我們通常使用that = this, 或者 _this = this來保存我們需要的this指針!
-
<script>
-
var obj = {
-
func: function() {},
-
say: function () {
-
var that = this; //此時的this就是obj對象
-
setTimeout(function () {
-
console.log(this)
-
that.func()
-
});
-
}
-
}
-
obj.say();
-
</script>
我們也可以使用 func.bind(this) 給回調函數直接綁定宿主對象, bind綁定宿主對象後依然返回這個函數, 這是更優雅的做法
- <span style="font-family:'Times New Roman';"><script>
- var obj = {
- func: function() {},
- say: function () {
- // 此時的this就是obj對象
- setTimeout(function () {
- console.log(this)
- this.func()
- }.bind(this));
- }
- }
- obj.say(); // obj
- </script></span>
示例3(改變自360面試題):
-
window.val = 1;
-
var obj = {
-
val: 2,
-
dbl: function () {
-
this.val *= 2;
-
val *= 2;
-
console.log(val);
-
console.log(this.val);
-
}
-
};
-
// 說出下面的輸出結果
-
obj.dbl();
-
var func = obj.dbl;
-
func();
結果是: 2 4 8 8
<1> 12行代碼調用
val變量在沒有指定對象前綴,默認從函數中找,找不到則從window中找全局變量
即 val *=2 就是 window.val *= 2
this.val默認指的是 obj.val ;因爲 dbl()第一次被obj直接調用
<2>14行代碼調用
func() 沒有任何前綴,類似於全局函數,即 window.func調用,所以
第二次調用的時候, this指的是window, val指的是window.val
第二次的結果受第一次的影響
3.在嚴格模式下的this
-
<script>
-
function test() {
-
'use strict';
-
console.log(this);
-
}
-
test();
-
</script>
結果是: undefined
4.箭頭函數中的 this
-
<script>
-
var obj = {
-
say: function () {
-
setTimeout(() => {
-
console.log(this)
-
});
-
}
-
}
-
obj.say(); // obj
-
</script>
此時的 this繼承自obj, 指的是定義它的對象obj, 而不是 window!
示例(多層嵌套的箭頭函數):
-
<script>
-
var obj = {
-
say: function () {
-
var f1 = () => {
-
console.log(this); // obj
-
setTimeout(() => {
-
console.log(this); // obj
-
})
-
}
-
f1();
-
}
-
}
-
obj.say()
-
</script>
因爲f1定義時所處的函數 中的 this是指的 obj, setTimeout中的箭頭函數this繼承自f1, 所以不管有多層嵌套,都是 obj
示例(複雜情況: 普通函數和箭頭函數混雜嵌套)
-
<script>
-
var obj = {
-
say: function () {
-
var f1 = function () {
-
console.log(this); // window, f1調用時,沒有宿主對象,默認是window
-
setTimeout(() => {
-
console.log(this); // window
-
})
-
};
-
f1();
-
}
-
}
-
obj.say()
-
</script>
示例(嚴格模式下的混雜嵌套)
-
<script>
-
var obj = {
-
say: function () {
-
'use strict';
-
var f1 = function () {
-
console.log(this); // undefined
-
setTimeout(() => {
-
console.log(this); // undefined
-
})
-
};
-
f1();
-
}
-
}
-
obj.say()
-
</script>
結果都是undefined
關於this指向問題的討論一直是學習js不可忽視的重要部分,那些一個又一個圍繞this挖的筆試坑,彷彿永遠也填不完
- var obj={
- fn:function(){
- console.log(this);
- }
- }
- obj.fn();//object
再看下面這段
- var obj={
- fn:function(){
- setTimeout(function(){
- console.log(this);
- });
- }
- }
- obj.fn();//window
假如我們在這裏使用箭頭函數呢
- var obj={
- fn:function(){
- setTimeout(() => {
- console.log(this);
- });
- }
- }
- obj.fn();//object
爲了更加清楚的對比一般函數和箭頭函數this指向的區別,我們給對象添加變量
- var obj={
- num:3,
- fn:function(){
- setTimeout(function(){
- console.log(this.num);
- });
- }
- }
- obj.fn();//undefined
- //............................................................
- var obj1={
- num:4,
- fn:function(){
- setTimeout(() => {
- console.log(this.num);
- });
- }
- }
- obj1.fn();//4
接下來看更復雜的情況
多層嵌套的箭頭函數
- var obj1={
- num:4,
- fn:function(){
- var f=() => { //object,也就是指obj1
- console.log(this);
- setTimeout(() => {
- console.log(this);// //object,也就是指obj1
- });
- }
- f();
- }
- }
- obj1.fn();
- var obj1={
- num:4,
- fn:function(){
- var f=function(){
- console.log(this); //window,因爲函數f定義後並沒有對象調用,this直接綁定到最外層的window對象
- setTimeout(() => {
- console.log(this);//window,外層this綁定到了window,內層也相當於定義在window層(全局環境)
- });
- }
- f();
- }
- }
- obj1.fn();
- var obj1={
- num:4,
- fn:function(){
- var f=() => {
- console.log(this); //object,f()定義在obj1對象中,this就指向obj1,這就是箭頭函數this指向的關鍵
- setTimeout(function() {
- console.log(this);//window,非箭頭函數的情況下還是要看宿主對象是誰,如果沒有被對象調用,函數體中的this就綁定的window上
- });
- }
- f();
- }
- }
- obj1.fn();
總結:
1.箭頭函數的this綁定看的是this所在的函數定義在哪個對象下,綁定到哪個對象則this就指向哪個對象
2.如果有對象嵌套的情況,則this綁定到最近的一層對象上