天天看點

js的this指向

作者::Wflynn

什麼是函數的調用位置

調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)

為什麼要了解調用位置:隻有了解函數的調用位置才能進一步的确定 ​

​this​

​ 的綁定對象
function baz () {
  // 目前調用棧是:baz, 是以,目前調用位置是全局作用域 
  console.log("baz");
  bar(); // bar 的調用位置
}

function bar () {
  // 目前調用棧是 baz -> bar,是以,目前調用位置在 baz 中 
  console.log("bar");
  fnn(); // fnn 的調用位置
}

function fnn () {
  // 目前調用棧是 baz -> bar -> fnn, 是以,目前調用位置在 bar 中 
  console.log("fnn");
}
baz(); // baz 的調用位置      

this 是什麼

​this​

​ 是包含它的函數作為方法被調用時所屬的對象。

  • 包含它的函數。
  • 作為方法被調用時。
  • 所屬的對象。
随着函數使用場合的不同,this 的值會發生變化。this 指向什麼,完全取決于什麼地方以什麼方式調用,而不是建立時。

this 的四種綁定規則

​this​

​​ 的 ​

​4​

​​ 種綁定規則分别是:預設綁定、隐式綁定、顯式綁定、​

​new​

​ 綁定。優先級從低到高。

​new​

​ 綁定 > 顯式綁定> 隐式綁定 > 預設綁定

預設綁定

最常用的函數調用類型:獨立函數調用。可以把這條規則看作是無法應用其他規則時的預設規則。

如代碼(非嚴格模式下)和圖檔所示
  • ​this.a​

    ​​ 被解析成了​

    ​window.a​

    ​​,​

    ​fnn​

    ​​ 中的​

    ​this​

    ​​ 是等于​

    ​window​

    ​ 的。
  • 由于​

    ​fnn()​

    ​​ 是直接使用不帶任何修飾的函數引用進行調用的,是以隻能使用預設綁定,無法應用其他規則,是以​

    ​this​

    ​ 指向全局對象。。
function fnn() { 
  console.log(this);
  console.log(this === window);
  console.log(this.a); 
}
var a = 2; 
fnn(); // 2      
js的this指向

隐式綁定

調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含

如代碼(非嚴格模式下)和圖檔所示
  • ​this.a​

    ​​ 被解析成了​

    ​obj.a​

    ​​,​

    ​fnn​

    ​​ 中的​

    ​this​

    ​​ 是等于​

    ​obj​

    ​ 的。
  • 由于調用​

    ​fnn()​

    ​​ 函數時,有引用上下文對象​

    ​obj​

    ​​,隐式綁定規則會把函數調用中的​

    ​this​

    ​​ 綁定到這個上下文對象​

    ​obj​

    ​​,是以​

    ​this​

    ​​ 指向​

    ​obj​

    ​ 對象。。
function fnn() { 
  console.log(this);
  console.log(this === obj);
  console.log(this.a); 
}
var obj = { 
  a: 2, 
  fnn: fnn 
};
  
obj.fnn(); // 2      
js的this指向

對象屬性引用鍊中隻有最頂層或者說最後一層會影響調用位置

如代碼所示,最後輸出的 ​

​this.a​

​​ 等于 ​

​888​

​​,因為最後調用 fnn 的上下文對象是 ​

​obj1​

​​,是以 this 綁定在 ​

​obj1​

​ 上
function fnn() { 
  console.log(this);
  console.log(this === obj1); // true
  console.log(this.a); // 888
}

var obj1 = { 
  a: 888, 
  fnn: fnn 
};

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

obj.obj1.fnn(); // this.a 輸出 888      

隐式丢失

一個最常見的 ​

​this​

​​ 綁定問題就是被隐式綁定的函數會丢失綁定對象,也就是說它會應用預設綁定,進而把 ​

​this​

​​ 綁定到全局對象或者 ​

​undefined​

​ 上,取決于是否是嚴格模式。

如代碼所示,最後輸出的 ​

​this.a​

​​ 等于 ​

​888​

​​,雖然 ​

​bar​

​​ 是 ​

​obj.fnn​

​​ 的一個引用,但是實際上,它引用的是 ​

​fnn​

​​ 函數本身,

是以此時的 ​​

​bar()​

​ 其實是一個不帶任何修飾的函數調用,是以應用了預設綁定。
function fnn() { 
  console.log(this); // window 對象
  console.log(this == window); // true
  console.log(this.a); // 888
}
var obj = { 
  a: 2, 
  fnn: fnn 
};
  
var bar = obj.fnn;

var a = "888"; // a 是全局對象的屬性 
bar(); // 888      

回調函數中的例子

function fnn() { 
  console.log(this); // window 對象
  console.log(this == window); // true
  console.log(this.a); // 888
}

function doFnn(fn) { 
  // fn 其實引用的是 fnn 
  fn(); // <-- 調用位置! 
}

var obj = { 
  a: 2, 
  fnn: fnn 
};
  
var a = "888"; // a 是全局對象的屬性 
doFnn(obj.fnn); // 888      

顯式綁定

使用 ​

​call​

​​,​

​apply​

​​ 或者 ​

​bind​

​ 方法綁定

call

​call()​

​​ 方法使用一個指定的 ​

​this​

​ 值和單獨給出的一個或多個參數來調用一個函數

function fnn(arg1,) { 
  console.log(this);
  console.log(this === obj); // true
  console.log(this.a); // 888
  console.log(arg1, arg2); // 參數一 參數二
}
var obj = { 
  a: 888, 
  fnn: fnn 
};
var a = 2
fnn.call(obj, '參數一', '參數二'); // 888      

apply

​apply()​

​​ 方法調用一個具有給定 ​

​this​

​ 值的函數,以及以一個數組(或類數組對象)的形式提供的參數。

function fnn(arg1,) { 
  console.log(this);
  console.log(this === obj); // true
  console.log(this.a); // 888
  console.log(arg1, arg2); // 參數一 參數二
}
var obj = { 
  a: 888, 
  fnn: fnn 
};
var a = 2
fnn.apply(obj, ['參數一', '參數二']); // 888      

bind

​bind()​

​​ 方法建立一個新的函數,在 ​

​bind()​

​​ 被調用時,這個新函數的 ​

​this​

​​ 被指定為 ​

​bind()​

​ 的第一個參數,而其餘參數将作為新函數的參數,供調用時使用。

function fnn(arg1,) { 
  console.log(this);
  console.log(this === obj); // true
  console.log(this.a); // 888
  console.log(arg1, arg2); // 參數一 參數二
}
var obj = { 
  a: 888, 
  fnn: fnn 
};
var a = 2
const fnnBind = fnn.bind(obj, '參數一', '參數二'); 
fnnBind() // 888      

​bind​

​​ 與 ​

​apply​

​​ 和 ​

​call​

​​ 的差別是,​

​bind​

​ 是建立一個函數,但不會直接調用

new 綁定

使用 ​

​new​

​​ 來調用 ​

​People(..)​

​​ 時,我們會構造一個新對象并把它綁定到 ​

​People(..)​

​​ 調用中的 ​

​this​

​ 上。

代碼示例

// 聲明一個構造函數
function People(name){
  console.log(fnn != this); // true
  this.name = name;
}
 
// 使用 new 建立執行個體對象 fnn
var fnn = new People("FX");
console.log(fnn);
console.log(fnn.name) // FX;      

如代碼和圖檔所示,我們 ​

​new​

​ 一個執行個體對象,代碼執行過程如下

var fnn = {} // 建立一個空對象; 或者 var fnn = new Object() 
fnn.__proto__ = People.prototype // 将該對象 fnn 的隐式原型指向構造函數顯式原型
People.call(fnn, "FX") // 将構造函數中 this 指向建立的對象 fnn,并傳入參數 "FX"
return fnn // 傳回對象 fnn,person 指向建立的對象 fnn(對象類型指派為按引用傳遞,fnn 與 person 指向同一個對象)      
為什麼 ​

​console.log(fnn != this)​

​​ 的值為 ​

​true​

​​,關于這一點我還不是特别了解。

按照我得想法,可能是在 ​​

​new​

​ 對象執行個體的過程中,執行個體對象實際并沒有建立完畢,導緻的不相等,如果有更好的了解,歡迎大家留言。

​new​

​ 綁定遇到 ​

​retrun​

function fnn () {
    this.user = 'fx'
    return {}
}
var a = new fnn()
console.log(a.user) // undefined      
function fnn () {
    this.user = 'fx'
    return function () {
    }
}
var a = new fnn()
console.log(a.user) // undefined      
function fnn () {
    this.user = 'fx'
    return 1
}
var a = new fnn()
console.log(a.user) // fx      
function fnn () {
    this.user = 'fx'
    return undefined
}
var a = new fnn()
console.log(a.user) // fx      
function fn () {
    this.user = 'fx'
    return null
}
var a = new fn
console.log(a.user) // fx      

如果傳回值是一個對象,那麼 ​

​this​

​​ 指向的就是那個傳回的對象,如果傳回值不是一個對象那麼 ​

​this​

​​ 還是指向函數的執行個體。

還有一點就是雖然 ​​

​null​

​​ 也是對象,但是在這裡 ​

​this​

​ 還是指向那個函數的執行個體。

優先級測試

顯示綁定 與 隐式綁定

如下代碼,可以看出 顯示綁定 優先級大于 隐式綁定
function fnn () {
  console.log(this.a)
}

var obj1 = {
  a: 2,
  fnn: fnn
}
var obj2 = {
  a: 3,
  fnn: fnn
}
obj1.fnn() // 2 
obj2.fnn() // 3 
obj1.fnn.call(obj2) // 3 
obj2.fnn.call(obj1) // 2      

​new​

​ 綁定 與 隐式綁定

如下代碼,可以看出 ​

​new​

​ 綁定 優先級大于 隐式綁定
function fnn (num) {
  this.a = num
}

var obj1 = {
  a: 2,
  fnn: fnn
}
var bar = new obj1.fnn(4);
console.log(obj1.a); // 2
console.log(bar.a); // 4      

​new​

​ 綁定 與 顯示綁定

如下代碼,可以看出 ​

​new​

​ 綁定 優先級大于 顯示綁定
function fnn (num) {
  this.a = num
}

var obj1 = {
  a: 2,
}
var bar = fnn.bind(obj1);
bar(888)
console.log(obj1.a); // 888

var baz = new bar(666); 
console.log(obj1.a); // 888
console.log(baz.a); // 666      

this 丢失的情況

如果你把 ​

​null​

​​ 或者 ​

​undefined​

​​ 作為 ​

​this​

​​ 的綁定對象傳入 ​

​call​

​​、​

​apply​

​​ 或者 ​

​bind​

​,這些值在調用時會被忽略,實際應用的是預設綁定規則,如下代碼

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

var a = 2; 
fnn.call(null); // 2      

間接引用的問題

如下代碼,指派表達式 ​

​p.fnn = o.fnn​

​​ 的傳回值是目标函數的引用,是以調用位置是 ​

​fnn()​

​​ 而不是 ​

​p.fnn()​

​​ 或者 ​

​o.fnn()​

​,這裡會應用預設綁定

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

var a = 2
var o = {
  a: 3,
  fnn: fnn
}
var p = {
  a: 4
}
o.fnn(); // 3 
(p.fnn = o.fnn)() // 2      

箭頭函數的 this

如下代碼所示,​

​fnn()​

​​ 内部建立的箭頭函數會捕獲調用時 ​

​fnn()​

​​ 的 ​

​this​

​​。由于 ​

​fnn()​

​​ 的 ​

​this​

​​ 綁定到 ​

​obj1​

​​,

​​

​bar​

​​(引用箭頭函數)的 ​

​this​

​​ 也會綁定到 ​

​obj1​

​​,箭頭函數的綁定無法被修改。(​

​new​

​ 也不行!)

function fnn () {
  // 傳回一個箭頭函數
  return () => {
    // this 繼承自 fnn()
    console.log(this.a)
  }
}

var obj1 = {
  a: 2
}
var obj2 = {
  a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 2      

非箭頭函數,輸出 ​

​3​

function fnn () {
  // 傳回一個箭頭函數
  return function () {
    // this 繼承自 fnn()
    console.log(this.a)
  }
}

var obj1 = {
  a: 2
}
var obj2 = {
  a: 3
}
var bar = fnn.call(obj1)
bar.call(obj2) // 3      

測試題

示例一

var o = {
    a:10,
    b:{
        a:12,
        fn:function(){
            console.log(this.a); // 12
        }
    }
}
o.b.fn(); // 12
 
var o = {
    a:10,
    b:{
        // a:12,
        fn:function(){
            console.log(this.a); //undefined
        }
    }
}
o.b.fn(); // undefined      

示例二

var o = {
    a: 10,
    b: {
        a: 12,
        fn: function () {
            console.log(this.a) //undefined
            console.log(this) //window
        }
    }
}
var j = o.b.fn
j()      

示例三

var x = 10
var obj = {
    x: 20,
    f: function () {
        console.log(this.x) // 20
        function fnn () {
            console.log(this.x)
        }
        fnn() // 10 預設綁定,這裡 this 綁定的是 window
    }
}
obj.f()      
  • ​fnn(1)​

    ​​ 使用預設綁定,​

    ​this.a = arg​

    ​​ 相當于​

    ​window.a = arg​

    ​​,​

    ​return this​

    ​​ 相當于​

    ​return window​

  • ​var a = fnn(1)​

    ​​ 相當于​

    ​window.a = window​

    ​​,是以​

    ​a.a​

    ​​ 等于​

    ​window​

    ​​,依次類推​

    ​a.a.a.a​

    ​ 仍舊是 window
  • ​fnn(10)​

    ​​ 使用預設綁定,​

    ​this.a = 10​

    ​​ 相當于​

    ​window.a = 10​

    ​​,​

    ​return this​

    ​​ 相當于​

    ​return window​

  • ​console.log(b.a)​

    ​​ 相當于​

    ​window.a​

    ​ 等于 10
function fnn (arg) {
  this.a = arg
  return this
}
var a = fnn(1)
console.log(a.a) // window 

var b = fnn(10)

console.log(b.a) // 10      
var x = 10
var obj = {
    x: 20,
    f: function () {
        console.log(this.x)
    }
}
var bar = obj.f
var obj2 = {
    x: 30,
    f: obj.f
}
obj.f() // 20
bar() // 10
obj2.f() // 30      

繼續閱讀