關於JavaScript中this的指向,你知曉幾分?請速來圍觀!

三石小小發表於2019-02-15

—恢復內容開始—

一、this是什麼東東?

  this是指包含它的函式作為方法被呼叫時所屬的物件。這句話理解起來跟卵一樣看不懂,但是如果你把它拆分開來變成這三句話後就好理解一點了。

  1.包含它的函式

  2.作為方法被呼叫時

  3.所屬的物件

二、this的繫結規則

  1、預設繫結  

var x = 0;
function num(){
    this.x = 1;
}
console.log(this.x);//0
num();
console.log(this.x);//1

//
當沒有使用let或者var時,宣告的變數是全域性變數,指向window
//在這一形態下,其函式內部的this是與全域性作用域時一樣,直接指向window,執行完num()後,更改了x的值,所以其形態 依然等價於window。
      function foo(){
      console.log(
this.a)     }     var a = 2;     foo(); //2     console.log(this.document === document); // true     // 在瀏覽器中,全域性物件為 window 物件:     console.log(this === window); // true     this.a = 3;     console.log(window.a); // 3     this指向全域性物件。在非嚴格模式中,當funcion被不帶任何修飾的函式直接引用進行呼叫的,只能使用預設繫結,無法應用其他規則  

 

    

  2、隱式繫結

    先看兩段例子   

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

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

obj.foo() //2
//隱式繫結————呼叫位置使用obj上下文來引用函式,可以說函式被呼叫時obj物件“擁有”或者“包含”它,它就指向誰。
 

 

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

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

var obj1 = {
   a:2,
   obj2:obj2,
   foo:foo }
obj1.foo() //2 obj1.obj2.foo()
//42 //此時可以控制檯檢視obj1,obj2物件裡究竟包含了什麼
//當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的this繫結到這個上下文物件
//物件屬性引用鏈中只有最後一層在呼叫位置中起作用

下面思考這一段會輸出什麼呢?

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

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

var bar = obj.foo;  //這裡bar將引用foo函式本身,所以不帶有函式物件的上下文,this會直接指向window
bar() //?

//為什麼沒有隱式繫結?這種情況稱為隱式丟失。

//因為bar=obj.foo 而obj.foo指向foo 也就是bar = function foo(){console.log(this.a)}
foo中的this指向window,
//在window中並沒有對a進行定義,so,結果是undefined

接下來再看一段會是什麼結果呢?(引數傳遞時的隱式賦值)

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

function doback(fn){
    fn()
}

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

var a = `global`;

doback(obj.foo) //?    顯然答案是global,但是為什麼呢?請繼續往下看!

//隱式丟失--被隱式繫結的函式會丟失繫結物件然後應用預設繫結。
//最後函式執行 doback(obj.foo)時,會進行引數傳遞,也就是 fn = obj.foo,就和上一個例子一樣了。既this指向的是window。

  

  3、顯示繫結

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

var obj = {
    a:2
}
foo.call(obj) //2

//顯式繫結--第一個引數是一個物件,接著在呼叫函式時將其繫結到this,通過foo.call(obj),我們可以在呼叫foo時強制把他的this繫結到obj上
function foo(){
    console.log(this.a)
}

var obj = {
    a:2
}

var a = `3333333`;

var bar = function(){
    foo.call(obj)
}

bar() // 2
bar.call(window) //2

// 硬繫結後bar無論怎麼呼叫,都不會影響foo函式的this繫結
// 通過建立函式bar(),並在內部呼叫foo.call(obj),強制把foo的this繫結到obj上。硬繫結的bar之後無論如何呼叫函式,都只會在obj上呼叫foo。

我們來看一下他的應用:
function foo(num) {
    console.log( this.a, num);
    return this.a + num;
}

var obj = {
    a: 2
};

var bar = function() {
    return foo.call( obj, ...arguments ); // 將obj物件硬編碼進去
   //return foo.apply( obj, arguments ); // 也可以使用apply將obj物件硬編碼進去
};

var b = bar( 3 ); // 2 3
console.log( b ); // 5

 


function fn1(){
   console.log(1);
}
function fn2(){
    console.log(2);
}

fn1.call(fn2);     //輸出 1
 
fn1.call.call(fn2);  //輸出 2  這個暫時沒有搞懂,但是東鴿子大師中有講解,感興趣的小夥伴可以看看。

  

  4、new繫結

    我們不去深入瞭解建構函式,但要知道new來呼叫函式,或者說發生建構函式呼叫時,執行了哪些

    當程式碼 new foo(…) 執行時:

 

    (1) 建立一個新物件,它繼承自foo.prototype.

    (2) 將建構函式的作用域賦給新物件(因此 this 就指向了這個新物件);new foo 等同於 new foo(),只能用在不傳遞任何引數的情況。

    (3) 執行建構函式中的程式碼(為這個新物件新增屬性) ;

    (4) 返回新物件, 那麼這個物件會取代整個new出來的結果。如果建構函式沒有返回物件,那麼new出來的結果為步驟1建立的物件。

 

function foo(a){
    this.a = a;
}

var bar = new foo(2);  //建立一個新物件bar,它繼承了foo.prototype.  也就是bar這個物件有a這個屬性,且傳進去的是2,使用new來呼叫foo(..)時,會構造一個新物件,並把它繫結到foo(..)呼叫中的this上
console.log(bar.a) //2 

 

 

三、下面做一個小練習看看你學會了嗎?

例一

function foo(a){ console.log(
this.a) } var obj1 = { a:2, foo:foo } var obj2 = { a:3, foo:foo } obj1.foo() //? obj2.foo() //? obj1.foo.call(obj2) //? obj2.foo.call(obj1) //?





答案:
obj1.foo() //2 隱式繫結
obj2.foo() //3 隱式繫結

obj1.foo.call(obj2) //3 顯式繫結
obj2.foo.call(obj1) //2 顯式繫結

通過答案得出:顯示繫結 > 隱式繫結
顯示繫結優先順序更高,所以在判斷時 應當 優先考慮 是否 存在 顯示繫結

 

 

 

例二

function foo(someting){
this.a = someting } var obj1 = { foo:foo } var obj2 = {}
obj1.foo(
2)
var bar = new obj1.foo(4)
console.log(obj1.a)
//? console.log(bar.a)//?







答案:
console.log(obj1.a) //2  
console.log(bar.a)  //4

通過答案得出: new繫結 > 隱式繫結
顯示繫結優先順序更高,所以在判斷時 應當 優先考慮 是否 存在 new繫結

 

 

需要注意的是:new和call/apply無法一起使用,因此無法通過new foo.call(obj)來直接測試

function foo(someting){
this.a = someting } var obj1 = {} var bar = foo.bind(obj1) //不知道bind()方法的同學可以直接點選此處檢視最騷的就是你同學貢獻的詳解。 bar(2) console.log(obj1.a)//? var baz = new bar(3) console.log(obj1.a)//? console.log(baz.a)//?






答案:2 2 3

通過答案得出:new繫結 > 顯示繫結
new修改了硬繫結呼叫bar(..)中的this,因為使用了new繫結,得到了一個名字為baz的新物件,並且baz.a的值為3.
所以 new繫結 > 顯示繫結 > 隱式繫結 > 預設繫結


 

 

 

 

在此特別鳴謝同事Jason大哥的share!!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

—恢復內容結束—

相關文章