作用域鏈this關鍵詞

Jsp發表於2018-05-14

this關鍵詞        

參考:阮一峰

this沒有作用域的限制,巢狀函式不會從呼叫它的函式中繼承this。如果巢狀函式作為方法呼叫this,this指向呼叫它的物件,如果巢狀函式作為函式呼叫,this值不是全域性物件就是undefined。

this總是返回

一個物件,this就是屬性或方法“當前”所在的物件。

一:由於物件的屬性可以賦給另一個物件,所以屬性所在的當前物件是可變的,即this的指向是可變的

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

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

B.describe = A.describe;
B.describe()
// "姓名:李四"
複製程式碼

二:只要函式被賦給另一個變數,this的指向就會變。A.describe被賦值給變數f,內部的this就會指向f執行時所在的物件(本例是頂層物件)。

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

var name = '李四';
var f = A.describe;
f() // "姓名:李四"
複製程式碼

JavaScript 語言之中,一切皆物件,執行環境也是物件,所以函式都是在某個物件之中執行,this就是函式執行時所在的物件(環境)。這本來並不會讓使用者糊塗,但是 JavaScript 支援執行環境動態切換,也就是說,this的指向是動態的,沒有辦法事先確定到底指向哪個物件。

使用場合:

一:全域性環境

this === window // true

function f() {
  console.log(this === window);
}
f() // true
複製程式碼

二:建構函式:指向例項物件

var Obj = function (p) {
  this.p = p;
};
var o = new Obj('Hello World!');
o.p // "Hello World!"
複製程式碼

三:物件的方法

如果物件的方法裡面包含thisthis的指向就是方法執行時所在的物件。該方法賦值給另一個物件,就會改變this的指向。

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

obj.foo() // obj
複製程式碼

但是,下面這幾種用法,都會改變this的指向。

// 情況一
(obj.foo = obj.foo)() // window
// 情況二
(false || obj.foo)() // window
// 情況三
(1, obj.foo)() // window
複製程式碼

上面程式碼中,obj.foo就是一個值。這個值真正呼叫的時候,執行環境已經不是obj了,而是全域性環境,所以this不再指向obj

可以這樣理解,JavaScript 引擎內部,objobj.foo儲存在兩個記憶體地址,稱為地址一和地址二。obj.foo()這樣呼叫時,是從地址一呼叫地址二,因此地址二的執行環境是地址一,this指向obj。但是,上面三種情況,都是直接取出地址二進行呼叫,這樣的話,執行環境就是全域性環境,因此this指向全域性環境。上面三種情況等同於下面的程式碼。

// 情況一
(obj.foo = function () {
  console.log(this);
})()
// 等同於
(function () {
  console.log(this);
})()

// 情況二
(false || function () {
  console.log(this);
})()

// 情況三
(1, function () {
  console.log(this);
})()
複製程式碼

如果this所在的方法不在物件的第一層,這時this只是指向當前一層的物件,而不會繼承更上面的層。

var a = {
  b: {
    m: function() {
      console.log(this.p);
    },
    p: 'Hello'
  }
};

var hello = a.b.m;
hello() // undefined
複製程式碼

上面程式碼中,m是多層物件內部的一個方法。為求簡便,將其賦值給hello變數,結果呼叫時,this指向了頂層物件。為了避免這個問題,可以只將m所在的物件賦值給hello,這樣呼叫時,this的指向就不會變。

var hello = a.b;
hello.m() // Hello
複製程式碼

使用注意點:

一:避免多層this

var o = {
  f1: function() {
    console.log(this);
    var that = this;
    var f2 = function() {
      console.log(that);
    }();
  }
}

o.f1()
// Object
// Object
複製程式碼

二:避免陣列處理方法中的this

陣列的mapforeach方法,允許提供一個函式作為引數。這個函式內部不應該使用this

var o = {
  v: 'hello',
  p: [ 'a1', 'a2' ],
  f: function f() {
    var that = this;
    this.p.forEach(function (item) {
      console.log(that.v+' '+item);
    });
  }
}

o.f()
// hello a1
// hello a2
複製程式碼

三:避免回掉函式中的this

var o = new Object();
o.f = function () {
函式例項的call方法,可以指定函式內部this的指向(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。  console.log(this === o);
}

// jQuery 的寫法
$('#button').on('click', o.f);
複製程式碼

控制檯會顯示false。原因是此時this不再指向o物件,而是指向按鈕的 DOM 物件,因為f方法是在按鈕物件的環境中被呼叫的。

繫結this 的方法

JavaScript 提供了callapplybind這三個方法,來切換/固定this的指向。

一:Function.prototype.call()

函式例項的call方法,可以指定函式內部this的指向(即函式執行時所在的作用域),然後在所指定的作用域中,呼叫該函式。

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

f() === window // true
f.call(obj) === obj // true
複製程式碼

call方法可以改變this的指向,指定this指向物件obj,然後在物件obj的作用域中執行函式fcall方法的引數,應該是一個物件。如果引數為空、nullundefined,則預設傳入全域性物件.

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

二:Function.prototype.apply()

apply方法的第一個引數也是this所要指向的那個物件,如果設為nullundefined,則等同於指定全域性物件。第二個引數則是一個陣列,該陣列的所有成員依次作為引數,傳入原函式。原函式的引數,在call方法中必須一個個新增,但是在apply方法中,必須以陣列形式新增。

三:Function.prototype.bind()

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

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

程式碼中,counter.inc方法被賦值給變數func。這時必須用bind方法將inc內部的this,繫結到counter.

結合call方法使用,
利用bind方法,可以改寫一些 JavaScript 原生方法的使用形式。


  • bind 是返回對應函式,便於稍後呼叫;apply 、call 則是立即呼叫 。




相關文章