全面解析JS中的this

this是什麼

在javascript中,每一個執行上下文能夠抽象成一組對象
execution-context.pngjavascript

this是與執行上下文相關的特殊對象,任何對象均可以用做this上下文的值,一個重要的注意事項就是this值是執行上下文的屬性,但不是變量對象的屬性。這樣的話,與變量相反,this值不會參與標識符解析,即在訪問代碼時,他的值直接來自執行上下文,也沒有任何做用域鏈查找,在進入上下文中,this只能肯定一次。因此this的值是和其所處的上下文環境有關係。java

this全面解析

1.調用位置

在歷屆this的綁定以前,首先要理解調用位置,調用位置就是函數在代碼中內調用的位置(而不是聲明的位置),例子:數組

function baz(){
    // 當前調用棧是: baz
    // 所以,當前調用位置是全局做用域
    console.log("baz");
    bar(); // <-- bar的調用位置
}

function bar(){
    // 當前調用棧是baz -> bar
    // 所以調用位置在baz中
    console.log("bar");
    foo();// <-foo的調用位置
}
function foo(){
    // 當前調用棧是baz -> bar -> foo
    // 調用位置在bar中
    
    console.log("foo");
}
baz() // <- baz的調用位置
2. 綁定規則
2.1 默認綁定

首先要介紹的最多見的函數調用類型:獨立函數調用。能夠把這條規則看做是沒法應用其餘規則時的默認規則瀏覽器

function foo(){
    console.log(this.a);
}
var a = 2;
foo(); //2

由於foo()在全局執行上下文中調用,因此this指向全局變量
若是使用嚴格模式,則不能將全局對象用於默認綁定,所以this會綁定到undefined:app

function foo(){
    "use strict"
    console.log(this.a);
}
var a = 2;
foo(); //error

雖然this的綁定規則徹底取決於調用位置,可是隻有foo()運行在非嚴格模式下時。默認綁定才能綁定到全局對象;在嚴格模式下調用foo()則不影響默認綁定函數

function foo(){
    console.log(this.a);
}
var a = 2;
(function(){
    foo(); //2
})()
2.2 隱式綁定
function foo(){
    console.log(this.a);
}

var obj = {
    a: 42,
    foo: foo
};

obj.foo();

當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象,由於調用foo()this被綁定到obj,所以this.aobj.a是同樣的
對象屬性引用鏈中只有上一層或者說最後一層在調用位置起做用oop

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

var obj2 = {
    a: 42,
    foo: foo
}

var obj1 = {
    a: 2,
    obj2: obj2
}

obj1.obj2.foo(); //42
2.3 隱式丟失

被隱式綁定的函數會丟失綁定對象,也就是說他會應用默認綁定,從而把this綁定到全局對象或者undefinedthis

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

var obj = {
    a:2,
    foo: foo
};

var bar = obj.foo; //函數別名
var a = "oops, global"; //a是全局對象的屬性。
bar(); //"oops, global"

雖然bar引用了obj.foo這個引用,但實際上他引用的是foo函數的自己。也就是說bar()是一個在全局上下文中調用的函數,所以this指向了全局對象。
這種情形頁出如今參數傳遞中。編碼

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

function doFoo(fn){
    fn();
}

var obj = {
    a: 2,
    foo: foo
};

var a = "oops, global"; //a是全局對象的屬性
doFoo(obj.foo);

參數傳遞其實就是一種隱式賦值,所以咱們傳入函數也會被隱式賦值。spa

2.4 顯示綁定

在分析隱式綁定時,咱們必須在一個對象內部包含一個指向函數的屬性,並經過這個屬性間接引用函數,從而把this間接(隱式)綁定到這個都對象上。
當咱們不想再對象內部間接包含引用函數,而像在某個對象上強制調用函數。咱們能夠用javascript中內置的applycall的方法來實現,這兩個方法的第一個參數是一個對象,是給this準備的,接着再調用函數時將其綁定到this。由於你能夠直接指定this的綁定對象,所以咱們稱之爲顯式綁定。

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
foo.call(obj);  //2

經過foo.call(...)咱們能夠在調用foo時強制把他的this綁定到obj上。若是你傳入一個原始值(字符串類型,布爾類型或者數字類型)來看成this的綁定對象,這個原始值會被轉換成他的對象形式,也就是「裝箱」
咱們經過顯示綁定的變種解決綁定丟失的問題

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}
var bar = function(){
    foo.call(obj);
};

bar(); //2
setTimeout(bar, 100); //2
//硬綁定的bar不可能在修改他的this
bar.call(window); //2

硬綁定的典型應用場景就是建立一個包裹函數,負責接收參數並返回值

function foo(something){
    console.log(this.a, something);
    return this.a + something;
}

var obj = {
    a: 2
}

var bar = function(){
    return foo.apply(obj, arguments);
} 
var b = bar(3); //2. 3
console.log(b); //5

另外一種方式則是建立一個能夠重複使用的輔助函數

function foo(something){
    console.log(this.a, something);
    return this.a + something;
}

var obj = {
    a: 2
}

function bind(fn, obj){
    return function(){
        return fn.apply(obj, arguments);
    }
}

var bar = bind(foo, obj);
var b = bar(3); //2 3
console.log(b); //5
2.5 apply,call,bind

以前介紹了applycall能夠改變this的指向,如今來說講他們的區別以及ES5新增的方法bind
applycall之間最主要的區別在於傳入參形式的不一樣。他倆的第一個參數都是指定了函數體內的this指向。
而第二個參數apply傳入爲一個帶下標的集合,這個集能夠爲數組,也能夠爲類數組。apply方法把這個集合中的元素做爲參數傳遞給被調用的函數

var func = function(a,b,c){
    alert([a, b, c]);  //1 2 3
}
func.apply(null, [1, 2, 3])

call傳入的參數數量不固定,跟apply相同的是,第一個參數也是函數體內的this指向,從第二個參數開始日後,每一個參數依次傳入函數。

var func = function(a, b, c){
    alert([a, b, c]); //1 2 3
}
func.call(null, 1, 2, 3);

當使用call或者apply的時候,若是咱們傳入的第一個參數爲null,函數體內的this會指向默認的宿主對象,在瀏覽器是window
大多數的高級瀏覽器已經實現了bind方法用來指定函數內部的this的指向

function foo(){
    console.log(this.a);
}
var obj = {
    a: 2
}

var bar = foo。bind(obj);

bar(); //2

bind(..)會返回一個硬編碼的新函數,他會把你指定的參數設置爲this的上下文並調用原始函數
咱們也能夠用apply模仿一個bind

Function.prototype.bind = function(){
    var self = this;
    var context = Array.prototype.shift().call(arguments);
    var args = Array.prototype.slice().call(arguments);
    return function(){
        this.apply(context, Array.prototype.concat.call(args, Array.prototype.shift().call(arguments);))
    }
}
var obj = {
    name: 'foo'
};

var func = function(a, b, c, d){
    console.log(this.name);
    console.log([a, b, c, d]) //
}.bind(obj, 1, 2);

func(3,4);
2.6 new綁定

在javascript中,構造函數只是一些使用new操做符時調用的函數,它們並不會屬於某個類,也不會是實例化一個類。
使用new來調用函數,或者說發生構函數調用時,會自動執行下面的操做
1.建立(或者說構造)一個去全新的對象。
2.這個新對象會被執行[[prototype]]鏈接
3.這個新對象會綁定到函數調用的this
4.若是函數沒有其餘返回對象,那麼new表達式中的函數調用會自動返回這個新對象。

function foo(a){
    this.a = a;
}
var bar = new foo(2);
console.log(bar.a); //2
軟綁定

前面咱們說過硬綁定這種方式綁定以後沒法修改this值,會下降函數靈活性。
若是能夠給默認綁定指定一個全局對象和undefined之外的值,那就能夠實現和硬綁定相同過的效果,同hi是保留隱式綁定或者顯示綁定修改this的能力

Function.prototype.softbind = function(){
     var self = this;
     var context = [].shift.call(arguments);
     var args = [].slice.call(arguments);
     var bound = function(){
     return self.apply((!this|| this === (window || global))?obj:this, [].concat.call(args, [].slice.call(arguments)));
     }
     bound.prototype =Object.create(self);
     return bound;
}
    function foo(){
        console.log(this.name);
    }
    var obj = {
        name: 'obj'
    }
    var obj1 = {
        name: 'obj1'
    }
    var fooobj = foo.softbind(obj, 1);
    fooobj();  //name: obj
    obj1.foo = foo.softbind(obj);
    obj1.foo();    //name: obj1
    setTimeout(obj1.foo, 10); //name: obj

能夠看到,軟綁定版本的foo()能夠手動講this綁定到obj1上,但若是應用默認綁定,則會將this綁定到obj上

this 語法

ES6中出現了不一樣與以上四種規則的特殊函數類型: 箭頭函數。它是根據外層(外層或者全局)做用域來決定的

function foo(){
    return (a) => {
        //this 繼承來自foo()
        console.log(this.a)
    };
}
var obj1 = {
    a: 2
}

var obj2 = {
    a: 3
}
var bar = foo.call(obj1);
bar.call(obj2); //2 不是3!

foo()內部建立的箭頭函數會捕獲調用時foo()this,因爲foo()this綁定到obj1,barthis也會綁定到obj1,箭頭函數的綁定沒法被更改。

相關文章
相關標籤/搜索