this 是js 中很重要的一個指針。可是每每也是最容易產生bug 的地方。一塊兒學習如下《你不知道的JavaScript》中關於this的介紹;app
每一個函數的this是在調用時被綁定的,徹底取決於函數的調用位置(也就是函數的調用方法)。
在理解this的綁定過程以前,首先要理解調用位置和調用棧;由於 調用位置 就在 當前正在執行的函數 的 前一個調用 中。函數
什麼是調用棧和調用位置
調用位置:調用位置就是函數在代碼中被調用的位置(而不是聲明的位置)。
調用棧:就是爲了到達當前執行位置所調用的全部函數。oop
function baz() { // 當前調用棧是:baz // 所以, 當前調用位置是全局做用域 console.log( "baz" ); bar(); // <-- bar的調用位置 } function bar() { // 當前調用棧是baz -> bar // 所以, 當前調用位置在baz中 console.log( "bar" ); foo(); // <-- foo的調用位置 } function foo() { // 當前調用棧是baz -> bar -> foo // 所以, 當前調用位置在bar中 console.log( "foo" ); } baz(); // <-- baz的調用位置
分析代碼:學習
function foo() { console.log( this.a ); //this經過默認綁定指向全局對象 } var a = 2; foo(); // 2
和this
function foo() { "use strict"; console.log( this.a ); //嚴格模式(strict mode)全局對象將沒法使用默認綁定 } var a = 2; foo(); // TypeError: this is undefined
解釋:編碼
分析代碼:指針
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; obj.foo(); // 2
解析:code
隱式丟失對象
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函數別名! var a = "oops, global"; // a是全局對象的屬性 if(bar === obj.foo){ console.log('true'); //true console.log(typeof bar) //function console.log(typeof obj.foo) //function }else { console.log('false') } bar(); // "oops, global" obj.foo();// 2 //等價的兩個函數,輸出不同的結果。爲何? //其實var bar === window.bar; 它隱式綁定了window 對象,因此它訪問到的天然是 window.a
解析:雖然bar是obj.foo的一個引用,可是實際上,它引用的是foo函數自己,所以此時的bar()實際上是一
個不帶任何修飾的函數調用,所以應用了默認綁定。這種狀況是不能夠預料的。ip
還有一種 this 丟失=》 回調函數
回調函數丟失this綁定是很是常見的
function foo() { console.log( this.a ); } function doFoo(fn) { // fn其實引用的是foo fn(); // <-- 調用位置! } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a是全局對象的屬性 doFoo( obj.foo ); // "oops, global"
分析代碼:
function foo() { console.log( this.a ); } var obj = { a:2 }; foo.call( obj ); // 2
經過foo.call(..),咱們能夠在調用foo時強制把它的this綁定到obj上
硬綁定
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = foo.bind( obj ); var b = bar( 3 ); // 2 3 console.log( b ); // 5
bind(..)會返回一個硬編碼的新函數,它會把參數設置爲this的上下文並調用原始函數。
JavaScript,構造函數只是一些使用new操做符時被調用的函數。它們並不會屬於某個類,也不會實例化一個類。實際上,它們甚至都不能說是一種特殊的函數類型,它們只是被new操做符調用的普通函數而已。
使用new來調用函數時的行爲
function foo(a) { this.a = a; } //new對普通函數的構造調用,默認行爲:建立一個新對象,得到對this的全部引用,返回新對象。 var bar = new foo(2); console.log( bar.a ); // 2 //以上函數 至關於下面的函數 function foo(a){ var obj = {}; obj.a = a; reture obj } var bar = foo(2); console.log( bar.a ); // 2
解析:使用new來調用foo(..)時,咱們會構造一個新對象並把它綁定到foo(..)調用中的this上。
顯式綁定 > 隱式綁定
bind硬綁定 > new綁定 > 隱式綁定
函數是否在new中調用(new綁定)?若是是的話this綁定的是新建立的對象。
var bar = new foo()
函數是否經過call、apply(顯式綁定)或者硬綁定調用?若是是的話,this綁定的是指定的對象。
function foo(a) { this.a = a; } var obj = { a:2 } foo.call(obj,3); //由於強制改變了foo 的上下文,this 顯示綁定 爲 obj;因此改變的是obj.a 的值 console.log(obj.a); //3
函數是否在某個上下文對象中調用(隱式綁定)?若是是的話,this綁定的是那個上下文對象。
var bar = obj1.foo()
若是都不是的話,使用默認綁定。若是在嚴格模式下,就綁定到undefined,不然綁定到全局對象。
var bar = foo()
在默認綁定中容易被混淆的是 IIFE 自執行函數的使用
var a = 3; var obj = { a:3, b:(function(){ console.log(this.a) })(), c:function(){ (function(){ console.log(this.a) })() } } obj.c(); //3 //3 this 都指向了window