別低估本身,但,這道題,真的有點難

今天一個朋友轉給我一道題,讓我幫忙解釋解釋。前端

當我看到題目的時候,第一眼以爲賊簡單,可是看提問越到後面越懵逼了,在琢磨着能不能猜對了…………web

var age = 10;
var person={
   age:20,
   getAge(){
       var age = 30;
       return this.age;
    },
};
alert(age,age*2);

person.getAge(); 

var b = person.getAge;
b(); 

(person.getAge)(); 

(1,person.getAge)();

(1,person.getAge.bind(person))();

(person.getAge,person.getAge)();

(person.getAge=person.getAge)();

person.getAge.call(); 

person.getAge.call(person);

function getAge2({
    this.age = 40;
    console.log(person.getAge());
};
getAge2();
console.log(age);

function getAge3(){
    this.age = 50;
    this.getAge4 = ()=>{
        console.log(person.getAge.call(this));
    }
}
new getAge3().getAge4();
console.log(age);

function getAge4(){
    this.age = 60;
    this.getAge5 = ()=>{
    console.log(person.getAge.call(this));
    }
}
new getAge4().getAge5();
console.log(age);

var age2 = 10;
var person2={
   age2:20,
   getAge2:()=>{
       var age2 = 30;
       return this.age2;    
    },
};
console.log(person2.getAge2.call());
console.log(person2.getAge2.call(person2));
複製代碼

果不其然,我答錯了好多……微信

這道題目,題簡單的不能再簡單了,就是對象,函數,變量,可是問的很深,沒有紮實的知識,很難確切的回答上這些問題。app

要回答這些問題,關鍵仍是要深刻了解 this 和 逗號表達式。編輯器

首先咱們簡單回顧下這兩個很是重要的知識。最後再看看文末的綜合題目。函數

this

this 在 js 中很是重要。在筆試的時候,若是有考察基礎知識,那麼出現 this 的機率那可不是通常的高。學習

普通函數

一句話:誰調用就指向誰。ui

var person={
   age:20,
   getAge(){
       var age = 30;
       return this.age;
    },
};
person.getAge(); // 20
複製代碼

這個的 getAge 方法是 person 調用的,因此 this 指向 person,person.age 輸出爲 20;this

箭頭函數

一句話:調用者指向誰,則指向誰。spa

var age = 10;
var person={
   age:20,
   getAge:()=>{
       var age = 30;
       return this.age;
    },
};
person.getAge(); // 10
複製代碼

這個的 getAge 方法是 person 調用的,則 getAge 和 person 的指向一致,person 是 window 調用的(參照上述普通函數),因此 person 指向 window,所以 getAge 也指向 window,輸出 10。

強制改變 this 指向

一句話:你說指向誰就指向誰。

改變 this 指向,有 call,apply,bind 這幾種方法。

var age = 10;
var person={
   age:20,
   getAge:function(){
       var age = 30;
       return this.age;
    },
};
person.getAge.call(person);
複製代碼

這裏在執行 getAge 方法的時候,傳入了 person,那麼 getAge 的 this 指向 person,因此輸出 20。

逗號表達式

逗號表達式(Comma Operator)

(http://www.ecma-international.org/ecma-262/#sec-abstract-operations)

逗號表達式 能夠用於分割任何一個表達式,能夠用於分割函數參數等。

function test(){
    let a=1;
    return ++a,a++,a++;
}
console.log(test());
複製代碼

這裏逗號用於分割表達式,等價於:

function test(){
    let a=1;
    ++a;
    a++;
    return a++;
}
console.log(test());
複製代碼

天然,答案不用多說,是 3 ,由於 return 後面的是 a++,若是是 ++a ,那結果是 4(這裏不太明白的,自行學習 「++運算符」)。

再看原題

接下來咱們來一一解答上面的問題,我再貼一下題目。

var age = 10;
var person={
   age:20,
   getAge(){
       var age = 30;
       return this.age;
    },
};
複製代碼

alert(age,age*2)

這裏逗號分隔的是參數列表(Argument Lists),由於 alert 只接受一個參數,即第一個參數,後面的都忽略。所以彈窗是 10。

var b = person.getAge; b();

這個輸出結果是 10,主要是要區別 person.getAge(),差異在於當前是先賦值給一個變量 b,而後執行 b()。

等價於:

var b = person.getAge; 
window.b(); 
複製代碼

回到最前面的知識點,[誰調用,指向誰],這裏賦值變量以後,調用 b 方法的是 window,因此 this 指向 window,答案是 10。

(person.getAge)();

這裏括號只是起到一個分割的做用,並無實際意義,等價於 person.getAge()。因此答案是 20;

(1,person.getAge)();

這裏和上一個題目的差異是引入了逗號表達式。咱們知道逗號表達式返回的是最後一個值,即 person.getAge,注意這裏是表達式返回值。

等價於:

var a = (1,person.getAge);
a();
複製代碼

或者:

var a = (false||person.getAge);
a();
複製代碼

顯然,這裏 a 調用方是 window,因此答案是 10。注意這裏是非嚴格模式下。

後面題目若是牽涉到模式區別的時候一般都是指非嚴格模式,非嚴格模式與嚴格模式下的重要區別是當 this 爲 null 或者 undefined 的時候,是否會改成指向 window。 非嚴格模式下會改成指向 window,嚴格模式就仍然爲保持 null 或者 undefined。

"use strict"
var age = 10;
var person={
   age:20,
   getAge(){
       var age = 30;
       return this.age;
    },
};
console.log(person.getAge());// 輸出仍然是 20,this 指針指向 person
var a = (1,person.getAge);
a();//  Cannot read property 'age' of undefined , this 是 undefined
複製代碼

(1,person.getAge.bind(person))();

這道題是 逗號表達式 和 bind 知識點,參照上面的分析,等價於:

var a =(1,person.getAge.bind(person));
a();
複製代碼

這裏與上一道的區別是,返回的是一個 bind 以後的函數,a 方法已經強制指向 person 了,因此等價於:

person.getAge.bind(person)()
複製代碼

答案爲 20,this 指向 person。

(person.getAge,person.getAge)();

這個其實就是一個逗號表達式,返回最後一個項的值,這裏連續設置兩個 person.getAge,只是一個陷阱,前面的 person.getAge 對結果沒影響。咬定青山山不放鬆,堅決排除陷阱。

等價於:

(1,person.getAge)();
複製代碼

答案與上面一致,爲 10。

(person.getAge=person.getAge)();

這裏有點不同,意思爲:對象 person,給它設置了一個屬性 getAge(若是有 getAge 屬性,則從新賦值),將這個屬性 getAge 用 person.getAge 賦值。

括號裏面是一個賦值表達式,表達式的返回值,就是這個從新被賦值了 person.getAge 的 person 對象下面的 getAge 屬性。

等價於:

var person.getAge = person.getAge // 賦值
var a = person.getAge;  // 表達式返回值
a();
複製代碼

上面已經解釋,因此輸出爲 10。

person.getAge.call();person.getAge.call(person);

這裏使用 call 函數改變 this 指針。在不傳做用域參數的時候,在嚴格模式下 this 是 undefined,這裏是非嚴格模式。

call,apply,bind 均可以改變 this 指針,以下:

// 10  call 爲空,this 爲 undefined,從新指向爲 window
person.getAge.call();
// 20 this 指針指向 person
person.getAge.call(person); 
複製代碼

末尾大題

題目 1

function getAge2({
    this.age = 40;
    console.log(person.getAge());// 20
};
getAge2();
console.log(age);// 40
複製代碼

裏面的 person.getAge 仍然是誰調用執行誰,person.getAge 方法的 this 是指向 person,輸出是 20。 可是裏面有一個 this.age = 40 的賦值。

那麼這個 this 是啥?固然也是誰調用指向誰,這裏的 getAge2 是 window 調用,因此這裏的 this 是 window。

那麼這至關於將外層的 age 已經修改成 40 了,因此 console.log(age) ,已經變成 40 了。

題目 2

function getAge3(){
    this.age = 50;
    this.getAge4 = ()=>{
        console.log(person.getAge.call(this));// 50
    }
}
new getAge3().getAge4();
console.log(age); // 40
複製代碼

在 getAge3 裏面,定義了一個公有方法 getAge4,可是這裏是一個箭頭函數,裏面使用 call 修改函數 person.getAge 的 this 指針指向當前的 this,那麼當前 this 指向哪裏呢?

仍然是看誰調用了 getAge4,這裏是 new getAge3() 這個實例調用了 getAge4,因此 call 傳進去的 this 是指向 new getAge3(),這裏 this.age=50 被賦值爲 50 了,因此輸出爲 50。

可是全局的 age 並無被修改,與 題1 不同,這裏的 this 指向了實例對象,並非指向 window,因此全局的 age 仍然爲 40。

題 3

當讀到這裏的時候,你或許已經豁然開朗,以爲很理解了,那麼這一題你會作嗎?

var age2 = 10;
var person2={
   age2:20,
   getAge2:()=>{
       var age2 = 30;
       return this.age2;
    },
};
console.log(person2.getAge2.call()); // 10
console.log(person2.getAge2.call(person2)); // 10
複製代碼

這裏的核心差異是 getAge2 函數是使用了箭頭函數。按上面理解的,箭頭函數的 this 指向 person2 ?

錯了!不是如此。

其實是箭頭函數沒有本身的 this,既然沒有,那 call 怎麼能改變它自身的 this 指針呢?

參照文檔:(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)

因此上面不論是 call(),仍是 call(person2),都沒法修改 this 指針,因此二者都輸出全局的 10。

End

咱們再來看一道題,箭頭函數的 this 。

var age = 10;
var person={
   age:20,
   child:{
    age:40,
    getAge:function(){
       return this.age;
    },
   },
   child2:{
    age:40,
    getAge:()=>{
       return this.age;
    },
   },
   child3:function(){
    this.getAge = ()=>{
        return this.age;
    }
   }
};
console.log(person.child.getAge());// 40
console.log(person.child2.getAge()); // 10
console.log((new person.child3()).getAge()); // undfined
複製代碼

根據咱們上面的知識,能夠知道 person.child.getAge() 的 this 就是誰調用指向誰,這裏是 child 調用,因此指向 child ,輸出 40 。

咱們知道,箭頭函數是沒有本身的 this,那麼它的 this 是啥呢?

就是當前箭頭函數逐級向上查找,找到函數做用域的 this,則爲當前箭頭函數的 this。

因此,這裏 person.child2.getAge 函數的父級調用方是 child2,可是 child2 是對象,也沒有本身的 this 和 做用域,因此繼續向上查找 person,而後發現 person 也是對象,再繼續向上查找,找到 window 這個大 Boss 了,因此 this 就指向 window ,輸出爲 10。

(new person.child3()).getAge() 的 this ,同理向上一級查找,發現 new person.child3() 是個函數實例,因此 this 指向 child3 的這個實例,然而 child3 實例沒有 age 屬性,因此輸出 undefined。

這些題,確實很難!若是有不理解的,多看幾篇解答,相信你必定會豁然開朗!或者關注公衆號,加做者微信,一對一解答。

歡迎關注個人微信公衆號,一塊兒作靠譜前端!

follow-me
相關文章
相關標籤/搜索