javascript系列--this指向和apply,call,bind三者的區別

一、前言

this指向,apply,call,bind的區別是一個經典的面試問題,同時在項目中會經常使用到的原生的js方法。同時也是ES5中的衆多坑的一個。ES6中可能會極大的避免了this產生的錯誤,有時候需要維護老的項目還是有必要了解一下this的指向和apply,call,bind三者的區別。

二、this的指向

在ES5中,其實this的指向,始終堅持一個原理:this永遠指向最後一個調用它的那個對象。

首先我們看一個栗子1:

var name = "windowsName";
function a() {
    var name = "Cherry";
    console.log(this.name);          // windowsName
    console.log("inner:" + this);    // inner: Window
}
a();
console.log("outer:" + this)         // outer: Window

輸出windowsName,是因爲“this永遠指向最後調用它的那個對象”,我們看到調用a的地方a(),前面沒有調用的對象那麼就是全局對象window,就是全局對象調用a(),相當於window.a()。

如果使用嚴格模式,全局對象就是undefined,會報錯name of undefined

栗子2:

var name = "windowsName";
var a = {
    name: "Cherry",
    fn : function () {
        console.log(this.name);      // Cherry
    }
}
a.fn();

在這個栗子中,函數fn是對象a調用的,所以console是a中的name

栗子3:

var name = "windowsName";
    var a = {
        name: "Cherry",
        fn : function () {
            console.log(this.name);      // Cherry
        }
    }
    window.a.fn();

這個栗子中,記住“this永遠指向最後一個調用它的那個對象”,調用fn的對象有window,a,但是最後調用fn是a對象,所以this指向對象a中的name。

栗子4:

var name = "windowsName";
var a = {
    // name: "Cherry",
    fn : function () {
        console.log(this.name);      // undefined
    }
}
window.a.fn();

爲啥undefined,調用fn的對象有:window,a,最後一個調用fn是a,但是a中沒有對那麼進行定義,也不會繼續向上一個對象尋找 this.name,而是直接輸出 undefined,所以this.name爲undefined。

栗子5(比較坑):

var name = "windowsName";
var a = {
    name : null,
    // name: "Cherry",
    fn : function () {
        console.log(this.name);      // windowsName
    }
}
var f = a.fn;
f();

這個栗子比較坑,爲啥 不是null,因爲雖然將a對象的fn方法賦值給變量f,但是沒有調用,“this永遠執行最後一個調用ta的那個對象”,由於剛剛的f沒有調用,所以fn()最後仍然是被window調用的,所以this指向的也就是window。

注意:this的指向並不是在創建的時候可以確定,在ES5中,永遠都是this永遠指向最後調用它的那個對象。

栗子6:

var name = "windowsName";
function fn() {
    var name = 'Cherry';
    innerFunction();
    function innerFunction() {
        console.log(this.name);      // windowsName
    }
}
fn()

三、怎樣改變this的指向

改變this的指向,我總結以下的方法:

(1)使用ES6中箭頭函數

(2)函數內部使用_this = this

(3)使用apply,call,bind方法

(4)new實例化一個對象

舉個栗子7:

var name = "windowsName";
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)     
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        },100);
    }
};
a.func2()     // this.func1 is not a function

在這個栗子中,不使用箭頭函數情況下,會報錯的,因爲最後調用setTimeout的對象時window,但是在window並沒有func1函數。

我們改變this的指向這一節將吧這個栗子作爲demo進行改造。

1、ES6中的箭頭函數

衆所周知,ES6的箭頭函數是可以避免ES5中this的坑,箭頭函數的this始終指向函數定義時候的this,而並不是執行時候。箭頭函數需要記住這句話:“箭頭函數沒有this綁定,必須通過查找作用域來決定其值,如果箭頭函數被非箭頭函數包含,則this的綁定的是最近一層非箭頭函數的this,否則,this爲undefined”

栗子8:

var name = "windowsName";
var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)     
    },
    func2: function () {
        setTimeout( () => {
            this.func1()
        },100);
    }
};
a.func2()     // Cherry

2、在函數內部使用_this = this

在不使用ES6中,那麼這種方式應該是最簡單的不會出錯的方式,我們先將調用這個函數的對象保存在變量_this中,然後在函數中都使用這個_this,這樣_this就不會改變了。

栗子9:

var name = "windowsName";
var a = {
    
    name : "Cherry",
    func1: function () {
        console.log(this.name)     
    },
    func2: function () {
        var _this = this;
        setTimeout( function() {
            _this.func1()
        },100);
    }
};
a.func2()       // Cherry

在func2中,首先設置var _this = this,這裏this是調用func2的對象a,爲了防止在func2中的setTimeout被window調用而導致的在setTimeout中的this爲window。我們將this賦值給一個變量_this,這樣在func2中我們使用_this就是指向對象a了。

3、使用apply

栗子10:

var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        }.apply(a),100);
    }
};
a.func2()            // Cherry

在栗子中,apply()方法調用一個函數,其具有一個指定的this值,以及作爲一個數組(或者類似數組的對象)提供的參數,fun.apply(thisArg, [argsArray])

thisArg:在fun函數運行時指定的this值。指定this的值並不一定是函數執行時真正的this值,如果是原始值的this會指向該原始值的自動包裝對象。

argsArray:一個數組或者類數組對象,其中的數組元素將作爲單獨的參數傳給fun函數。參數爲null或者undefined,則表示不需要傳入任何參數。

4、使用call

栗子11:

var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        }.call(a),100);
    }
};
a.func2()            // Cherry

在栗子中,call()方法調用一個函數,其具有一個指定的this值,以及若干個參數列表,fun.call(thisArg, arg1, arg2, ...)

thisArg:在fun函數運行時指定的this值。指定this的值並不一定是函數執行時真正的this值,如果是原始值的this會指向該原始值的自動包裝對象。

arg1, arg2, ...:若干個參數列表

5、使用bind

栗子12:

var a = {
    name : "Cherry",
    func1: function () {
        console.log(this.name)
    },
    func2: function () {
        setTimeout(  function () {
            this.func1()
        }.bind(a)(),100);
    }
};
a.func2()            // Cherry

在栗子中,bind()方法創建一個新的函數,當被調用時,將其this的關鍵字設置爲提供的值,在調用新函數時,在任何提供一個給定的參數序列。

bind創建了一個新函數,必須手動去調用。

四、apply,call,bind區別

1、apply和call的區別

apply和call基本類似,他們的區別只是傳入的參數不同。apply傳入的參數是包含多個參數的數組,call傳入的參數是若干個參數列表。

栗子13:

var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b);
        console.log( this.name );
    }
}
var b = a.fn;
b.apply(a,[1,2])     // 3   Cherry

栗子14:

var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b);
        console.log( this.name );
    }
}
var b = a.fn;
b.call(a,1,2)       // 3   Cherry

2、bind和apply、call區別

bind方法會創建一個新的函數,當被調用的時候,將其this關鍵字設置爲提供的值,我們必須手動去調用。

var a ={
    name : "Cherry",
    fn : function (a,b) {
        console.log( a + b);
        console.log( this.name );
    }
}
var b = a.fn;
b.bind(a,1,2)()   //3   //Cherry
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章