javascript中call、apply、bind方法的使用

image

context的概念

在知道我們為什么要使用call、apply、bind方法之前,我覺得有必要先了解一下context的相關概念,通常context的作用是取決于函數將如何被調用,當函數作為對象的方法調用時,this就會被設置為調用方法的對象:

 var object = {
        foo: function(){
            console.log(this === object);
        }
    };

    object.foo(); // true

當通過new一個對象的實例的方式來調用一個函數的時候,this的值將被設置為新創建的實例:

  function foo(){
        console.log(this);
    }

    foo() // window
    new foo() // foo{}

當作為未綁定對象被調用時,this默認指向全局上下文或者瀏覽器中的window對象。然而,如果函數在嚴格模式下被執行,上下文將被默認為undefined

動態改變this的值

然而call、apply、bind方法允許你在自定義的context中執行函數,什么意思呢,通俗的說就是可以不遵循上面給出的部分定義,可以動態的改變this,我們直接引出MDN關于call方法的定義:

call() 方法調用一個函數, 其具有一個指定的this值和分別地提供的參數(參數的列表)

舉個例子:

// 非嚴格模式下
function add(args) {
        console.log(this);
    }
    function sub(args) {
        console.log(this);
    }
    add(); // Window {frames: Window, postMessage: ?, blur: ?, focus: ?, close: ?, …}
    sub(); // Window {frames: Window, postMessage: ?, blur: ?, focus: ?, close: ?, …}

結果是符合預期的:

當作為未綁定對象被調用時,this默認指向全局上下文或者瀏覽器中的window對象

我們給他加上call方法:

// 非嚴格模式下
 function add(args) {
        console.log(this);
    }
    function sub(args) {
        console.log(this);
    }
    add(); // Window {frames: Window, postMessage: ?, blur: ?, focus: ?, close: ?, …}
    sub(); // Window {frames: Window, postMessage: ?, blur: ?, focus: ?, close: ?, …}
    add.call(sub,1); // ? sub(args) { ... }
    sub.call(add,1); // ? add(args) { ... }

這時候this的值有點類似于第一種情況:

當函數作為對象的方法調用時,this就會被設置為調用方法的對象

看起來我們調用call方法的時候好像實際上是進行了對象方法的調用:

    sub.call(add,1);  //sub
    =>
    var sub = {
        add: function(){
            console.log(this);
        }
    };
    sub.add(); // sub

這就是動態的改變了this
還有一點要注意的是,這里call和apply方法第一個參數的this值并不一定是該函數執行時真正的this值,比如在非嚴格模式下,我們傳入nullundefined會自動指向全局對象,在瀏覽器環境中就是window,在node環境中就是global,另外我們還可以傳入一些原始值(數字,字符串,布爾值),這時候的this會指向這些原始值的包裝對象,比如傳入的是數字,會指向Number。

call和apply的區別

既然call和apply方法能夠動態的改變this的值,我們可以利用這個特性來實現簡單的繼承,比如此時我們有一個父構造函數:

function Product(name, age) {
        this.name = name;
        this.age = age;
        this.say = function() {
            console.log(this.name+"is"+this.age+"years old");
        }
    }

如果我們想寫一個子構造函數,只需要在子構造函數里面調用父構造函數的call方法就可以實現繼承:

function Toy(name, price) {
        Product.call(this, name, price);
        this.self = "single";
    }

實現繼承的同時父子構造函數還都能分別有自己的屬性,比如這里的self屬性。
這里使用了call方法作為演示,其實apply方法的使用和call方法并無多大區別,重點在參數上,他們的第一個參數都是一個指定的this值,這個值可以是任意js對象,但是第二個參數有區別,call方法接受的是若干個參數的列表,而apply方法接受的是一個包含多個參數的數組,如果上面的例子用apply來寫的話就是這樣:

function Product(name, age) {
        this.name = name;
        this.age = age;
        this.say = function() {
            console.log(this.name+"is"+this.age+"years old");
        }
    }

    function Toy(name, price) {
        Product.apply(this, [name, price]);
        this.self = "single";
    }

bind方法的特殊性

之所以把bind方法單獨放出來是因為bind方法和前面兩者還是有不小的區別的,雖然都是動態改變this的值,舉個例子:

   var obj = {
        x: 81,
    };

    var foo = {
        getX: function() {
            return this.x;
        }
    }
    console.log(foo.getX.bind(obj)());  //81
    console.log(foo.getX.call(obj));    //81
    console.log(foo.getX.apply(obj));   //81

有沒有注意到使用bind方法時候后面還要多加上一對括號,因為使用bind只是返回了對應函數并沒有立即執行,而call和apply方法是立即執行的,并且MDN還提到,當使用new操作符調用綁定函數時指定的this參數會變無效:

 var obj = {
        x: 81,
    };

    var foo = {
        getX: function() {
            return this.x;
        }
    }
    var result = foo.getX.bind(obj);
    console.log(new result());  //getX

bind方法的另一個應用是使一個函數擁有預設的初始函數,這些參數作為bind的第二個參數跟在this(或其他對象)后面,之后它們會被插入到目標函數的參數列表的開始位置,傳遞給綁定函數的參數會跟在它們的后面:

function list() {
  return Array.prototype.slice.call(arguments);
}

var list1 = list(1, 2, 3); // [1, 2, 3]

// Create a function with a preset leading argument
var leadingThirtysevenList = list.bind(undefined, 37);

var list2 = leadingThirtysevenList(); // [37]
var list3 = leadingThirtysevenList(1, 2, 3); // [37, 1, 2, 3]

參考鏈接

Understanding Scope and Context in JavaScript

Function.prototype.call()

同步發表在:
csdn博客
個人博客

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容