1. 38道關(guān)于this的面試題

        共 21794字,需瀏覽 44分鐘

         ·

        2021-12-24 19:26

        作者 | 戰(zhàn)場小包
        來源 | https://juejin.cn/post/701947082005754676

        前言

        當一個函數(shù)調(diào)用時,會創(chuàng)建一個執(zhí)行上下文,這個上下文包括函數(shù)調(diào)用的一些信息(調(diào)用棧,傳入?yún)?shù),調(diào)用方式),this就指向這個執(zhí)行上下文。

        this不是靜態(tài)的,也并不是在編寫的時候綁定的,而是在運行時綁定的。它的綁定和函數(shù)聲明的位置沒有關(guān)系,只取決于函數(shù)調(diào)用的方式。

        本篇文章有點長,涉及到很多道面試題,有難有簡單,如果能耐心的通讀一編,我相信以后this都不成問題。在文章的最開始,陳列一下本篇文章涉及的內(nèi)容,保證讓大家不虛此行。

        默認綁定
        隱式綁定
        隱式綁定丟失
        顯式綁定
        顯式綁定應用
        new綁定
        箭頭函數(shù)綁定
        綜合題

        this指向哪里

        在JavaScript中,要想完全理解this,首先要理解this的綁定規(guī)則,this的綁定規(guī)則一共有5種:

        默認綁定
        隱式綁定
        顯式(硬)綁定
        new綁定
        ES6新增箭頭函數(shù)綁定

        下面來一一介紹以下this的綁定規(guī)則。

        1、默認綁定

        默認綁定通常是指函數(shù)獨立調(diào)用,不涉及其他綁定規(guī)則。非嚴格模式下,this指向window,嚴格模式下,this指向undefined。

        01、題目1.1:非嚴格模式

        var foo = 123;function print(){  this.foo = 234;    console.log(this); // window  console.log(foo); // 234}print();

        非嚴格模式,print()為默認綁定,this指向window,所以打印window和234。

        這個foo值可以說道兩句:如果學習過預編譯的知識,在預編譯過程中,foo和print函數(shù)會存放在全局GO中(即window對象上),所以上述代碼就類似下面這樣:

        window.foo = 123function print() {    this.foo = 234;    console.log(this);   console.log(window.foo);}window.print()

        02、題目1.2:嚴格模式

        把題目1.1稍作修改,看看嚴格模式下的執(zhí)行結(jié)果。

        "use strict"可以開啟嚴格模式

        "use strict";var foo = 123;function print(){    console.log('print this is ', this);     console.log(window.foo)    console.log(this.foo);}console.log('global this is ', this);print();

        注意事項:開啟嚴格模式后,函數(shù)內(nèi)部this指向undefined,但全局對象window不會受影響

        答案

        global this is Window{...}print this is undefined123Uncaught TypeError: Cannot read property 'foo' of undefined

        03、題目1.3:let/const

        let a = 1;const b = 2;var c = 3;function print() {    console.log(this.a);    console.log(this.b);    console.log(this.c);}print();console.log(this.a);

        let/const定義的變量存在暫時性死區(qū),而且不會掛載到window對象上,因此print中是無法獲取到a和b的。

        答案

        undefinedundefined3undefined

        04、題目1.4:對象內(nèi)執(zhí)行

        a = 1;function foo() {    console.log(this.a); }const obj = {    a: 10,    bar() {        foo(); // 1    }}obj.bar();

        foo雖然在obj的bar函數(shù)中,但foo函數(shù)仍然是獨立運行的,foo中的this依舊指向window對象。

        05、題目1.5:函數(shù)內(nèi)執(zhí)行

        var a = 1function outer () {  var a = 2  function inner () {     console.log(this.a) // 1  }  inner()}outer()

        這個題與題目1.4類似,但要注意,不要把它看成閉包問題

        06、題目1.6:自執(zhí)行函數(shù)

        a = 1;(function(){    console.log(this);    console.log(this.a)}())function bar() {    b = 2;    (function(){        console.log(this);        console.log(this.b)    }())}bar();

        默認情況下,自執(zhí)行函數(shù)的this指向window

        自執(zhí)行函數(shù)只要執(zhí)行到就會運行,并且只會運行一次,this指向window。

        答案

        Window{...}1Window{...}2 // b是imply global,會掛載到window上

        2、隱式綁定

        函數(shù)的調(diào)用是在某個對象上觸發(fā)的,即調(diào)用位置存在上下文對象,通俗點說就是**XXX.func()**這種調(diào)用模式。

        此時func的this指向XXX,但如果存在鏈式調(diào)用,例如XXX.YYY.ZZZ.func,記住一個原則:this永遠指向最后調(diào)用它的那個對象。

        07、題目2.1:隱式綁定

        var a = 1;function foo() {    console.log(this.a); }// 對象簡寫,等同于 {a:2, foo: foo}var obj = {a: 2, foo}foo();obj.foo();

        foo(): 默認綁定,打印1

        obj.foo(): 隱式綁定,打印2

        答案

        12

        obj是通過var定義的,obj會掛載到window之上的,obj.foo()就相當于window.obj.foo(),這也印證了this永遠指向最后調(diào)用它的那個對象規(guī)則。

        08、題目2.2:對象鏈式調(diào)用

        感覺上面總是空談鏈式調(diào)用的情況,下面直接來看一個例題:

        var obj1 = {    a: 1,    obj2: {        a: 2,        foo(){            console.log(this.a)        }    }}obj1.obj2.foo() // 2

        3、隱式綁定的丟失

        隱式綁定可是個調(diào)皮的東西,一不小心它就會發(fā)生綁定的丟失。一般會有兩種常見的丟失:

        使用另一個變量作為函數(shù)別名,之后使用別名執(zhí)行函數(shù)

        將函數(shù)作為參數(shù)傳遞時會被隱式賦值

        隱式綁定丟失之后,this的指向會啟用默認綁定。

        具體來看題目:

        09、題目3.1:取函數(shù)別名

        a = 1var obj = {    a: 2,    foo() {        console.log(this.a)    }}var foo = obj.foo;obj.foo();foo();

        JavaScript對于引用類型,其地址指針存放在棧內(nèi)存中,真正的本體是存放在堆內(nèi)存中的。

        上面將obj.foo賦值給foo,就是將foo也指向了obj.foo所指向的堆內(nèi)存,此后再執(zhí)行foo,相當于直接執(zhí)行的堆內(nèi)存的函數(shù),與obj無關(guān),foo為默認綁定?;\統(tǒng)的記,只要fn前面什么都沒有,肯定不是隱式綁定。

        答案

        2 1

        不要把這里理解成window.foo執(zhí)行,如果foo為let/const定義,foo不會掛載到window上,但不會影響最后的打印結(jié)果

        10、題目3.2:取函數(shù)別名

        如果取函數(shù)別名沒有發(fā)生在全局,而是發(fā)生在對象之中,又會是怎樣的結(jié)果呢?

        var obj = {     a: 1,     foo() {        console.log(this.a)    } };var a = 2;var foo = obj.foo;var obj2 = { a: 3, foo: obj.foo }
        obj.foo();foo();obj2.foo();

        obj2.foo指向了obj.foo的堆內(nèi)存,此后執(zhí)行與obj無關(guān)(除非使用call/apply改變this指向)

        答案

        1 2 3

        11、題目3.3:函數(shù)作為參數(shù)傳遞

        function foo() {  console.log(this.a)}function doFoo(fn) {  console.log(this)  fn()}var obj = { a: 1, foo }var a = 2doFoo(obj.foo)

        用函數(shù)預編譯的知識來解答這個問題:函數(shù)預編譯四部曲前兩步分別是:

        找形參和變量聲明,值賦予undefined

        將形參與實參相統(tǒng)一,也就是將實參的值賦予形參。

        obj.foo作為實參,在預編譯時將其值賦值給形參fn,是將obj.foo指向的地址賦給了fn,此后fn執(zhí)行不會與obj產(chǎn)生任何關(guān)系。fn為默認綁定。

        答案

        Window {…}
        2

        12、題目3.4:函數(shù)作為參數(shù)傳遞

        將上面的題略作修改,doFoo不在window上執(zhí)行,改為在obj2中執(zhí)行

        function foo() {  console.log(this.a)}function doFoo(fn) {  console.log(this)  fn()}var obj = { a: 1, foo }var a = 2var obj2 = { a: 3, doFoo }obj2.doFoo(obj.foo)

        console.log(this): obj2.doFoo符合xxx.fn格式,doFoo的為隱式綁定,this為obj2,打印{a: 3, doFoo: ?}

        fn(): 沒有于obj2產(chǎn)生聯(lián)系,默認綁定,打印2

        答案

        {a: 3, doFoo: ?}2

        13、題目3.5:回調(diào)函數(shù)

        下面這個題目我們寫代碼時會經(jīng)常遇到:

        var name='zcxiaobao';function introduce(){    console.log('Hello,My name is ', this.name);}const Tom = {    name: 'TOM',    introduce: function(){        setTimeout(function(){            console.log(this)            console.log('Hello, My name is ',this.name);        })    }}const Mary = {    name: 'Mary',    introduce}const Lisa = {    name: 'Lisa',    introduce}
        Tom.introduce();setTimeout(Mary.introduce, 100);setTimeout(function(){ Lisa.introduce();},200);

        setTimeout是異步調(diào)用的,只有當滿足條件并且同步代碼執(zhí)行完畢后,才會執(zhí)行它的回調(diào)函數(shù)。

        Tom.introduce()執(zhí)行: console位于setTimeout的回調(diào)函數(shù)中,回調(diào)函數(shù)的this指向window

        Mary.introduce直接作為setTimeout的函數(shù)參數(shù)(類似題目題目3.3),會發(fā)生隱式綁定丟失,this為默認綁定

        Lisa.introduce執(zhí)行雖然位于setTimeout的回調(diào)函數(shù)中,但保持xxx.fn模式,this為隱式綁定。

        答案

        Window {…}Hello, My name is  zcxiaobaoHello,My name is  zcxiaobaoHello,My name is  Lisa

        所以如果我們想在setTimeout或setInterval中使用外界的this,需要提前存儲一下,避免this的丟失。

        const Tom = {    name: 'TOM',    introduce: function(){        _self = this        setTimeout(function(){            console.log('Hello, My name is ',_self.name);        })    }}Tom.introduce()

        14、題目3.6:隱式綁定丟失綜合題

        name = 'javascript' ;let obj = {    name: 'obj',    A (){        this.name += 'this';        console.log(this.name)    },    B(f){        this.name += 'this';        f();    },    C(){      setTimeout(function(){          console.log(this.name);      },1000);    }}let a = obj.A;             a();                        obj.B(function(){               console.log(this.name); });                         obj.C();                    console.log(name);

        本題目不做解析,具體可以參照上面的題目。

        答案

        javascriptthisjavascriptthisjavascriptthisjavascriptthis

        4、顯式綁定

        顯式綁定比較好理解,就是通過call()、apply()、bind()等方法,強行改變this指向。

        上面的方法雖然都可以改變this指向,但使用起來略有差別:

        call()和apply()函數(shù)會立即執(zhí)行

        bind()函數(shù)會返回新函數(shù),不會立即執(zhí)行函數(shù)

        call()和apply()的區(qū)別在于call接受若干個參數(shù),apply接受數(shù)組。

        15、題目4.1:比較三種調(diào)用方式

        function foo () {  console.log(this.a)}var obj = { a: 1 }var a = 2
        foo()foo.call(obj)foo.apply(obj)foo.bind(obj)

        foo(): 默認綁定。

        foo.call(obj): 顯示綁定,foo的this指向obj

        foo.apply(obj): 顯式綁定

        foo.bind(obj): 顯式綁定,但不會立即執(zhí)行函數(shù),沒有返回值

        答案

        211

        16、題目4.2:隱式綁定丟失

        題目3.4發(fā)生隱式綁定的丟失,如下代碼:我們可不可以通過顯式綁定來修正這個問題。

        function foo() {  console.log(this.a)}function doFoo(fn) {  console.log(this)  fn()}var obj = { a: 1, foo }var a = 2doFoo(obj.foo)

        首先先修正doFoo()函數(shù)的this指向。

        doFoo.call(obj, obj.foo)

        然后修正fn的this。

        function foo() {  console.log(this.a)}function doFoo(fn) {  console.log(this)  fn.call(this)}var obj = { a: 1, foo }var a = 2doFoo(obj.foo)

        大功告成。

        17、題目4.3:回調(diào)函數(shù)與call

        接著上一個題目的風格,稍微變點花樣:

        var obj1 = {    a: 1}var obj2 = {    a: 2,    bar: function () {        console.log(this.a)    },    foo: function () {        setTimeout(function () {            console.log(this)            console.log(this.a)        }.call(obj1), 0)    }}var a = 3obj2.bar()obj2.foo()

        乍一看上去,這個題看起來有些莫名其妙,setTimeout那是傳了個什么東西?

        做題之前,先了解一下setTimeout的內(nèi)部機制:

        setTimeout(fn) {    if (回調(diào)條件滿足) (        fn    )}

        這樣一看,本題就清楚多了,類似題目4.2,修正了回調(diào)函數(shù)內(nèi)fn的this指向。

        答案

        2{a: 1}1

        18、題目4.4:注意call位置

        function foo () {    console.log(this.a)}var obj = { a: 1 }var a = 2
        foo()foo.call(obj)foo().call(obj)

        foo(): 默認綁定

        foo.call(obj): 顯式綁定

        foo().call(obj): 對foo()執(zhí)行的返回值執(zhí)行call,foo返回值為undefined,執(zhí)行call()會報錯

        答案

        212Uncaught TypeError: Cannot read property 'call' of undefined

        19、題目4.5:注意call位置(2)

        上面由于foo沒有返回函數(shù),無法執(zhí)行call函數(shù)報錯,因此修改一下foo函數(shù),讓它返回一個函數(shù)。

        function foo () {    console.log(this.a)    return function() {        console.log(this.a)    }}var obj = { a: 1 }var a = 2
        foo()foo.call(obj)foo().call(obj)

        foo(): 默認綁定

        foo.call(obj): 顯式綁定

        foo().call(obj): foo()執(zhí)行,打印2,返回匿名函數(shù)通過call將this指向obj,打印1。

        這里千萬注意:最后一個foo().call(obj)有兩個函數(shù)執(zhí)行,會打印2個值。

        答案

        2121

        20、題目4.6:bind

        將上面的call全部換做bind函數(shù),又會怎樣那?

        call是會立即執(zhí)行函數(shù),bind會返回一個新函數(shù),但不會執(zhí)行函數(shù)

        function foo () {    console.log(this.a)    return function() {        console.log(this.a)    }}var obj = { a: 1 }var a = 2
        foo()foo.bind(obj)foo().bind(obj)

        首先我們要先確定,最后會輸出幾個值?bind不會執(zhí)行函數(shù),因此只有兩個foo()會打印a。

        foo(): 默認綁定,打印2

        foo.bind(obj): 返回新函數(shù),不會執(zhí)行函數(shù),無輸出

        foo().bind(obj): 第一層foo(),默認綁定,打印2,后bind將foo()返回的匿名函數(shù)this指向obj,不執(zhí)行

        答案

        22

        21、題目4.7:外層this與內(nèi)層this

        做到這里,不由產(chǎn)生了一些疑問:如果使用call、bind等修改了外層函數(shù)的this,那內(nèi)層函數(shù)的this會受影響嗎?(注意區(qū)別箭頭函數(shù))

        function foo () {    console.log(this.a)    return function() {        console.log(this.a)    }}var obj = { a: 1 }var a = 2foo.call(obj)()

        foo.call(obj): 第一層函數(shù)foo通過call將this指向obj,打印1;第二層函數(shù)為匿名函數(shù),默認綁定,打印2。

        答案

        12

        22、題目4.8:對象中的call

        把上面的代碼移植到對象中,看看會發(fā)生怎樣的變化?

        var obj = {    a: 'obj',    foo: function () {        console.log('foo:', this.a)        return function () {            console.log('inner:', this.a)        }    }}var a = 'window'var obj2 = { a: 'obj2' }
        obj.foo()()obj.foo.call(obj2)()obj.foo().call(obj2)

        看著這么多括號,是不是感覺有幾分頭大。沒事,咱們來一層一層分析:

        obj.foo()(): 第一層obj.foo()執(zhí)行為隱式綁定,打印出foo:obj;第二層匿名函數(shù)為默認綁定,打印inner:window

        obj.foo.call(obj2)(): 類似題目4.7,第一層obj.foo.call(obj2)使用call將obj.foo的this指向obj2,打印foo: obj2;第二層匿名函數(shù)默認綁定,打印inner:window

        obj.foo().call(obj2): 類似題目4.5,第一層隱式綁定,打?。篺oo: obj,第二層匿名函數(shù)使用call將this指向obj2,打印inner: obj2

        23、題目4.9:帶參數(shù)的call

        顯式綁定一開始講的時候,就談過call/apply存在傳參差異,那咱們就來傳一下參數(shù),看看傳完參數(shù)的this會是怎樣的美妙。

        var obj = {  a: 1,  foo: function (b) {    b = b || this.a    return function (c) {      console.log(this.a + b + c)    }  }}var a = 2var obj2 = { a: 3 }
        obj.foo(a).call(obj2, 1)obj.foo.call(obj2)(1)

        要注意call執(zhí)行的位置:

        obj.foo(a).call(obj2, 1):

        obj.foo(a): foo的AO中b值為傳入的a(形參與實參相統(tǒng)一),值為2,返回匿名函數(shù)fn

        匿名函數(shù)fn.call(obj2, 1): fn的this指向為obj2,c值為1

        this.a + b + c = obj2.a + FooAO.b + c = 3 + 2 + 1 = 6

        obj.foo.call(obj2)(1):

        obj.foo.call(obj2): obj.foo的this指向obj2,未傳入?yún)?shù),b = this.a = obj2.a = 3;返回匿名函數(shù)fn

        匿名函數(shù)fn(1): c = 1,默認綁定,this指向window

        this.a + b + c = window.a + obj2.a + c = 2 + 3 + 1 = 6

        答案

        66

        麻了嗎,兄弟們。進度已經(jīng)快過半了,休息一會,爭取把this一次性吃透。 

        5、顯式綁定擴展

        上面提了很多call/apply可以改變this指向,但都沒有太多實用性。下面來一起學幾個常用的call與apply使用。

        24、題目5.1:apply求數(shù)組最值

        JavaScript中沒有給數(shù)組提供類似max和min函數(shù),只提供了Math.max/min,用于求多個數(shù)的最值,所以可以借助apply方法,直接傳遞數(shù)組給Math.max/min

        const arr = [1,10,11,33,4,52,17]Math.max.apply(Math, arr)Math.min.apply(Math, arr)

        25、題目5.2:類數(shù)組轉(zhuǎn)為數(shù)組

        ES6未發(fā)布之前,沒有Array.from方法可以將類數(shù)組轉(zhuǎn)為數(shù)組,采用Array.prototype.slice.call(arguments)或[].slice.call(arguments)將類數(shù)組轉(zhuǎn)化為數(shù)組。

        26、題目5.3:數(shù)組高階函數(shù)

        日常編碼中,我們會經(jīng)常用到forEach、map等,但這些數(shù)組高階方法,它們還有第二個參數(shù)thisArg,每一個回調(diào)函數(shù)都是顯式綁定在thisArg上的。

        例如下面這個例子

        const obj = {a: 10}const arr = [1, 2, 3, 4]arr.forEach(function (val, key){    console.log(`${key}: ${val} --- ${this.a}`)}, obj)

        答案

        0: 1 --- 101: 2 --- 102: 3 --- 103: 4 --- 10

        6、new綁定

        使用new來構(gòu)建函數(shù),會執(zhí)行如下四部操作:

        創(chuàng)建一個空的簡單JavaScript對象(即{});
        為步驟1新創(chuàng)建的對象添加屬性__proto__,將該屬性鏈接至構(gòu)造函數(shù)的原型對象 ;
        將步驟1新創(chuàng)建的對象作為this的上下文 ;
        如果該函數(shù)沒有返回對象,則返回this。

        通過new來調(diào)用構(gòu)造函數(shù),會生成一個新對象,并且把這個新對象綁定為調(diào)用函數(shù)的this。

        27、題目6.1:new綁定

        function User(name, age) {    this.name = name;    this.age = age;}var name = 'Tom';var age = 18;
        var zc = new User('zc', 24);console.log(zc.name)

        答案

        zc

        28、題目6.2:屬性加方法

        function User (name, age) {  this.name = name;  this.age = age;  this.introduce = function () {    console.log(this.name)  }  this.howOld = function () {    return function () {      console.log(this.age)    }  }}var name = 'Tom';var age = 18;var zc = new User('zc', 24)zc.introduce()zc.howOld()()

        這個題很難不讓人想到如下代碼,都是函數(shù)嵌套,具體解法是類似的,可以對比來看一下啊。

        const User = {  name: 'zc';  age: 18;  introduce = function () {    console.log(this.name)  }  howOld = function () {    return function () {      console.log(this.age)    }  }}var name = 'Tom';var age = 18;User.introduce()User.howOld()()

        zc.introduce(): zc是new創(chuàng)建的實例,this指向zc,打印zc

        zc.howOld()(): zc.howOld()返回一個匿名函數(shù),匿名函數(shù)為默認綁定,因此打印18(阿包永遠18)

        答案

        zc18

        29、題目6.3:new界的天王山

        new界的天王山,每次看懂后,沒過多久就會忘掉,但這次要從根本上弄清楚該題。

        接下來一起來品味品味:

        function Foo(){    getName = function(){ console.log(1); };    return this;}Foo.getName = function(){ console.log(2); };Foo.prototype.getName = function(){ console.log(3); };var getName = function(){ console.log(4); };function getName(){ console.log(5) };
        Foo.getName(); getName(); Foo().getName();getName(); new Foo.getName();new Foo().getName();new new Foo().getName();

        預編譯

        GO = {    Foo: fn(Foo),    getName: function getName(){ console.log(5) };}

        分析后續(xù)執(zhí)行

        Foo.getName(): 執(zhí)行Foo上的getName方法,打印2

        getName(): 執(zhí)行GO中的getName方法,打印4

        Foo().getName()

        Foo()執(zhí)行

        // 修改全局GO的getName為function(){ console.log(1); }getName = function(){ console.log(1) }// Foo為默認綁定,this -> window// return windowreturn this

        Foo().getName(): 執(zhí)行window.getName(),打印1

        getName(): 執(zhí)行GO中的getName,打印1

        分析后面三個打印結(jié)果之前,先補充一些運算符優(yōu)先級方面的知識(圖源:MDN) 


        從上圖可以看到,部分優(yōu)先級如下:new(帶參數(shù)列表) = 成員訪問 = 函數(shù)調(diào)用 > new(不帶參數(shù)列表)。
        new Foo.getName()
        首先從左往右看:new Foo屬于不帶參數(shù)列表的new(優(yōu)先級19),F(xiàn)oo.getName屬于成員訪問(優(yōu)先級20),getName()屬于函數(shù)調(diào)用(優(yōu)先級20),同樣優(yōu)先級遵循從左往右執(zhí)行。
        Foo.getName執(zhí)行,獲取到Foo上的getName屬性
        此時原表達式變?yōu)閚ew (Foo.getName)(),new (Foo.getName)()為帶參數(shù)列表(優(yōu)先級20),(Foo.getName)()屬于函數(shù)調(diào)用(優(yōu)先級20),從左往右執(zhí)行
        new (Foo.getName)()執(zhí)行,打印2,并返回一個以Foo.getName()為構(gòu)造函數(shù)的實例

        這里有一個誤區(qū):很多人認為這里的new是沒做任何操作的的,執(zhí)行的是函數(shù)調(diào)用。那么如果執(zhí)行的是Foo.getName(),調(diào)用返回值為undefined,new undefined會發(fā)生報錯,并且我們可以驗證一下該表達式的返回結(jié)果。

        console.log(new Foo.getName())// 2// Foo.getName {}

        可見在成員訪問之后,執(zhí)行的是帶參數(shù)列表格式的new操作。

        new Foo().getName()

        同步驟4一樣分析,先執(zhí)行new Foo(),返回一個以Foo為構(gòu)造函數(shù)的實例

        Foo的實例對象上沒有g(shù)etName方法,沿原型鏈查找到Foo.prototype.getName方法,打印3

        new new Foo().getName()

        從左往右分析: 第一個new不帶參數(shù)列表(優(yōu)先級19),new Foo()帶參數(shù)列表(優(yōu)先級20),剩下的成員訪問和函數(shù)調(diào)用優(yōu)先級都是20

        new Foo()執(zhí)行,返回一個以Foo為構(gòu)造函數(shù)的實例

        在執(zhí)行成員訪問,F(xiàn)oo實例對象在Foo.prototype查找到getName屬性

        執(zhí)行new (new Foo().getName)(),返回一個以 Foo.prototype.getName()為構(gòu)造函數(shù)的實例,打印3

        new Foo.getName() 與 new new Foo().getName()區(qū)別:

        new Foo.getName()的構(gòu)造函數(shù)是Foo.getName

        new new Foo().getName()的構(gòu)造函數(shù)為Foo.prototype.getName

        測試結(jié)果如下:

        foo1 = new Foo.getName()foo2 = new new Foo().getName()console.log(foo1.constructor)console.log(foo2.constructor)

        輸出結(jié)果:

        23? (){ console.log(2); }? (){ console.log(3); }

        通過這一步比較應該能更好的理解上面的執(zhí)行順序。

        答案

        2411233

        兄弟們,革命快要成功了,再努力一把,以后this都小問題啦。 

        7、箭頭函數(shù)

        箭頭函數(shù)沒有自己的this,它的this指向外層作用域的this,且指向函數(shù)定義時的this而非執(zhí)行時。

        this指向外層作用域的this: 箭頭函數(shù)沒有this綁定,但它可以通過作用域鏈查到外層作用域的this

        指向函數(shù)定義時的this而非執(zhí)行時: JavaScript是靜態(tài)作用域,就是函數(shù)定義之后,作用域就定死了,跟它執(zhí)行時的地方無關(guān)。更詳細的介紹見JavaScript之靜態(tài)作用域與動態(tài)作用域。

        30、題目7.1:對象方法使用箭頭函數(shù)

        name = 'tom'const obj = {    name: 'zc',    intro: () => {        console.log('My name is ' + this.name)    }}obj.intro()

        上文說到,箭頭函數(shù)的this通過作用域鏈查到,intro函數(shù)的上層作用域為window。

        答案

        My name is tom

        31、題目7.2:箭頭函數(shù)與普通函數(shù)比較

        name = 'tom'const obj = {    name: 'zc',    intro:function ()  {        return () => {            console.log('My name is ' + this.name)        }    },    intro2:function ()  {        return function() {            console.log('My name is ' + this.name)        }    }}obj.intro2()()obj.intro()()

        obj.intro2()(): 不做贅述,打印My name is tom

        obj.intro()(): obj.intro()返回箭頭函數(shù),箭頭函數(shù)的this取決于它的外層作用域,因此箭頭函數(shù)的this指向obj,打印My name is zc

        32、題目7.3:箭頭函數(shù)與普通函數(shù)的嵌套

        name = 'window'const obj1 = {    name: 'obj1',    intro:function ()  {        console.log(this.name)        return () => {            console.log(this.name)        }    }}const obj2 = {    name: 'obj2',    intro: ()=>  {        console.log(this.name)        return function() {            console.log(this.name)        }    }}const obj3 = {    name: 'obj3',    intro: ()=> {        console.log(this.name)        return () => {            console.log(this.name)        }    }}
        obj1.intro()()obj2.intro()()obj3.intro()()

        obj1.intro()(): 類似題目7.2,打印obj1,obj1

        obj2.intro()(): obj2.intro()為箭頭函數(shù),this為外層作用域this,指向window。返回匿名函數(shù)為默認綁定。打印window,window

        obj3.intro()(): obj3.intro()與obj2.intro()相同,返回值為箭頭函數(shù),外層作用域intro的this指向window,打印window,window

        答案

        obj1obj1windowwindowwindowwindow

        33、題目7.4:new碰上箭頭函數(shù)

        function User(name, age) {    this.name = name;    this.age = age;    this.intro = function(){        console.log('My name is ' + this.name)    },    this.howOld = () => {        console.log('My age is ' + this.age)    }}
        var name = 'Tom', age = 18;var zc = new User('zc', 24);zc.intro();zc.howOld();

        zc是new User實例,因此構(gòu)造函數(shù)User的this指向zc

        zc.intro(): 打印My name is zc

        zc.howOld(): howOld為箭頭函數(shù),箭頭函數(shù)this由外層作用域決定,且指向函數(shù)定義時的this,外層作用域為User,this指向zc,打印My age is 24

        34、題目7.5:call碰上箭頭函數(shù)

        箭頭函數(shù)由于沒有this,不能通過call\apply\bind來修改this指向,但可以通過修改外層作用域的this來達成間接修改。

        var name = 'window'var obj1 = {  name: 'obj1',  intro: function () {    console.log(this.name)    return () => {      console.log(this.name)    }  },  intro2: () => {    console.log(this.name)    return function () {      console.log(this.name)    }  }}var obj2 = {  name: 'obj2'}obj1.intro.call(obj2)()obj1.intro().call(obj2)obj1.intro2.call(obj2)()obj1.intro2().call(obj2)

        obj1.intro.call(obj2)(): 第一層函數(shù)為普通函數(shù),通過call修改this為obj2,打印obj2。第二層函數(shù)為箭頭函數(shù),它的this與外層this相同,同樣打印obj2。

        obj1.intro().call(obj2): 第一層函數(shù)打印obj1,第二次函數(shù)為箭頭函數(shù),call無效,它的this與外層this相同,打印obj1

        obj1.intro2.call(obj2)(): 第一層為箭頭函數(shù),call無效,外層作用域為window,打印window;第二次為普通匿名函數(shù),默認綁定,打印window

        obj1.intro2().call(obj2): 與上同,打印window;第二層為匿名函數(shù),call修改this為obj2,打印obj2

        答案

        obj2obj2obj1obj1windowwindowwindowobj2

        8、箭頭函數(shù)擴展

        總結(jié)

        箭頭函數(shù)沒有this,它的this是通過作用域鏈查到外層作用域的this,且指向函數(shù)定義時的this而非執(zhí)行時。

        不可以用作構(gòu)造函數(shù),不能使用new命令,否則會報錯

        箭頭函數(shù)沒有arguments對象,如果要用,使用rest參數(shù)代替

        不可以使用yield命令,因此箭頭函數(shù)不能用作Generator函數(shù)。

        不能用call/apply/bind修改this指向,但可以通過修改外層作用域的this來間接修改。

        箭頭函數(shù)沒有prototype屬性。

        避免使用場景

        箭頭函數(shù)定義對象方法

        const zc = {    name: 'zc',    intro: () => {        // this -> window        console.log(this.name)    }}zc.intro() // undefined

        箭頭函數(shù)不能作為構(gòu)造函數(shù)。

        const User = (name, age) => {    this.name = name;    this.age = age;}// Uncaught TypeError: User is not a constructorzc = new User('zc', 24);

        事件的回調(diào)函數(shù)

        DOM中事件的回調(diào)函數(shù)中this已經(jīng)封裝指向了調(diào)用元素,如果使用構(gòu)造函數(shù),其this會指向window對象。

        document.getElementById('btn')        .addEventListener('click', ()=> {            console.log(this === window); // true        })

        9、綜合題

        學完上面的知識,是不是感覺自己已經(jīng)趨于化境了,現(xiàn)在就一起來華山之巔一決高下吧。

        35、題目9.1: 對象綜合體

        var name = 'window'var user1 = {    name: 'user1',    foo1: function () {        console.log(this.name)    },    foo2: () => console.log(this.name),    foo3: function () {        return function () {            console.log(this.name)        }    },    foo4: function () {        return () => {            console.log(this.name)        }    }}var user2 = { name: 'user2' }
        user1.foo1()user1.foo1.call(user2)
        user1.foo2()user1.foo2.call(user2)
        user1.foo3()()user1.foo3.call(user2)()user1.foo3().call(user2)
        user1.foo4()()user1.foo4.call(user2)()user1.foo4().call(user2)

        這個題目并不難,就是把上面很多題做了個整合,如果上面都學會了,此題問題不大。

        user1.foo1()、user1.foo1.call(user2): 隱式綁定與顯式綁定

        user1.foo2()、user1.foo2.call(user2): 箭頭函數(shù)與call

        user1.foo3()()、user1.foo3.call(user2)()、user1.foo3().call(user2): 見題目4.8

        user1.foo4()()、user1.foo4.call(user2)()、user1.foo4().call(user2): 見題目7.5

        答案:

        var name = 'window'var user1 = {    name: 'user1',    foo1: function () {        console.log(this.name)    },    foo2: () => console.log(this.name),    foo3: function () {        return function () {            console.log(this.name)        }    },    foo4: function () {        return () => {            console.log(this.name)        }    }}var user2 = { name: 'user2' }
        user1.foo1() // user1user1.foo1.call(user2) // user2
        user1.foo2() // windowuser1.foo2.call(user2) // window
        user1.foo3()() // windowuser1.foo3.call(user2)() // windowuser1.foo3().call(user2) // user2
        user1.foo4()() // user1user1.foo4.call(user2)() // user2user1.foo4().call(user2) // user1

        36、題目9.2:隱式綁定丟失

        var x = 10;var foo = {   x : 20,   bar : function(){       var x = 30;       console.log(this.x)       }};foo.bar();(foo.bar)();(foo.bar = foo.bar)();(foo.bar, foo.bar)();

        突然出現(xiàn)了一個代碼很少的題目,還乍有些不習慣。

        foo.bar(): 隱式綁定,打印20

        (foo.bar)(): 上面提到過運算符優(yōu)先級的知識,成員訪問與函數(shù)調(diào)用優(yōu)先級相同,默認從左到右,因此括號可有可無,隱式綁定,打印20

        (foo.bar = foo.bar)():隱式綁定丟失,給foo.bar起別名,雖然名字沒變,但是foo.bar上已經(jīng)跟foo無關(guān)了,默認綁定,打印10

        (foo.bar, foo.bar)(): 隱式綁定丟失,起函數(shù)別名,將逗號表達式的值(第二個foo.bar)賦值給新變量,之后執(zhí)行新變量所指向的函數(shù),默認綁定,打印10。

        上面那說法有可能有幾分難理解,隱式綁定有個定性條件,就是要滿足XXX.fn()格式,如果破壞了這種格式,一般隱式綁定都會丟失。

        37、題目9.3:arguments(推薦看)

        var length = 10;function fn() {    console.log(this.length);} var obj = {  length: 5,  method: function(fn) {    fn();    arguments[0]();  }}; obj.method(fn, 1);

        這個題要注意一下,有坑。

        fn(): 默認綁定,打印10

        arguments[0](): 這種執(zhí)行方式看起來就怪怪的,咱們把它展開來看看:

        arguments是一個類數(shù)組,arguments展開,應該是下面這樣:

        arguments: {    0: fn,    1: 1,    length: 2}

        arguments[0]: 這是訪問對象的屬性0?0不好理解,咱們把它稍微一換,方便一下理解:

        arguments: {    fn: fn,    1: 1,    length: 2}

        到這里大家應該就懂了,隱式綁定,fn函數(shù)this指向arguments,打印2

        38、題目9.4:壓軸題(推薦看)

        var number = 5;var obj = {    number: 3,    fn: (function () {        var number;        this.number *= 2;        number = number * 2;        number = 3;        return function () {            var num = this.number;            this.number *= 2;            console.log(num);            number *= 3;            console.log(number);        }    })()}var myFun = obj.fn;myFun.call(null);obj.fn();console.log(window.number);

        fn.call(null) 或者 fn.call(undefined) 都相當于fn()

        obj.fn為立即執(zhí)行函數(shù): 默認綁定,this指向window

        我們來一句一句的分析:

        var number: 立即執(zhí)行函數(shù)的AO中添加number屬性,值為undefined

        this.number *= 2: window.number = 10

        number = number * 2: 立即執(zhí)行函數(shù)AO中number值為undefined,賦值后為NaN

        number = 3: AO中number值由NaN修改為3

        返回匿名函數(shù),形成閉包

        此時的obj可以類似的看成以下代碼(注意存在閉包):

        obj = {   number: 3,   fn: function () {        var num = this.number;        this.number *= 2;        console.log(num);        number *= 3;        console.log(number);    }}

        myFun.call(null): 相當于myFun(),隱式綁定丟失,myFun的this指向window。

        依舊一句一句的分析:

        var num = this.number: this指向window,num = window.num = 10

        this.number *= 2: window.number = 20

        console.log(num): 打印10

        number *= 3: 當前AO中沒有number屬性,沿作用域鏈可在立即執(zhí)行函數(shù)的AO中查到number屬性,修改其值為9

        console.log(number): 打印立即執(zhí)行函數(shù)AO中的number,打印9

        obj.fn(): 隱式綁定,fn的this指向obj

        繼續(xù)一步一步的分析:

        var num = this.number: this->obj,num = obj.num = 3

        this.number *= 2: obj.number *= 2 = 6

        console.log(num): 打印num值,打印3

        number *= 3: 當前AO中不存在number,繼續(xù)修改立即執(zhí)行函數(shù)AO中的number,number *= 3 = 27

        console.log(number): 打印27

        console.log(window.number): 打印20

        這里解釋一下,為什么myFun.call(null)執(zhí)行時,找不到number變量,是去找立即執(zhí)行函數(shù)AO中的number,而不是找window.number: JavaScript采用的靜態(tài)作用域,當定義函數(shù)后,作用域鏈就已經(jīng)定死。(更詳細的解釋文章最開始的推薦中有)

        答案

        10932720

        總結(jié)

        默認綁定: 非嚴格模式下this指向全局對象,嚴格模式下this會綁定到undefined

        隱式綁定: 滿足XXX.fn()格式,fn的this指向XXX。如果存在鏈式調(diào)用,this永遠指向最后調(diào)用它的那個對象

        隱式綁定丟失:起函數(shù)別名,通過別名運行;函數(shù)作為參數(shù)會造成隱式綁定丟失。

        顯示綁定: 通過call/apply/bind修改this指向

        new綁定: 通過new來調(diào)用構(gòu)造函數(shù),會生成一個新對象,并且把這個新對象綁定為調(diào)用函數(shù)的this。

        箭頭函數(shù)綁定: 箭頭函數(shù)沒有this,它的this是通過作用域鏈查到外層作用域的this,且指向函數(shù)定義時的this而非執(zhí)行時

        后語

        this到這里基本接近尾聲了,松了一口氣。這篇文章寫了好久,找資源,修改博文,各種亂七八糟的雜事,導致遲遲寫不出滿意的博文。有可能天生理科男的緣故吧,怎么寫感覺文章都很生硬,但好在還是順利寫完了。




        學習更多技能

        請點擊下方公眾號

        瀏覽 36
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 欧美10一11娇小XXXX | 操逼黄片网站 | 电视剧h文肉版 | 38色综合 | 国产高潮又粗又猛精品影院 |