精品人伦一区二区三区蜜桃视频_日韩精品视频在线_www.色综合_久久久久久一区_aaaaaa黄色片_亚洲精品久久

詳解前端 this

JavaScript 中的?this,因其靈活的指向、復雜的使用場景一直是面試中的熱點,不論是初級還是中高級開發者,這都是一道必考題。這個概念雖然基礎,但是非常重要,是否能深刻理解?this,是前端 JavaScript 中進階的重要一環。this?指向多變,很多隱蔽的 bug 都緣于它。與此同時,this?強大靈活,如果能熟練駕馭,就會寫出更簡潔、優雅的代碼。

社區上對于?this?的講解雖然不少,但缺乏統一梳理。本節課,讓我們直面?this?的方方面面,并通過例題真正領會與掌握?this

this?相關知識點如下:

this 到底指向誰

曾經在面試阿里某重點部門時,面試官從多個角度考察過我對?this?的理解:全局環境下的this、箭頭函數的?this、構造函數的?thisthis?的顯隱性和優先級,等等。盡管我能一一作答,可是最后的問題:請用一句話總結?this?的指向,注意只用一句話。?我卻犯難了。

有一種廣泛流傳的說法是:

誰調用它,this?就指向誰。

也就是說,this?的指向是在調用時確定的。這么說沒有太大的問題,可是并不全面。面試官要求我用更加規范的語言進行總結,那么他到底在等什么樣的回答呢?

我們還要回到 JavaScript 中一個最基本的概念分析——執行上下文,這個概念,我們會在下一講《老司機也會在閉包相關知識點翻車》中進行擴展。

事實上,調用函數會創建新的屬于函數自身的執行上下文。執行上下文的調用創建階段會決定?this?的指向。到此,我們可以得出的一個結論:

this?的指向,是在調用函數時根據執行上下文所動態確定的。

具體環節和規則,可以先“死記硬背”以下幾條規律,后面再慢慢一一分析:

  • 在函數體中,簡單調用該函數時(非顯式/隱式綁定下),嚴格模式下?this?綁定到?undefined,否則綁定到全局對象?windowglobal
  • 一般構造函數?new?調用,綁定到新創建的對象上;
  • 一般由?call/apply/bind?方法顯式調用,綁定到指定參數的對象上;
  • 一般由上下文對象調用,綁定在該對象上;
  • 箭頭函數中,根據外層上下文綁定的?this?決定?this?指向。

當然,真實環境多樣,我們來逐一梳理。

實戰例題分析

例題組合 1:全局環境下的 this

這種情況相對簡單直接,函數在瀏覽器全局環境中被簡單調用,非嚴格模式下?this?指向?window;在?use strict?指明嚴格模式的情況下就是?undefined。我們來看例題,請描述打印結果:

function f1 () {
    console.log(this)
}
function f2 () {
    'use strict'
    console.log(this)
}
f1() // window
f2() // undefined

這樣的題目比較基礎,但是需要候選人格外注意其變種,請再看一道題目:

const foo = {
    bar: 10,
    fn: function() {
       console.log(this)
       console.log(this.bar)
    }
}
var fn1 = foo.fn
fn1()

這里?this?仍然指向的是?window。雖然?fn?函數在?foo?對象中作為方法被引用,但是在賦值給?fn1?之后,fn1?的執行仍然是在?window?的全局環境中。因此輸出?window?和?undefined,它們相當于:

console.log(window)
console.log(window.bar)

還是上面這道題目,如果調用改變為:

const foo = {
    bar: 10,
    fn: function() {
       console.log(this)
       console.log(this.bar)
    }
}
foo.fn()

將會輸出:

{bar: 10, fn: ?}
10

因為這個時候?this?指向的是最后調用它的對象,在?foo.fn()?語句中?this?指向?foo?對象。請記住:

在執行函數時,如果函數中的?this?是被上一級的對象所調用,那么?this?指向的就是上一級的對象;否則指向全局環境。

例題組合 2:上下文對象調用中的 this

如上結論,面對下題時我們便不再困惑:

const student = {
    name: 'Lucas',
    fn: function() {
        return this
    }
}
console.log(student.fn() === student)

最終結果將會返回?true

當存在更復雜的調用關系時,請看例題:

const person = {
    name: 'Lucas',
    brother: {
        name: 'Mike',
        fn: function() {
            return this.name
        }
    }
}
console.log(person.brother.fn())

在這種嵌套的關系中,this?指向最后調用它的對象,因此輸出將會是:Mike

到此,this?的上下文對象調用已經理解得比較清楚了。我們再看一道更高階的題目:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: function() {
        return o1.fn()
    }
}
const o3 = {
    text: 'o3',
    fn: function() {
        var fn = o1.fn
        return fn()
    }
}

console.log(o1.fn())
console.log(o2.fn())
console.log(o3.fn())

答案是:o1o1undefined,你答對了嗎?

我們來一一分析。

  • 第一個?console?最簡單,o1?沒有問題。難點在第二個和第三個上面,關鍵還是看調用?this?的那個函數。
  • 第二個?console??o2.fn(),最終還是調用?o1.fn(),因此答案仍然是?o1
  • 最后一個,在進行?var fn = o1.fn?賦值之后,是“裸奔”調用,因此這里的?this?指向?window,答案當然是?undefined

如果面試者回答順利,可以緊接著追問,如果我們需要讓:

console.log(o2.fn())

輸出?o2,該怎么做?

一般開發者可能會想到使用?bind/call/apply?來對?this?的指向進行干預,這確實是一種思路。但是我接著問,如果不能使用?bind/call/apply,有別的方法嗎?

這樣可以考察候選人基礎掌握的深度以及隨機應變的思維能力。答案為:

const o1 = {
    text: 'o1',
    fn: function() {
        return this.text
    }
}
const o2 = {
    text: 'o2',
    fn: o1.fn
}

console.log(o2.fn())

還是應用那個重要的結論:this?指向最后調用它的對象,在?fn?執行時,掛到?o2?對象上即可,我們提前進行了賦值操作。

例題組合 3:bind/call/apply 改變 this 指向

上文提到 bind/call/apply,在這個概念上,比較常見的基礎考察點是:bind/call/apply 三個方法的區別。

這樣的問題相對基礎,我們直接上答案:一句話總結,他們都是用來改變相關函數?this?指向的,但是?call/apply?是直接進行相關函數調用;bind?不會執行相關函數,而是返回一個新的函數,這個新的函數已經自動綁定了新的?this?指向,開發者需要手動調用即可。再具體的?call/apply?之間的區別主要體現在參數設定上,這里不再展開。

用代碼來總結:

const target = {}
fn.call(target, 'arg1', 'arg2')

相當于:

const target = {}
fn.apply(target, ['arg1', 'arg2'])

相當于:

const target = {}
fn.bind(target, 'arg1', 'arg2')()

具體基礎用法這里不再科普,如果讀者尚不清楚,需要自己補充一下知識點。

我們來看一道例題分析:

const foo = {
    name: 'lucas',
    logName: function() {
        console.log(this.name)
    }
}
const bar = {
    name: 'mike'
}
console.log(foo.logName.call(bar))

將會輸出?mike,這不難理解。但是對 call/apply/bind 的高級考察往往會結合構造函數以及組合式實現繼承。實現繼承的話題,我們會單獨講到。構造函數的使用案例,我們結合接下來的例題組合進行分析。

例題組合 4:構造函數和 this

這方面最直接的例題為:

function Foo() {
    this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar)

答案將會輸出?Lucas。但是這樣的場景往往伴隨著下一個問題:new?操作符調用構造函數,具體做了什么?以下供參考:

  • 創建一個新的對象;
  • 將構造函數的?this?指向這個新對象;
  • 為這個對象添加屬性、方法等;
  • 最終返回新對象。

以上過程,也可以用代碼表述:

var obj  = {}
obj.__proto__ = Foo.prototype
Foo.call(obj)

當然,這里對?new?的模擬是一個簡單基本版的,更復雜的情況我們會在原型、原型鏈相關的第2-5課《面向對象和原型——永不過時的話題》中講述。

需要指出的是,如果在構造函數中出現了顯式?return?的情況,那么需要注意分為兩種場景:

function Foo(){
    this.user = "Lucas"
    const o = {}
    return o
}
const instance = new Foo()
console.log(instance.user)

將會輸出?undefined,此時?instance?是返回的空對象?o

function Foo(){
    this.user = "Lucas"
    return 1
}
const instance = new Foo()
console.log(instance.user)

將會輸出?Lucas,也就是說此時?instance?是返回的目標對象實例?this

結論:如果構造函數中顯式返回一個值,且返回的是一個對象,那么?this?就指向這個返回的對象;如果返回的不是一個對象,那么?this?仍然指向實例。

例題組合 5:箭頭函數中的 this 指向

首先我們再來溫習一下相關結論。

結論:箭頭函數使用?this?不適用以上標準規則,而是根據外層(函數或者全局)上下文來決定。

來看題目:

const foo = {  
    fn: function () {  
        setTimeout(function() {  
            console.log(this)
        })
    }  
}  
console.log(foo.fn())

這道題中,this?出現在?setTimeout()?中的匿名函數里,因此?this?指向?window?對象。如果需要?this?指向?foo?這個 object 對象,可以巧用箭頭函數解決:

 

const foo = {  
    fn: function () {  
        setTimeout(() => {  
            console.log(this)
        })
    }  
} 
console.log(foo.fn())

// {fn: ?}

單純箭頭函數中的?this?非常簡單,但是綜合所有情況,結合?this?的優先級考察,這時候?this?指向并不好確定。請繼續閱讀。

例題組合 6:this 優先級相關

我們常常把通過?callapplybindnew??this?綁定的情況稱為顯式綁定;根據調用關系確定的?this?指向稱為隱式綁定。

那么顯式綁定和隱式綁定誰的優先級更高呢?

請看例題:

function foo (a) {
    console.log(this.a)
}

const obj1 = {
    a: 1,
    foo: foo
}

const obj2 = {
    a: 2,
    foo: foo
}

obj1.foo.call(obj2)
obj2.foo.call(obj1)

輸出分別為 2、1,也就是說?callapply?的顯式綁定一般來說優先級更高。

function foo (a) {
    this.a = a
}

const obj1 = {}

var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)

上述代碼通過?bind,將?bar?函數中的?this?綁定為?obj1?對象。執行?bar(2)?后,obj1.a?值為 2。即經過?bar(2)?執行后,obj1?對象為:{a: 2}

當再使用?bar?作為構造函數時:

 

var baz = new bar(3)
console.log(baz.a)

將會輸出 3。我們看?bar?函數本身是通過?bind?方法構造的函數,其內部已經對將?this?綁定為?obj1,它再作為構造函數,通過?new?調用時,返回的實例已經與?obj1?解綁。 也就是說:

new?綁定修改了?bind?綁定中的?this,因此?new?綁定的優先級比顯式?bind?綁定更高。

我們再看:

function foo() {
    return a => {
        console.log(this.a)
    };
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

const bar = foo.call(obj1)
console.log(bar.call(obj2))

將會輸出 2。由于?foo()??this?綁定到?obj1bar(引用箭頭函數)的?this?也會綁定到?obj1,箭頭函數的綁定無法被修改。

如果將?foo?完全寫成箭頭函數的形式:

var a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

將會輸出?123

 

這里我再“抖個機靈”,僅僅將上述代碼的第一處變量?a?的賦值改為:

const a = 123
const foo = () => a => {
    console.log(this.a)
}

const obj1 = {
    a: 2
}

const obj2 = {
    a: 3
}

var bar = foo.call(obj1)
console.log(bar.call(obj2))

答案將會輸出為?undefined,原因是因為使用?const?聲明的變量不會掛載到?window?全局對象當中。因此?this?指向?window?時,自然也找不到?a?變量了。關于?const?或者?let?等聲明變量的方式不再本課的主題當中,我們后續也將專門進行介紹。

到這里,讀者是否有“融會貫通”的感覺了呢?如果還有困惑,也不要灰心。進階的關鍵就是基礎,基礎需要反復學習,“死記硬背”后才能慢慢體會。

開放例題分析

不知道實戰例題分析是否已經把你繞暈了。事實上,this?的指向涉及的規范繁多,優先級也較為混亂。刻意刁難并不是很好的面試做法,一些細節候選人如果沒有記住也不是太大的問題。作為面試官,我往往會另辟蹊徑,出一些開放性題目。

其中,最典型的一道題目為:實現一個?bind?函數。

作為面試者,我也曾經在頭條的面試流程中被問到模擬?bind。這道題并不新鮮,部分讀者也會有自己的解答思路,而且社區上關于原生?bind?的研究也很多。但是,我們這里想強調的是,可能有一些細節被大家忽略了。在回答時,我往往先實現一個初級版本,然后根據 ES5-shim 源碼進一步說明。

Function.prototype.bind = Function.prototype.bind || function (context) {
    var me = this;
    var args = Array.prototype.slice.call(arguments, 1);
    return function bound () {
        var innerArgs = Array.prototype.slice.call(arguments);
        var finalArgs = args.concat(innerArgs);
        return me.apply(context, finalArgs);
    }
}

這樣的實現已經非常不錯了。但是,就如同之前?this?優先級分析所示:bind?返回的函數如果作為構造函數,搭配?new?關鍵字出現的話,我們的綁定?this?就需要“被忽略”。

為了實現這樣的規則,開發者就應該需要考慮如何區分這兩種調用方式。具體來講?bound?函數中就要進行?this instanceof?的判斷。

另外一個細節是,函數具有?length?屬性,表示形參的個數。上述實現方式形參的個數顯然會失真。我們的實現就需要對?length?屬性進行還原。可是難點在于:函數的?length?屬性值是不可重寫的。

總結

通過本課的學習,我們看到?this?紛繁多象,確實不容易徹底掌握。本節盡可能系統地進行講解、說明,例題盡可能地覆蓋更多 case。與此同時,需要讀者在閱讀之外繼續進行消化與吸收。只有“記死”,才能“用活”。

THE END
主站蜘蛛池模板: 国产视频久久 | 国产69精品久久99不卡免费版 | 一级黄色毛片子 | 国产精品久久久久久模特 | 在线观看中文字幕视频 | 最新中文字幕久久 | 欧美精品网站 | 亚洲网站在线 | 久久成人免费视频 | 欧美日韩亚洲国产 | 久久精品亚洲精品国产欧美 | 久久精品97 | 一区精品视频 | 伊人网91| 国产精品亚洲一区二区三区在线 | 999热精品 | 亚洲一区视频在线 | 久久久精品一区二区 | 亚洲精品免费视频 | 亚洲欧洲精品成人久久奇米网 | 亚洲成年在线 | 波多野结衣中文字幕一区二区三区 | 色婷婷精品久久二区二区蜜臂av | 日本成人中文字幕在线观看 | 夜夜夜夜草 | 日韩在线一区二区三区 | 狠狠干狠狠插 | 欧美一区二区三区在线 | av黄色在线 | 91久色 | 免费一区 | 久久综合久久综合久久 | 国产玖玖 | 黄色精品 | 伊人狠狠| 午夜99| 欧美日韩在线观看一区 | 国产精品久久国产精品 | 91资源在线观看 | 欧美在线精品一区 | 日韩中文字幕在线不卡 |