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

詳解前端 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。與此同時,需要讀者在閱讀之外繼續進行消化與吸收。只有“記死”,才能“用活”。

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

applek管理員

個人說明在個人中心里面設置

本月創作熱力圖

最新評論
小超
小超
7月9日
下個版本考慮將下載地址加密(防止采集)
Lvtu
Lvtu
7月4日
qiang厲害
評論于留言建議
chinacnd
chinacnd
6月26日
這個主題是怎么做到加載速度那么快的?
風起云涌
風起云涌
6月17日
前臺投稿現在有點太抽象了 希望可以弄成那種分區填寫的 標題,內容,圖片,目錄,標簽,這樣的
評論于留言建議
主站蜘蛛池模板: 国产成人一区三区| 精品视频一区在线| 国产va免费精品高清在线观看| 欧美在线亚洲在线| 91精品国产91久久久久久吃药| 亚洲欧美日韩综合一区| 国产精品99久久久久久久| 日本久久久a级免费| 国产精品久久国产| 久久国产午夜精品理论片最新版本 | 草莓视频一区| 日韩五码在线观看| 九九热精品在线| 国产中文字幕视频在线观看| 国产在线精品一区二区中文v| 国产日韩av在线播放| 激情五月综合色婷婷一区二区| 欧美日韩精品免费观看视一区二区 | 亚洲人成网站在线播放2019| 欧美激情一级欧美精品| 久久久精品美女| www欧美日韩| 日韩精品视频一区二区在线观看| 国产精品手机播放| 国产精品中文字幕在线| 国产激情综合五月久久| 亚洲综合在线播放| 日韩精品福利片午夜免费观看| 欧美一级成年大片在线观看| 欧美日韩一区在线观看视频| 日韩一区二区三区国产| 午夜精品三级视频福利| 国产成人高潮免费观看精品 | 99久久99| 亚洲人成网站在线播放2019| 麻豆精品视频| 欧美一级免费在线观看| 精品国产依人香蕉在线精品 | 国产精品极品尤物在线观看| 国产欧美日韩精品专区| 中文字幕久久一区|