JavaScript中的this
其實是在函數被調用時發生的綁定,它指向什麼徹底取決於函數在哪裏被調用。javascript
先來列舉一下都有哪些函數調用方式:java
通俗來講,「函數被誰調用,this
就指向誰」。git
函數在全局做用域下運行;在非嚴格模式下,this
指向全局對象window
,在嚴格模式下,this
會變成undefined
。es6
function foo1(){ return this; } foo1() === window; // true function foo2(){ 'use strict'; return this; } foo2() === undefined; // true
當以對象裏的方法的方式調用函數時,它們的this
是調用函數的對象。github
function foo() { console.log(this); // {x: 1, foo: ƒ} console.log(this.x); // 1 } var obj = { x: 1, foo: foo }; obj.foo();
上面代碼中,調用foo
的是對象obj
,因此這裏的this
指向對象obj
。數組
若是將對象裏的foo
方法賦給一個變量:app
function foo() { console.log(this); // window console.log(this.x); // undefined } var obj = { x: 1, foo: foo }; var fn = obj.foo; fn();
將obj.foo
賦給了fn
變量,而fn
是在全局做用域中運行,所以this
指向window
,因爲window
下沒有x
這個變量,因此this.x
爲undefined
。函數
若是把obj.foo
傳遞給一個本身聲明的函數。this
function foo() { console.log(this); // window console.log(this.x); // undefined } var obj = { x: 1, foo: foo }; // 自定義函數 function todoFn(cb) { cb(); }; todoFn(obj.foo);
將obj.foo
傳遞給自定義函數做爲回調函數執行。結果同樣。由於它仍然是在全局做用域中運行。prototype
最後來看看在setTimeout()
中又會輸出怎樣的結果。
function foo() { console.log(this); // {x: 1, foo: ƒ} setTimeout(function(){ console.log(this); // window }, 0); } var obj = { x: 1, foo: foo } obj.foo();
第一個this
仍是對象方法調用方式;但在setTimeout()
裏的this
指向的是全局做用域window
。爲何會這樣?
緣由是,由setTimeout()
調用的代碼運行在與所在函數徹底分離的執行環境上,該函數是在全局做用域中執行的,因此this
就指向 window
。
上面代碼的後三段代碼中,都出現了一樣的問題,就是它們的this
都丟失了。怎麼作才能讓它們的this
仍然指向原來的對象?
後面即將提到的call()
、apply()
及bind()
方法都能解決這一問題。
使用call()
或者apply()
能夠改變上下文的this
值。
// 定義一個對象 var obj = { x: 'local' }; // 定義一個全局變量 var x = 'global'; // 定義函數,返回值取決於調用方式 function speak(){ return this.x; } speak(); // "global" speak.call(obj); // "local" speak.apply(obj); // "local"
直接調用sayName()
時,至關於普通函數調用,this
指向window
,值爲global
;使用call()
或apply()
做用一致,都讓this
指向了對象obj
。只不過在給call()
和apply()
傳遞參數時有區別,如:
function add(c, d) { return this.a + this.b + c + d; } var obj = { a: 1, b: 2 }; // 第一個參數做爲 this 使用的對象,然後續參數做爲參數傳遞給函數 add.call(obj, 3, 4); // 10 // 第一個參數一樣是做爲 this 使用的對象 // 第二個參數是傳遞一個數組 add.apply(obj, [3, 4]); // 10
Function.prototype.bind()
會建立一個新的包裝函數,這個函數會忽略它當前的this
綁定(不管綁定的對象是什麼), 並把咱們提供的對象綁定到this
上。
var x = 9; var obj = { x: 81, getX: function() { return this.x; } }; // 至關於對象方法調用,this指向對象 o obj.getX(); // 81 // 將對象 o 的方法getX賦值給 f,f 執行時是在全局環境中,this指向全局做用域 var f = obj.getX; f(); // 9 // 建立一個新函數,將this綁定到對象 o 中 var f2 = obj.getX.bind(o); f2(); // 81
接着上面【對象方法調用】裏的幾個丟失this
的例子。如何用call()
、apply()
或bind()
來綁定this
到指定的對象。
先看看這個例子:
function foo() { console.log(this); // {x: 1, foo: ƒ} console.log(this.x); // 1 } var obj = { x: 1, foo: foo }; // 使用call()或apply() var fn1 = obj.foo; fn1.call(obj); //fn.apply(obj); // 使用bind() var fn2 = obj.foo.bind(obj); fn2();
再看這個回調函數的例子:
function foo() { console.log(this); // {x: 1, foo: ƒ} console.log(this.x); // 1 } var obj = { x: 1, foo: foo }; // 自定義函數 function todoFn(cb) { cb.call(obj); // cb.apply(obj); }; todoFn(obj.foo);
只須要在執行回調函數的時候使用call()
或apply()
將this
綁定到obj
對象便可。
最後看定時器的例子:
function foo() { console.log(this); // {x: 1, foo: ƒ} setTimeout(function(){ console.log(this); // {x: 1, foo: ƒ} }.bind(this), 0); } var obj = { x: 1, foo: foo } obj.foo();
這裏是使用了bind()
方法,將上下文的this
綁定到定時器裏的匿名函數,它最終仍是指向對象obj
。
若是函數或者方法調用以前帶有關鍵字new
,它就構成構造函數調用。使用new
來調用函數, 或者說發生構造函數調用時,會自動執行下面的操做。
this
。new
表達式中的函數調用會自動返回這個新對象。來看一個例子:
function foo(x){ this.x = x; console.log(this); // foo {x: 2} } var f = new foo(2); console.log(f.x); // 2
使用new
來調用foo()
時, 會構造一個新對象並把它綁定到foo()
調用中的this
上。即構造函數的this
指向它實例化出來的對象。
箭頭函數自己沒有this
,是根據外層(函數或者全局)做用域來決定this
。
先來看一個例子:
var x = 1; function testFn() { var fn1 = function() { console.log(this); // window console.log(this.x); // 1 } var fn2 = () => { console.log(this); // window console.log(this.x); // 1 } fn1(); fn2(); } testFn();
兩段代碼輸出的結果一致。但運行的機制不同。
fn1
是普通函數調用,前面說過普通函數調用this
指向window
;fn2
是箭頭函數,它會繼承外層函數testFn
的this
,而testFn
是在全局做用域中運行的,因此它的this
是window
,所以fn2
箭頭函數的this
也是window
。對上面對例子作一點改動:
var x = 1; function testFn() { var fn1 = function() { console.log(this); // window console.log(this.x); // 1 } var fn2 = () => { console.log(this); // {x: 2} console.log(this.x); // 2 } fn1(); fn2(); } testFn.call({ x: 2 });
改變了testFn
的調用方式。
fn1
依然是普通函數調用,this
指向window
;fn2
箭頭函數依然是繼承它外層函數testFn
的this
,但這裏testFn
經過call
使this
指向了對象{id: 2}
,因此fn2
的this
也指向該對象。接着上面【對象方法調用】裏的setTimeout
例子。若是使用箭頭函數會怎樣?
function foo() { console.log(this); // {x: 1, foo: ƒ} console.log(this.x); // 1 setTimeout(() => { console.log(this); // {x: 1, foo: ƒ} console.log(this.x); // 1 }, 0); } var obj = { x: 1, foo: foo } obj.foo();
這裏定時器回調的this
由它外層函數foo
的做用域決定,而foo
是由obj
調用,因此它的this
指向對象obj
,因此定時器的this
也指向對象obj
。
另外,因爲箭頭函數沒有本身的this
,因此固然也就不能用call()
、apply()
、bind()
這些方法去改變this
的指向。
JavaScript的this
取決於被調用的方式。
this
指向window
,嚴格模式下,this
是undefined
;this
指向該對象;call()
、apply()
或bind()
方式調用,this
指向被綁定的對象;this
指向實例化出來的新對象;this
, 具體來講,箭頭函數會繼承外層函數調用的this
綁定。原文地址:https://github.com/daijingfeng/blog/issues/2
參考資料:
一、https://developer.mozilla.org/zh-CN/docs/Web/API/Window/setTimeout
二、https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Arrow_functions
三、http://es6.ruanyifeng.com/#docs/function#箭頭函數
四、《JavaScript權威指南(第6版)(中文版)》
五、《你不知道的JavaScript(上卷)》