javascript 面向對象學習(三)——this,bind、apply 和 call

this 是 js 裏繞不開的話題,也是很是容易混淆的概念,今天試着把它理一理。設計模式

this 在非嚴格模式下,老是指向一個對象,在嚴格模式下能夠是任意值,本文僅考慮非嚴格模式。記住它老是指向一個對象對於理解它的意義很重要。this 在實際使用中,大體分爲如下幾種狀況:數組

1. 函數做爲對象的方法調用時,this 指向調用該函數的對象瀏覽器

var obj = {
    name: 'jack',
    getName: function() {
        console.log(this === obj) // true
        console.log(this.name)  // jack
    }
}
obj.getName()

這個應該很好理解,很少說了。app

2. 函數做爲普通函數被調用時,this 指向全局對象。在瀏覽器中,全局對象是window。函數

var name = 'global'
function getName() {
    console.log(this === window) // true
    console.log(this.name) // global
}
getName()

個人理解是上面的代碼能夠改寫爲this

window.name = 'global'
window.getName = function() {
    console.log(this === window) // true
    console.log(this.name) // global
}
window.getName()

這樣其實與狀況1是同樣的,至關於函數做爲對象的方法調用,只不過這裏的對象是全局對象。spa

《Javascript 設計模式與開發實踐》一書中有個例子以下:.net

window.name = 'globalName';
var myObject = {
    name: 'seven',
    getName: function(){
        return this.name
    } 
}

var getName = myObject.getName
console.log(getName())  // globalName

getName 是定義在myObject 對象中的方法,在調用getName 方法時,打印出的倒是全局對象的name,而不是myObject對象的name,這再次證實了 this 並不是指向函數被聲明時的環境對象,而是指向函數被調用時的環境對象prototype

3. 函數做爲構造函數調用時,指向構造出的新對象設計

function Person(name) {
    this.name = name  
}

var jack = new Person('Jack')
console.log(jack.name) // Jack
var rose = new Person('Rose')
console.log(rose.name) // Rose

這裏建立了兩個不一樣名字的對象,打印出的name也是不同的,說明構造函數的 this 會根據建立對象的不一樣而變化。須要注意的是,若是構造函數裏返回了一個Object類型的對象,那麼this會指向這個對象,而不是利用構造函數建立出的對象。咱們在構造函數一章裏也提到過,new 操做符所作的最後一步就是返回新對象,而若是咱們顯式地返回一個對象,就會覆蓋這步操做,this也就再也不指向新對象。

4. 函數做爲事件處理函數調用時,指向觸發事件的元素

document.getElementById("myBtn").addEventListener("click", function(e){
    console.log(this === e.currentTarget) // true
});

5. 箭頭函數

因爲箭頭函數沒有this,它的 this 是繼承父執行上下文裏面的 this。執行上下文後面再討論,如今只要知道簡單對象(非函數)是沒有執行上下文的。

var obj = {
    name:  'obj',
    getName: function() {
console.log(this) // 執行上下文裏的 this
return (()=>{ console.log(this.name) }) } } var fn = obj.getName() fn() // obj

按照狀況2來處理的話,this 指向全局對象,應該輸出 undefined,結果並非。與普通函數不一樣,箭頭函數的 this 是在函數被聲明時決定的,而不是函數被調用時。在這裏,父執行上下文是 getName 函數,也就繼承了 getName 的 this,即 obj。

利用 bind、apply、call 改變 this 指向

bind、apply、call 都是定義在 Function 原型對象上的方法,全部函數對象都能繼承這個方法,三者都能用來改變 this 指向,咱們來看看它們的聯繫與區別。

function fn() {
    console.log(this.name)
}

// bind
var bindfn = fn.bind({name: 'bind'})
bindfn() // bind // apply
fn.apply({name: 'apply'}) // apply // call
fn.call({name: 'call'}) // call

咱們定義了一個函數fn,而後分別調用了它的 bind、apply、call 方法,並傳入一個對象參數,經過打印出的內容能夠看到 this 被綁定到了參數對象上。bind 彷佛有些不一樣,多了一步 bindfn() 調用,這是由於 bind 方法返回的是一個函數,不會當即執行,而調用 apply 和 call 方法會當即執行。

下面再來看一下 fn 函數存在參數的狀況:

function fn(a, b, c) {
    console.log(a, b, c)
}

var bindfn = fn.bind(null, 'bind');
bindfn('A', 'B', 'C');           // bind A B

fn.apply(null, ['apply', 'A']) // apply A undefined

fn.call(null, 'call', 'A');  // bind A undefined

bindfn 打印出的結果是fn調用bind方法時的傳遞的參數加上bindfn傳遞的參數,參數 'C' 被捨棄掉了。調用 apply 和 call 方法打印出的則是傳遞給它們的參數,不同的是,apply 的參數是一個數組(或類數組),call 則是把參數依次傳入函數。這時候再看它們的定義應該會好理解不少:

bind() 方法建立一個新的函數,在 bind() 被調用時,這個新函數的 this 被指定爲 bind() 的第一個參數,而其他參數將做爲新函數的參數,供調用時使用。

apply() 方法調用一個具備給定 this 值的函數,以及做爲一個數組(或類數組對象)提供的參數。

call() 方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

咱們能夠利用它們來借用其餘對象的方法。已知函數的參數列表 arguments 是一個類數組對象,好比上例中函數 fn 的參數 a, b, c,由於它不是一個真正的數組,不能調用數組方法,這時借用 apply/call 方法(bind 也能夠,就是用得比較少)將 this 指向 arguments 就能借用數組方法:

(function(){
    Array.prototype.push.call(arguments, 'c')
    console.log(arguments) // ['a', 'b', 'c']
})('a','b')

值得一提的是,push 方法並非只有數組才能調用,一個對象只要知足1.可讀寫 length 屬性;2.對象自己可存取屬性. 就能夠利用 call / apply 調用 push 方法。

 

參考:

《Javascript 設計模式與開發實踐》

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this

http://www.imooc.com/article/80117

http://www.javashuo.com/article/p-ubcyjxax-dg.html

相關文章
相關標籤/搜索