講清楚之 javascript中的this

講清楚之 javascript中的this

這一節來討論this。 在 javascript 中 this 也是一個神的存在,相干於 java 等言語在編譯階段肯定,而在 javascript 中, this 是動態綁定,也就是在運轉期綁定的。這致使了 javascript 中 this 的靈活性,而且對辨認對象差別的挪用場景下 this 指向帶來了一些攪擾。

在全局環境中this指向window,即this === window。this 的靈活性重要表現在函數環境中,輕易剖斷失足的也是函數環境下的 this 指向.

this 是函數內部的可見屬性之一(另一個是 arguments), 在函數內部我們能夠直接運用this接見指定對象的屬性。那末指定對象是怎樣肯定的?

下面就繚繞 this 的指向對象來梳理

this 是怎樣肯定的?

起首 this 是在函數被挪用時肯定的, 也就是在進入函數后,函數內表達式、語句實行前的變量對象建立階段肯定的。

而它指向什麼取決於函數在那裡被挪用(在什麼對象上被挪用).

平常情況須要關注函數在那裡被挪用、被怎樣挪用.

下面重要剖析函數在差別挪用場景下this的指向.

當函數作為要領被對象具有並挪用時 this 指向該對象,不然 this 為 undefind

把題目睜開形貌就是: 當函數挪用時是被某一個對象所具有,函數內的
this將綁定到該對象上。假如函數是自力挪用的,則函數內部的 this 在嚴厲形式下為 undefind, 在非嚴厲形式下 this 會指向 window(node.js中指向global)。

根據上面的準繩,我們主要推斷的是函數被所具有,舉幾個栗子更好明白:

栗子1:

let a = 1
function foo () {
    console.log(this.a)
}

foo() // 1

foo() 是在全局環境下自力挪用的,此時函數 foo 被全局對象具有(this 指向 window),所以this.a獵取的是 window 全局對象下面的 a.

栗子2:

var a = 1
var foo = function (){
    console.log(this.a)
}
var too = {
    a: 2,
    b: foo
}
var bar = too.b

foo() // 1
too.b() // 2
bar() // 1

函數實行時肯定 this 指向的大抵邏輯:

foo() :

  • 在全局對象 window 下挪用,所以輸出1。

too.b():

  • 對象 too 的屬性 b 指向函數 foo,此時函數 foo 是 對象 too 內部的一個要領;
  • too.b()實行時,b 是被對象 too挪用的,此時內部的 this 指向 對象 too;
  • 所以this.a獵取的是too.a,輸出2;

bar():

  • 對象 too 的要領被賦值給 bar, 即此時 bar 標識符同 foo 標識符一樣都指向同一個棧地點所代表的函數。
  • 實行bar()此時在全局對下 window 下挪用,所以輸出1。

栗子3:

var a = 1
var foo = function () {
    console.log(this.a)
}
var too = function (fn) {
    var a = 2
    fn()
}
too(foo) // 1

too(foo) :這裏函數 foo 作為參數被通報到函數 too的內部實行, fn()實行時並沒有被其他對象顯現具有,所以我們隱式的剖斷fn()是在全局對象 window 下面實行的,所以輸出 1 。

這個栗子很輕易搞錯(我本身覺得每過一段時間再看照樣會錯o(︶︿︶)o),第一印象輸出的應該是2,那是應為把 this 與作用域鏈弄殽雜了。一直要記着作用域鏈是在源代碼編碼階段就肯定了,而 this 是在函數運轉階段才肯定,屬於
實行高低文的觀點,是在運轉階段根據
this地點函數被誰挪用來肯定的。

我們再來把上面的栗子輕微修正一下

栗子4:

var a = 1
var foo = function () {
    console.log(this.a) // 輸出 1
    console.log(a) // 區分在這裏, 輸出 1
}
var too = function (fn) {
    var a = 2
    fn()
}
too(foo) // 1, 1
表達式
console.log(this.a)基於高低文 this表達式所屬 foo 函數在 too 函數內挪用時 this 指向 window1
console.log(a)基於作用域鏈全局高低文中的變量 a 在 foo 函數作用域鏈上1

不曉得你明白了沒有,這個栗子也表現了高低文作用域在舉行變量/屬性查找時的區分

栗子5:

let c = 1
let foo = {
    c: 2
}
let too = function () {
    console.log(this.c)
}
foo.a = {
    b: too,
    c: 3
}

foo.a.b() // 3

this的綁定只受最靠近的成員援用的影響。foo.a.b()函數b作為對象foo.a的要領被挪用,所以此時的this綁定到foo.ab與對象foo的包括成員沒有多大關聯,最靠近的對象才決議this的綁定。

末了console.log(this.c)取到的是foo.ac的值 3 .

栗子5:

let a = 1
let foo = {
    a: 2,
    msg: function () {
        console.log(`hi, ${this.a}`)
    }
}
let too = Object.create(foo)

too.msg() // hi, 2

上面用對象foo作為原型建立新對象too, 所以對象 too 繼續對象 foo 的一切屬性、要領。too.msg()實行時,msg 函數被 too 挪用,此時this就指向對象too, 所以console.log(hi, ${this.a})接見的是從對象foo繼續來的 a.

所以關於在對象原型鏈上某處定義的要領,一樣的觀點也實用。假如該要領存在於一個對象的原型鏈上,那末對象實例的this指向的是挪用這個要領的對象,經由過程this能夠接見到真相鏈上的要領。

經由過程上面的幾個栗子考證了我們的總結:

當函數作為對象要領挪用時 this 指向該對象,作為函數自力挪用時 this 指向全局對象 window (嚴厲形式下 this 為 undefind )。

大部分時刻根據上面的準繩來推斷 this 的指向是沒有問題,然則 this 另有以下幾種特別場景須要注重。

當函數作為組織函數挪用,此時函數內部的 this 指向函數本身

函數也是對象

栗子:

function Foo (a) {
    this.a = a // 實例化后 this 指向 too
}
let too = new Foo(1)

我們曉得函數 this 在運轉期肯定,而組織函數實例化時在內部實際上是為我們建立了一個新的對象,經由過程一系列的操縱后將 this 指向了這個新對象。

new操縱符實行時的邏輯推導以下:

  1. 建立一個新的空對象;
  2. 將組織函數的 this 指向這個新對象;
  3. 將組織函數的真相添加到新對象的真相鏈里,將屬性、要領掛載在新對象上;
  4. 返回這個新對象

返回的新對象就是我們實例化的對象。即, new 操縱符挪用組織函數時,this 是指向內部建立的新對象,末了會將新建立的對象返回給實例變量。

所以當函數作為組織函數挪用,則函數內部的 this 綁定到該函數上。在經由過程組織函數實例化對象時,對象內部的 this 也一樣指向該實例對象。

當函數運用 cal、apply 要領挪用實行,此時 this 指向 call、apply 要領傳入的對象

在 javascript 里函數也是對象。 一切函數都繼續於 Function 組織函數,而 call、apply 是 Function.prototype 真相的要領,所以函數都從真相的真相里繼續了 call、apply 要領。

call、apply 用於向函數注入 this 對象和變量(call 與 apply 的區分在於通報的參數不一樣,其他沒有區分)。

let a = 1
let too = {
    a: 2
}
function foo () {
    console.log(this.a)
}
foo.call(too) // 2

函數 foo 內里的 this 此時指向call通報進來的對象 too,所以this.a打印的是 2.

假如通報給 this 的值不是一個對象,JavaScript 會嘗試運用內部 ToObject 操縱將其轉換為對象。因而,假如通報的值是一個原始值比方 7'foo',那末就會運用相干組織函數將它轉換為對象,所以原始值 7 會被轉換為對象: new Number(7) ,而字符串 'foo' 轉化成 new String('foo')

function bar() {
  console.log(this, Object.prototype.toString.call(this));
}

//原始值 7 被隱式轉換為對象
bar.call(7); // Number {[[PrimitiveValue]]: 7}__proto__: Number[[PrimitiveValue]]: 7 "[object Number]"

ECMAScript 5 引入了 Function.prototype.bind。當函數挪用f.bind(someObject)會建立一個與f具有雷同函數體和作用域的函數,然則在這個新函數中,this將永遠地被綁定到了bind對象someObject

栗子:

let a = 1
let too1 = {
    a: 2
}
function foo () {
    console.log(this.a)
}
let bar = foo.bind(too1)
let bar2 = {
    a: 4,
    b: bar
}

bar() // 2
bar.call({a: 3}) // 2
bar2.b() // 2

foo 經由過程bind建立一個新函數時,新函數的this強迫綁定到了傳入的對象too1, 後續實行中bar即使是作為對象要領挪用照樣運用 call、apply 都沒法替代運用 bind 綁定的 this。

當函數是以箭頭函數體式格局建立的,此時的 this 指向箭頭函數實行時的宿主函數的高低文

栗子:

function foo () {
    let that = this
    let too = () => {
        console.log(this === that) // true
    }
    too()
}
foo()

too 為箭頭函數,內部的 this 被指向為他建立時的高低文,即 foo 的 this 。反過來說就是箭頭函數沒有本身的高低文,他同享的是關閉詞法高低文。

注重這裏提到的 “this 是動態綁定,在運轉期綁定的” 重如果指進入函數后,函數運轉前的高低文建立階段(
預處理),此時函數內的表達式、語句並沒有實行。但這裏都統稱為函數運轉期,細緻請關注
變量對象一節的形貌(^.^)。

    原文作者:kooky
    原文地址: https://segmentfault.com/a/1190000015038826
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞