關於this、call、applay和bind

前言

this關鍵字是一個很是重要的語法點。絕不誇張地說,不理解它的含義,大部分開發任務都沒法完成。而this能夠動態切換,爲JavaScript創造了巨大的靈活性的同時,也使得編程變得困難和模糊。有時,須要把this固定下來,避免出現意想不到的狀況。JavaScript 提供了call、apply、bind這三個方法,來切換/固定this的指向。編程

基本概念

this能夠用在構造函數之中,表示實例對象。表示實例對象。除此以外,this還能夠用在別的場合,做爲對象屬性執行、做爲普通函數執行。但不論是什麼場合,this都有一個共同點:它老是返回一個對象! 它老是返回一個對象! 它老是返回一個對象!重要的事情說三遍。數組

簡單說,this就是屬性或方法「當前」所在的對象。瀏覽器

this.name
複製代碼

上面代碼中,this就表明name屬性當前所在的對象。bash

使用this

下面從一個簡單例子開始數據結構

var person = {
  name: '張三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

console.log(person.describe());
// "姓名:張三"

複製代碼

上面代碼中,this.name表示name屬性所在的那個對象。因爲this.name是在describe方法中調用,而describe方法所在的當前對象是person,所以this指向personthis.name就是person.nameapp

再來個例子清楚的理解this動態的指向函數

var A = {
  name: '張三',
  describe: function () {
    return '姓名:'+ this.name;
  }
};

var B = {
  name: '李四'
};

B.describe = A.describe;
console.log(B.describe());
// "姓名:李四"

複製代碼

上面代碼中,A.describe屬性被賦給B,因而B.describe就表示describe方法所在的當前對象是B,因此this.name就指向B.nameui

若是還看不明白,整合第一個和第二個例子,來更加清晰的理解一下下。this

function f() {
  return '姓名:'+ this.name;
}

var A = {
  name: '張三',
  describe: f
};

var B = {
  name: '李四',
  describe: f
};

console.log(A.describe()); // "姓名:張三"
console.log(B.describe()); // "姓名:李四"

複製代碼

上面代碼中,函數f內部使用了this關鍵字,隨着函數f所在的對象不一樣,this的指向也不一樣。spa

總而言之,JavaScript語言之中,一切皆對象,運行環境也是對象,因此函數都是在某個對象之中運行,this就是函數運行時所在的對象(環境)。這原本並不會讓用戶糊塗,可是JavaScript支持運行環境動態切換,也就是說,this的指向是動態的,沒有辦法事先肯定到底指向哪一個對象。

函數中this指向

JavaScript函數是一個單獨的值,它能夠在不一樣的環境(上下文)中執行,同時JavaScript 容許在函數體內部,引用當前環境的其餘變量。

爲了可以在函數體內部得到當前的運行環境(context)。因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

JavaScript 語言之因此有this的設計,跟內存裏面的數據結構有關係。基本數據類型是按值訪問的,引用類型存儲在內存中。

var obj = { foo:  5 };
複製代碼

一、對於對象裏面的屬性存儲德是基本數據類型狀況,變量obj是一個地址(reference)。後面若是要讀取obj.foo,引擎先從obj拿到內存地址,而後再從該地址讀出原始的對象,返回它的foo屬性。

JavaScript 引擎會先在內存裏面,生成一個對象{foo:5},而後把這個對象的內存地址賦值給變量obj

{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}
複製代碼

注意,foo屬性的值保存在屬性描述對象的value屬性裏面。

這樣的結構是很清晰的,問題在於屬性的值多是一個函數。

二、this老是指向函數的直接調用者(而非間接調用者)

var obj = { foo: function () {} };

複製代碼

這時,引擎會將函數單獨保存在內存中,而後再將函數的地址賦值給foo屬性的value屬性。

{
  foo: {
    [[value]]: 函數的地址
    ...
  }
}

複製代碼

因爲函數是一個單獨的值,因此它能夠在不一樣的環境(上下文)執行。

var f = function () {};
var obj = { f: f };

// 單獨執行
f()

// obj 環境執行
obj.f()
JavaScript 容許在函數體內部,引用當前環境的其餘變量。

var f = function () {
  console.log(x);
};

複製代碼

上面代碼中,函數體裏面使用了變量x。該變量由運行環境提供。

如今問題就來了,因爲函數能夠在不一樣的運行環境執行,因此須要有一種機制,可以在函數體內部得到當前的運行環境(對象)(context)。因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

總之,this老是指向函數的直接調用者(而非間接調用者)

改變this指向

this的動態切換,當然爲 JavaScript 創造了巨大的靈活性,但也使得編程變得困難和模糊。有時,須要把this固定下來,避免出現意想不到的狀況。JavaScript提供了call、apply、bind這三個方法,來切換/固定this的指向。

call

一、使用call函數

函數實例的 call方法,能夠指定函數內部this的指向(即函數執行時所在的做用域),而後在所指定的做用域中,調用該函數。

var obj = {};

var f = function () {
  return this;
};

f() === window // true
f.call(obj) === obj // true

複製代碼

二、call方法的參數

call方法的參數,應該是一個對象。若是參數爲空、null和undefined,則默認傳入全局對象

var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456

複製代碼

若是call方法的參數是一個原始值,那麼這個原始值會自動轉成對應的包裝對象,而後傳入call方法。

var f = function () {
  return this;
};

f.call(5)
// Number {[[PrimitiveValue]]: 5}

複製代碼

call方法還能夠接受多個參數。call的第一個參數就是this所要指向的那個對象,後面的參數則是函數調用時所需的參數。

func.call(thisValue, arg1, arg2, ...)
複製代碼

三、call方法應用

call方法的一個應用是調用對象的原生方法。hasOwnPropertyobj對象繼承的方法,若是這個方法一旦被覆蓋,就不會獲得正確結果。call方法能夠解決這個問題,它將hasOwnProperty方法的原始定義放到obj對象上執行,這樣不管obj上有沒有同名方法,都不會影響結果。

var obj = {};
obj.hasOwnProperty('toString') // false

// 覆蓋掉繼承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

複製代碼

apply

一、使用apply

var a = {
        name : "Cherry",

        func1: function () {
            console.log(this.name)
        },

        func2: function () {
            setTimeout(  function () {
                this.func1()
            }.apply(a),100);
        }

    };

    a.func2()            // Cherry

複製代碼

二、apply參數

它接收一個數組做爲函數執行時的參數,使用格式以下。

func.apply(thisValue, [arg1, arg2, ...])
複製代碼

三、apply應用

(1)找出數組最大元素

JavaScript 不提供找出數組最大元素的函數。結合使用apply方法和Math.max方法,就能夠返回數組的最大元素。

var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15

複製代碼

(2)將數組的空元素變爲undefined

經過apply方法,利用Array構造函數將數組的空元素變成undefined

Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]

複製代碼

bind

一、使用bind()

bind方法用於將函數體內的this綁定到某個對象,而後返回一個新函數。

var d = new Date();
d.getTime() // 1481869925657

var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.

複製代碼

上面代碼中,咱們將d.getTime方法賦給變量print,而後調用print就報錯了。這是由於getTime方法內部的this,綁定Date對象的實例,賦給變量print之後,內部的this已經不指向Date對象的實例了。

bind方法能夠解決這個問題。

var print = d.getTime.bind(d);
print() // 1481869925657

複製代碼

二、bind參數

bind方法的參數就是所要綁定this的對象,下面是一個更清晰的例子。

var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};

var func = counter.inc.bind(counter);
func();
counter.count // 1
複製代碼

bind還能夠接受更多的參數,將這些參數綁定原函數的參數。

var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);
newAdd(5) // 20
複製代碼

若是bind方法的第一個參數是nullundefined,等於將this綁定到全局對象,函數運行時this指向頂層對象(瀏覽器爲window)。

function add(x, y) {
  return x + y;
}

var plus5 = add.bind(null, 5);
plus5(10) // 15

複製代碼

三、bind的應用 結合call方法使用

利用bind方法,能夠改寫一些JavaScript原生方法的使用形式,以數組的slice方法爲例。

[1, 2, 3].slice(0, 1) // [1]
// 等同於
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]

複製代碼

call方法實質上是調用Function.prototype.call方法,所以上面的表達式能夠用bind方法改寫。

var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]

複製代碼
相關文章
相關標籤/搜索