js - javaScript 中的 this (v2)

零、序言

  本篇是《你不知道的 javascript(上)》讀書筆記。

  v1 版本寫得比較亂,傳送門

  注意:如無特殊標註,本篇中的 this 指的是 es5 & 非嚴格模式下的 this。

 

一、總集

  在 js 中, this 的值需要到函數的調用時才能明確,因此完全取決於函數的調用位置(執行 fn(...) 的時候)。

 

二、綁定規則

1. 默認綁定

這種情況最簡單最常用,函數作爲獨立函數被調用,函數體中的 this 指向 window。同時這條規則也是其他規則不適合時的默認規則。

demo:

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

var a = 'global';
fn();  //  global

 

注意,如果在 fn 函數體中使用了嚴格模式,那麼 fn 中的 this 會被綁定成 undefinded。

function fn() {
    'use strict';
    console.log(this); // undefined
}

 

2. 隱式綁定

  “另一條需要考慮的規則是調用位置是否有上下文對象,或者說是否被某個對象擁有或者包含,不過這種說法可能會造成一些誤導。”

  這條規則說穿了也很簡單,就是當函數作爲對象的一個屬性,並被這個對象調用時,那麼函數體中的 this 會指向這個對象。

先看個正常的 demo:

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

obj.foo(); // 2

 

接着看一下會出問題的 demo:

function foo() {
    console.log( this.a );
}
var obj = {
    a: 2,
    foo: foo 
};
var bar = obj.foo; // 函數別名!
var a = "oops, global"; // a 是全局對象的屬性
bar(); // "oops, global"

 

  var bar = obj.foo這句,本質上是把函數 foo 的內存地址傳遞給變量 bar,並不是真正地執行了函數 foo,真正的函數執行時 bar() 這一句,因此,在正函數執行的時候,是以獨立函數的規則運行的,所以這裏實際上是走的默認綁定的規則。

 

  因此,在所有的類似這種僅僅傳遞函數的內存地址而不是執行函數的場景,都要注意存在的隱式丟失的問題。同時列舉常見的需要注意的場景:

  • setTimeout( obj.foo, 100 );
  • function func( fn ) { fn() }; func( obj.foo );


3. 顯式綁定

  相對於隱式綁定,顯式綁定時我們可以自主操控的,這就給了大牛們提供了黑操作的控件。也叫硬綁定。

  js 中原生綁定的形式只有三個, call/apply/bind,其中 bind 是在 es5 中新加入的函數。對於這三個函數的用法和內部僞代碼,請移步 call,apply 和 bind 方法

  除了我們自己使用上面的函數進行顯式綁定,js 中的某些內置 api 也會實現顯式綁定,例如 forEach:

function fn(el) {
    console.log(el, this.id);
}

var obj = {
    id: 'awesome',
}

// 調用
[1, 2, 3].forEach(foo, obj);
// 1 awesome 2 awesome 3 awesome

 

4. new 綁定

好消息是,這是最後一條綁定規則。

js 中 new 操作符的操作列在其下:

  • 創建(或者說構造)一個全新的對象;
  • 這個新對象會被執行 [[ 原型 ]] 鏈;
  • 這個新對象會綁定到函數調用的 this;
  • 如果函數沒有返回其他對象,那麼 new 表達式中的函數調用會自動返回這個新對象;

 

ps: new 操作符的僞代碼實現請移步 js new 與 return。

當然,真實的實現會更復雜,有興趣的可自信查找資料。

 

三、注意事項

  1. 顯式綁定中, call/apply 綁定後會執行下函數, bind 綁定後會返回一個新的函數, 該函數內部的 this 會指向被綁定的對象。

  2. 一般而言,顯式綁定(call/apply/bind)之後的 this 不能夠再次被修改或者綁定。(特殊情況見第 4 節)。

  3. new 和 apply/call 不可同時使用。

  4. new 和 bind 可以同時使用,bind 的源碼在實現的時候會在內部判斷有沒有與 new 一起使用,如果是的話就會使用新創建的 this 替換硬綁定的 this。

 

四、例外

規則總有例外。

 

1. 被忽略的 this

  null(undefinded) 作爲一個特殊的 object,也是被允許使用在顯式綁定中的。顯式綁定在遇到這兩個特殊的對象的時候,這些值的調用會被忽略,實際應用的是默認的綁定規則。

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

foo.apply( null, [2, 3] ); // 2 3 window

var bar = foo.bind(null, 2);
bar(3);  // 2 3 window

   當然,使用 null 來忽略 this 的綁定也具有一定的副作用。如果某個函數確實使用了 this (比如第三方庫中的一個函數), 那默認綁定規則會把 this 綁定到全局對象(window 等,當然,嚴格模式下會指向 undefined), 這見導致不可預見的後果(比如修改全局對象)。

  因此我們可以使用如下的代碼來防止這個副作用。

function foo(a,b) {
    console.log(a, b, this);
}
// 我們自定義的空對象
var ø = Object.create( null );

foo.apply( ø, [2, 3] ); // 2, 3, {}

var bar = foo.bind( ø, 2 )
bar(3); // 2, 3, {}

 

2.間接引用

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

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

放上這段代碼的原因在於最後一句,一開始沒懂,後來看了下執行過程,最後一句代碼前半段是個賦值語句,整體等價於:

p.foo = o.foo;
p.foo();

 

3. es 6 箭頭函數

  箭頭函數不會創建自己的 this,箭頭函數不使用 this 的四種標準規則,而是根據外層(函數或全局)作用域來決定 this。

  另外, 箭頭函數不能作爲構造器,與 new 一起使用會拋出錯誤。

 

五、其他資料和鏈接

 1.對阮一峯《ES6 入門》中箭頭函數 this 描述的探究;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章