1. 程式人生 > 實用技巧 >this new bind call apply

this new bind call apply

1. this 指向的型別

this 是在函式被呼叫時確定的,它的指向完全取決於函式呼叫的地方,而不是它被宣告的地方(除箭頭函式外)。當一個函式被呼叫時,會建立一個執行上下文,它包含函式在哪裡被呼叫(呼叫棧),函式的呼叫方式,傳入的引數等資訊,this就是這個記錄的一個屬性,它會在函式執行的過程中被用到。

this在函式的指向有以下幾種場景:

1 作為建構函式被new呼叫;

2 作為物件的方法使用;

3 作為函式直接呼叫

4 被call apply bind呼叫

5 箭頭函式中的this

下面我們分別來討論一下這些場景中this的指向

1.1 new 繫結

函式如果作為建構函式使用new

呼叫時,this繫結的是新建立的建構函式的例項。

function Foo() {
console.log(this)
}

var bar = new Foo() // 輸出: Foo 例項,this 就是 bar

實際上使用new呼叫建構函式時,會依次執行下面的操作:

  1. 建立一個新物件;
  2. 建構函式的prototype被賦值給這個新物件的__proto__
  3. 將新物件賦給當前的this
  4. 執行建構函式;
  5. 如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件,如果返回的不是物件將被忽略;

1.2 顯式繫結

通過callapplybind我們可以修改函式繫結的this

,使其成為我們指定的物件。通過這些方法的第一個引數我們可以顯式地繫結this

function foo(name, price) {
this.name = name
this.price = price
}

function Food(category, name, price) {
foo.call(this, name, price) // call 方式呼叫
// foo.apply(this, [name, price]) // apply 方式呼叫
this.category = category
}

new Food('食品', '漢堡', '5塊錢')

// 瀏覽器中輸出: {name: "漢堡", price: "5塊錢", category: "食品"}

call 和 apply 的區別是 call 方法接受的是引數列表,而 apply 方法接受的是一個引數陣列。

func.call(thisArg, arg1, arg2, ...) // call 用法
func.apply(thisArg, [arg1, arg2, ...]) // apply 用法

bind()方法建立一個新的函式,在bind()被呼叫時,這個新函式的this被指定為bind()的第一個引數,而其餘引數將作為新函式的引數,供呼叫時使用。

附參考連結:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

func.bind(thisArg[, arg1[, arg2[, ...]]]) // bind 用法

舉個例子:

var food = {
    name: '漢堡',
    price: '5塊錢',
    getPrice: function(place) {
        console.log(place + this.price)
    }
}

food.getPrice('KFC ')   // 瀏覽器中輸出: "KFC 5塊錢"

var getPrice1 = food.getPrice.bind({ name: '雞腿', price: '7塊錢' }, '肯打雞 ')
getPrice1()       // 瀏覽器中輸出: "肯打雞 7塊錢"


1.3 隱式繫結

函式是否在某個上下文物件中呼叫,如果是的話this繫結的是那個上下文物件。

var a = 'hello'

var obj = {
a: 'world',
foo: function() {
console.log(this.a)
}
}

obj.foo() // 瀏覽器中輸出: "world"

上面程式碼中,foo方法是作為物件的屬性呼叫的,那麼此時foo方法執行時,this指向obj物件。也就是說,此時this指向呼叫這個方法的物件,如果嵌套了多個物件,那麼指向最後一個呼叫這個方法的物件:

var a = 'hello'

var obj = {
a: 'world',
b:{
a:'China',
foo: function() {
console.log(this.a)
}
}
}

obj.b.foo() // 瀏覽器中輸出: "China"

最後一個物件是obj上的b,那麼此時foo方法執行時,其中的this指向的就是b物件。

1.4 預設繫結

函式獨立呼叫,直接使用不帶任何修飾的函式引用進行呼叫,也是上面幾種繫結途徑之外的方式。非嚴格模式下this繫結到全域性物件(瀏覽器下是winodw,node 環境是global),嚴格模式下this繫結到undefined(因為嚴格模式不允許this指向全域性物件)。

var a = 'hello'

function foo() {
    var a = 'world'
    console.log(this.a)
    console.log(this)
}

foo()             // 相當於執行 window.foo()

// 瀏覽器中輸出: "hello"
// 瀏覽器中輸出: Window 物件

上面程式碼中,變數a被宣告在全域性作用域,成為全域性物件window的一個同名屬性。函式foo被執行時,this此時指向的是全域性物件,因此打印出來的a是全域性物件的屬性

注意有一種情況:

var a = 'hello'

var obj = {
    a: 'world',
    foo: function() {
        console.log(this.a)
    }
}

var bar = obj.foo

bar()              // 瀏覽器中輸出: "hello"

此時bar函式,也就是obj上的foo方法為什麼又指向了全域性物件呢,是因為bar方法此時是作為函式獨立呼叫的,所以此時的場景屬於預設繫結,而不是隱式繫結。這種情況和把方法作為回撥函式的場景類似:

var a = 'hello'

var obj = {
    a: 'world',
    foo: function() {
        console.log(this.a)
    }
}

function func(fn) {
    fn()
}

func(obj.foo)              // 瀏覽器中輸出: "hello"


引數傳遞實際上也是一種隱式的賦值,只不過這裡obj.foo方法是被隱式賦值給了函式func的形參fn,而之前的情景是自己賦值,兩種情景實際上類似。這種場景我們遇到的比較多的是setTimeoutsetInterval,如果回撥函式不是箭頭函式,那麼其中的this指向的就是全域性物件.

其實我們可以把預設綁定當作是隱式繫結的特殊情況,比如上面的bar(),我們可以當作是使用window.bar()的方式呼叫的,此時bar中的this根據隱式繫結的情景指向的就是window

2. this 繫結的優先順序

2. this 繫結的優先順序

this存在多個使用場景,那麼多個場景同時出現的時候,this到底應該如何指向呢。這裡存在一個優先順序的概念,this根據優先順序來確定指向。優先順序:new 繫結 > 顯示繫結 > 隱式繫結 > 預設繫結

所以this的判斷順序:

  1. new 繫結:函式是否在new中呼叫?如果是的話this繫結的是新建立的物件;
  2. 顯式繫結:函式是否是通過bindcallapply呼叫?如果是的話,this繫結的是指定的物件;
  3. 隱式繫結:函式是否在某個上下文物件中呼叫?如果是的話,this繫結的是那個上下文物件;
  4. 如果都不是的話,使用預設繫結。如果在嚴格模式下,就繫結到undefined,否則繫結到全域性物件;

3. 箭頭函式中的 this

箭頭函式是根據其宣告的地方來決定this的,它是 ES6 中出現的知識點,在後文 <基礎篇:ES6 中可能遇到的知識點> 中會有更詳細講解。

箭頭函式的this繫結是無法通過callapplybind被修改的,且因為箭頭函式沒有建構函式constructor,所以也不可以使用new呼叫,即不能作為建構函式,否則會報錯。

var a = 'hello'

var obj = {
    a: 'world',
    foo: () => {
        console.log(this.a)
    }
}

obj.foo()             // 瀏覽器中輸出: "hello"