欧美激情中文字幕在线_国产区精品视频_免费久久99精品国产自_日韩精品 欧美

詳解前端 this

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

社區(qū)上對(duì)于?this?的講解雖然不少,但缺乏統(tǒng)一梳理。本節(jié)課,讓我們直面?this?的方方面面,并通過例題真正領(lǐng)會(huì)與掌握?this

this?相關(guān)知識(shí)點(diǎn)如下:

this 到底指向誰

曾經(jīng)在面試阿里某重點(diǎn)部門時(shí),面試官從多個(gè)角度考察過我對(duì)?this?的理解:全局環(huán)境下的this、箭頭函數(shù)的?this、構(gòu)造函數(shù)的?thisthis?的顯隱性和優(yōu)先級(jí),等等。盡管我能一一作答,可是最后的問題:請(qǐng)用一句話總結(jié)?this?的指向,注意只用一句話。?我卻犯難了。

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

誰調(diào)用它,this?就指向誰。

也就是說,this?的指向是在調(diào)用時(shí)確定的。這么說沒有太大的問題,可是并不全面。面試官要求我用更加規(guī)范的語言進(jìn)行總結(jié),那么他到底在等什么樣的回答呢?

我們還要回到 JavaScript 中一個(gè)最基本的概念分析——執(zhí)行上下文,這個(gè)概念,我們會(huì)在下一講《老司機(jī)也會(huì)在閉包相關(guān)知識(shí)點(diǎn)翻車》中進(jìn)行擴(kuò)展。

事實(shí)上,調(diào)用函數(shù)會(huì)創(chuàng)建新的屬于函數(shù)自身的執(zhí)行上下文。執(zhí)行上下文的調(diào)用創(chuàng)建階段會(huì)決定?this?的指向。到此,我們可以得出的一個(gè)結(jié)論:

this?的指向,是在調(diào)用函數(shù)時(shí)根據(jù)執(zhí)行上下文所動(dòng)態(tài)確定的。

具體環(huán)節(jié)和規(guī)則,可以先“死記硬背”以下幾條規(guī)律,后面再慢慢一一分析:

  • 在函數(shù)體中,簡單調(diào)用該函數(shù)時(shí)(非顯式/隱式綁定下),嚴(yán)格模式下?this?綁定到?undefined,否則綁定到全局對(duì)象?windowglobal
  • 一般構(gòu)造函數(shù)?new?調(diào)用,綁定到新創(chuàng)建的對(duì)象上;
  • 一般由?call/apply/bind?方法顯式調(diào)用,綁定到指定參數(shù)的對(duì)象上;
  • 一般由上下文對(duì)象調(diào)用,綁定在該對(duì)象上;
  • 箭頭函數(shù)中,根據(jù)外層上下文綁定的?this?決定?this?指向。

當(dāng)然,真實(shí)環(huán)境多樣,我們來逐一梳理。

實(shí)戰(zhàn)例題分析

例題組合 1:全局環(huán)境下的 this

這種情況相對(duì)簡單直接,函數(shù)在瀏覽器全局環(huán)境中被簡單調(diào)用,非嚴(yán)格模式下?this?指向?window;在?use strict?指明嚴(yán)格模式的情況下就是?undefined。我們來看例題,請(qǐng)描述打印結(jié)果:

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

這樣的題目比較基礎(chǔ),但是需要候選人格外注意其變種,請(qǐng)?jiān)倏匆坏李}目:

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

這里?this?仍然指向的是?window。雖然?fn?函數(shù)在?foo?對(duì)象中作為方法被引用,但是在賦值給?fn1?之后,fn1?的執(zhí)行仍然是在?window?的全局環(huán)境中。因此輸出?window?和?undefined,它們相當(dāng)于:

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

還是上面這道題目,如果調(diào)用改變?yōu)椋?/span>

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

將會(huì)輸出:

{bar: 10, fn: ?}
10

因?yàn)檫@個(gè)時(shí)候?this?指向的是最后調(diào)用它的對(duì)象,在?foo.fn()?語句中?this?指向?foo?對(duì)象。請(qǐng)記住:

在執(zhí)行函數(shù)時(shí),如果函數(shù)中的?this?是被上一級(jí)的對(duì)象所調(diào)用,那么?this?指向的就是上一級(jí)的對(duì)象;否則指向全局環(huán)境。

例題組合 2:上下文對(duì)象調(diào)用中的 this

如上結(jié)論,面對(duì)下題時(shí)我們便不再困惑:

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

最終結(jié)果將會(huì)返回?true

當(dāng)存在更復(fù)雜的調(diào)用關(guān)系時(shí),請(qǐng)看例題:

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

在這種嵌套的關(guān)系中,this?指向最后調(diào)用它的對(duì)象,因此輸出將會(huì)是:Mike

到此,this?的上下文對(duì)象調(diào)用已經(jīng)理解得比較清楚了。我們?cè)倏匆坏栏唠A的題目:

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,你答對(duì)了嗎?

我們來一一分析。

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

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

console.log(o2.fn())

輸出?o2,該怎么做?

一般開發(fā)者可能會(huì)想到使用?bind/call/apply?來對(duì)?this?的指向進(jìn)行干預(yù),這確實(shí)是一種思路。但是我接著問,如果不能使用?bind/call/apply,有別的方法嗎?

這樣可以考察候選人基礎(chǔ)掌握的深度以及隨機(jī)應(yīng)變的思維能力。答案為:

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

console.log(o2.fn())

還是應(yīng)用那個(gè)重要的結(jié)論:this?指向最后調(diào)用它的對(duì)象,在?fn?執(zhí)行時(shí),掛到?o2?對(duì)象上即可,我們提前進(jìn)行了賦值操作。

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

上文提到 bind/call/apply,在這個(gè)概念上,比較常見的基礎(chǔ)考察點(diǎn)是:bind/call/apply 三個(gè)方法的區(qū)別。

這樣的問題相對(duì)基礎(chǔ),我們直接上答案:一句話總結(jié),他們都是用來改變相關(guān)函數(shù)?this?指向的,但是?call/apply?是直接進(jìn)行相關(guān)函數(shù)調(diào)用;bind?不會(huì)執(zhí)行相關(guān)函數(shù),而是返回一個(gè)新的函數(shù),這個(gè)新的函數(shù)已經(jīng)自動(dòng)綁定了新的?this?指向,開發(fā)者需要手動(dòng)調(diào)用即可。再具體的?call/apply?之間的區(qū)別主要體現(xiàn)在參數(shù)設(shè)定上,這里不再展開。

用代碼來總結(jié):

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

相當(dāng)于:

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

相當(dāng)于:

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

具體基礎(chǔ)用法這里不再科普,如果讀者尚不清楚,需要自己補(bǔ)充一下知識(shí)點(diǎn)。

我們來看一道例題分析:

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

將會(huì)輸出?mike,這不難理解。但是對(duì) call/apply/bind 的高級(jí)考察往往會(huì)結(jié)合構(gòu)造函數(shù)以及組合式實(shí)現(xiàn)繼承。實(shí)現(xiàn)繼承的話題,我們會(huì)單獨(dú)講到。構(gòu)造函數(shù)的使用案例,我們結(jié)合接下來的例題組合進(jìn)行分析。

例題組合 4:構(gòu)造函數(shù)和 this

這方面最直接的例題為:

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

答案將會(huì)輸出?Lucas。但是這樣的場景往往伴隨著下一個(gè)問題:new?操作符調(diào)用構(gòu)造函數(shù),具體做了什么?以下供參考:

  • 創(chuàng)建一個(gè)新的對(duì)象;
  • 將構(gòu)造函數(shù)的?this?指向這個(gè)新對(duì)象;
  • 為這個(gè)對(duì)象添加屬性、方法等;
  • 最終返回新對(duì)象。

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

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

當(dāng)然,這里對(duì)?new?的模擬是一個(gè)簡單基本版的,更復(fù)雜的情況我們會(huì)在原型、原型鏈相關(guān)的第2-5課《面向?qū)ο蠛驮汀啦贿^時(shí)的話題》中講述。

需要指出的是,如果在構(gòu)造函數(shù)中出現(xiàn)了顯式?return?的情況,那么需要注意分為兩種場景:

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

將會(huì)輸出?undefined,此時(shí)?instance?是返回的空對(duì)象?o

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

將會(huì)輸出?Lucas,也就是說此時(shí)?instance?是返回的目標(biāo)對(duì)象實(shí)例?this

結(jié)論:如果構(gòu)造函數(shù)中顯式返回一個(gè)值,且返回的是一個(gè)對(duì)象,那么?this?就指向這個(gè)返回的對(duì)象;如果返回的不是一個(gè)對(duì)象,那么?this?仍然指向?qū)嵗?/p>

例題組合 5:箭頭函數(shù)中的 this 指向

首先我們?cè)賮頊亓?xí)一下相關(guān)結(jié)論。

結(jié)論:箭頭函數(shù)使用?this?不適用以上標(biāo)準(zhǔn)規(guī)則,而是根據(jù)外層(函數(shù)或者全局)上下文來決定。

來看題目:

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

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

 

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

// {fn: ?}

單純箭頭函數(shù)中的?this?非常簡單,但是綜合所有情況,結(jié)合?this?的優(yōu)先級(jí)考察,這時(shí)候?this?指向并不好確定。請(qǐng)繼續(xù)閱讀。

例題組合 6:this 優(yōu)先級(jí)相關(guān)

我們常常把通過?callapplybindnew?對(duì)?this?綁定的情況稱為顯式綁定;根據(jù)調(diào)用關(guān)系確定的?this?指向稱為隱式綁定。

那么顯式綁定和隱式綁定誰的優(yōu)先級(jí)更高呢?

請(qǐng)看例題:

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?的顯式綁定一般來說優(yōu)先級(jí)更高。

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

const obj1 = {}

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

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

當(dāng)再使用?bar?作為構(gòu)造函數(shù)時(shí):

 

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

將會(huì)輸出 3。我們看?bar?函數(shù)本身是通過?bind?方法構(gòu)造的函數(shù),其內(nèi)部已經(jīng)對(duì)將?this?綁定為?obj1,它再作為構(gòu)造函數(shù),通過?new?調(diào)用時(shí),返回的實(shí)例已經(jīng)與?obj1?解綁。 也就是說:

new?綁定修改了?bind?綁定中的?this,因此?new?綁定的優(yōu)先級(jí)比顯式?bind?綁定更高。

我們?cè)倏矗?/p>

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))

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

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

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))

將會(huì)輸出?123

 

這里我再“抖個(gè)機(jī)靈”,僅僅將上述代碼的第一處變量?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))

答案將會(huì)輸出為?undefined,原因是因?yàn)槭褂?span>?const?聲明的變量不會(huì)掛載到?window?全局對(duì)象當(dāng)中。因此?this?指向?window?時(shí),自然也找不到?a?變量了。關(guān)于?const?或者?let?等聲明變量的方式不再本課的主題當(dāng)中,我們后續(xù)也將專門進(jìn)行介紹。

到這里,讀者是否有“融會(huì)貫通”的感覺了呢?如果還有困惑,也不要灰心。進(jìn)階的關(guān)鍵就是基礎(chǔ),基礎(chǔ)需要反復(fù)學(xué)習(xí),“死記硬背”后才能慢慢體會(huì)。

開放例題分析

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

其中,最典型的一道題目為:實(shí)現(xiàn)一個(gè)?bind?函數(shù)。

作為面試者,我也曾經(jīng)在頭條的面試流程中被問到模擬?bind。這道題并不新鮮,部分讀者也會(huì)有自己的解答思路,而且社區(qū)上關(guān)于原生?bind?的研究也很多。但是,我們這里想強(qiáng)調(diào)的是,可能有一些細(xì)節(jié)被大家忽略了。在回答時(shí),我往往先實(shí)現(xiàn)一個(gè)初級(jí)版本,然后根據(jù) ES5-shim 源碼進(jìn)一步說明。

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);
    }
}

這樣的實(shí)現(xiàn)已經(jīng)非常不錯(cuò)了。但是,就如同之前?this?優(yōu)先級(jí)分析所示:bind?返回的函數(shù)如果作為構(gòu)造函數(shù),搭配?new?關(guān)鍵字出現(xiàn)的話,我們的綁定?this?就需要“被忽略”。

為了實(shí)現(xiàn)這樣的規(guī)則,開發(fā)者就應(yīng)該需要考慮如何區(qū)分這兩種調(diào)用方式。具體來講?bound?函數(shù)中就要進(jìn)行?this instanceof?的判斷。

另外一個(gè)細(xì)節(jié)是,函數(shù)具有?length?屬性,表示形參的個(gè)數(shù)。上述實(shí)現(xiàn)方式形參的個(gè)數(shù)顯然會(huì)失真。我們的實(shí)現(xiàn)就需要對(duì)?length?屬性進(jìn)行還原。可是難點(diǎn)在于:函數(shù)的?length?屬性值是不可重寫的。

總結(jié)

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

上一篇 常用公共前端CDN庫
下一篇 nginx禁止直接訪問目錄或文件
目錄
applek

applek管理員

個(gè)人說明在個(gè)人中心里面設(shè)置

本月創(chuàng)作熱力圖

最新評(píng)論
小超
小超
7月9日
下個(gè)版本考慮將下載地址加密(防止采集)
Lvtu
Lvtu
7月4日
qiang厲害
評(píng)論于留言建議
chinacnd
chinacnd
6月26日
這個(gè)主題是怎么做到加載速度那么快的?
風(fēng)起云涌
風(fēng)起云涌
6月17日
前臺(tái)投稿現(xiàn)在有點(diǎn)太抽象了 希望可以弄成那種分區(qū)填寫的 標(biāo)題,內(nèi)容,圖片,目錄,標(biāo)簽,這樣的
評(píng)論于留言建議
主站蜘蛛池模板: 久久精品视频网站| 日韩av免费在线播放| 国产不卡一区二区在线播放| 日本精品一区二区三区在线播放视频 | 欧美日韩国产999| 国产成人综合一区二区三区| 狠狠干视频网站| 久久九九国产视频| 日本丰满少妇黄大片在线观看| 99久久综合狠狠综合久久止| 国产欧美综合一区| 久久精品国产91精品亚洲| 99在线热播| 97久久久免费福利网址| 国产精品av免费| 精品日韩美女| 欧美久久久久久V| 日本午夜精品电影| 日韩av在线播放不卡| 日韩视频在线免费播放| 亚洲97在线观看V| 亚洲综合精品伊人久久| 一区二区三区日韩视频| 亚洲欧洲精品在线| 亚洲v日韩v综合v精品v| 日韩欧美在线一区二区| 日本高清视频一区二区三区 | 国产精品久久久久久久久久三级| 久久免费一级片| 久久99国产综合精品女同| 久久久这里只有精品视频| 精品少妇人妻av一区二区 | 人妻精品无码一区二区三区| 日本不卡久久| 久久国产精品亚洲va麻豆| 国产日韩av高清| 国产精品区免费视频| 国产精品视频999| 不卡视频一区| 日韩在线中文视频| 久久中文字幕在线视频V|