在第二章運行上下文(Execution Context)中咱們提升運行上下文的結構:java
function ExecutionContext() {
this.LexicalEnvironment = undefined;
this.VariableEnvironment = undefined;
this.ThisBinding = undefined;
}
複製代碼
這篇就來聊聊這個ThisBinding。當前運行上下文的ThisBinding其實就是在當前運行上下文上執行代碼裏this的值。因此你在代碼執行的時候遇到"this",就會來找當前運行上下文的這個ThisBinding做爲this的值。那麼,ThisBinding的值是多少呢?瀏覽器
要知道ThisBinding的值是多少,ExecutionContext何時被建立,建立時ThisBinding被設置爲何。bash
而咱們又知道JS代碼在三種狀況下會建立ExecutionContext:app
可運行代碼(Executable Code)
ECMAScript 5 規範,定義了三類可運行代碼(Executable Code) ,運行這些代碼時候會建立運行上下文(Execution Contexts):函數
- global code:就是js整個「程序」,就是源代碼文件中全部不在function體中的代碼。
- function code:就是函數體中的代碼,除了內嵌函數體中的代碼之外
- eval code : 就是傳給內置eval函數的代碼字符串
只要咱們瞭解運行這三種代碼時候,建立了ExecutionContext的ThisBinding被設置爲多少。就知道運行在該運行上下文的this的值了。ui
這個咱們提過:this
當JS引擎開始要進行global code代碼運行以前,會先建立一個全局運行上下文(global execution context),並放入運行棧中:spa
//建立一個空的運行上下文 var globalExecutionContext = new ExecutionContext(); //建立全局詞法環境 GlobalEnvironment = creatGlobalEnvironment(globalobject)//能夠看做是瀏覽器環境下的window //設置運行上下文 globalExecutionContext.LexicalEnvironment = GlobalEnvironment; globalExecutionContext.VariableEnvironment = GlobalEnvironment; globalExecutionContext.ThisBinding = globalobject; Runtime.push(globalExecutionContext); //這時的Runtime是這樣的: Runtime = { executionContextStack: [globalExecutionContext]; }; 複製代碼
在進入程序代碼以前,建立了全局運行上下文,其中的ThisBinding被設置爲了全局對象:globalExecutionContext.ThisBinding = globalobject;prototype
因此在全局代碼中的this的值爲爲全局對象(瀏覽器下爲window)。code
在function code裏this的值就比較多變了,咱們在函數調用一篇中提到,用不一樣的調用方式調用函數後,進入function code以前會設置不一樣thisArg,並傳遞到function code代碼中 。進入function code後,會建立function的運行上下文,且設置其ThisBinding爲thisArg。所以函數裏的this的值,就和調用關係又很大的關係。
這裏強調一下,函數中的this和函數的調用方式相關而與在哪被建立無關。與函數的詞法環境的"靜態"相比它是動態的。它之和當前運行上下文的ThisBings相關。
咱們在函數運行講過:
那這五種調用方式,在進入函數代碼運行以前,攜帶進去的,要做爲this的"東西"都是啥呢?
- 帶undefined 進去的:函數調用functionName();和 當即調用函數表達式(function(){})(),(function functionName(){})();
- 帶對象進去的:
- 方法調用:如someObj.method() : 帶someObj進去
- new functionName() 方式的調用:建立一個新對象 newObject,帶進去
- functionName.call和functionName.apply:把call和apply指定thisArg帶進去
判斷攜帶進來的thisArg的值:
- 若是是strict,使barExecutionContext.ThisBinding = thisArg;
- 不是strict
- 若是thisArg是undefined,使barExecutionContext.ThisBinding = globalobject;
- 若是thisArg不是undefined,使barExecutionContext.ThisBinding = toObject(thisArg);
所以函數中this的值就有幾種狀況:
由於普通函數調用,包括調用functionName();和 當即調用函數表達式(function(){})(),(function functionName(){})();等,傳到函數裏的thisAarg是undefined。
所以,若是是非strict,則ThisBinding = globalobject;也就是:
針對如someObj.method() : someObj做爲 thisArg傳進函數代碼裏,在建立函數運行上下文的時候,ThisBinding = someObj。
所以在方法調用模式someObj.method()中的this,爲someObj。
var a = 2;
var someObj = {
a:1
print:function(){
console.log(this.a)
}
}
var outPrint = someObj.print;
someObj.print(); //1
outPrint();//2
複製代碼
outPrint()調用時,傳進函數的thisArg是undefined,所在outPrint()的運行上下文中,ThisBinding被設置爲全局對象,因此這時,this.a就是全局對象上的var a = 2;
而someObj.print()調用時,傳進傳進函數的thisArg是someObj,因此在someObj.print()運行上下文中,ThisBinding被設置someObj,因此這時this.a就是someObj上a:1。
注意,函數(箭頭函數除外)裏的this只和調用方式相關和在哪調用,函數在哪建立無關。
- new functionName() 方式的調用:建立一個新對象 newObject,帶進去
在new方式調用的函數運行上下文中,ThisBinding = newObject,所以在new方式調用的函數中,this值就是新建立的對象newObject。
function Point(x, y) {
this.x = x;
this.y = y;
}
var p = Point(7, 5); // 沒有new,普通調用,this爲全局對象,在全局對象上建立x=7 y=5
var point = new Point(7, 5); //使用new調用,this爲新建立的對象,在新對象上建立x=7,y=5
console.log(x); // 7
console.log(y); // 5
複製代碼
call和apply調用,可以顯示的傳遞給函數thisArg。其實不僅是Function.prototype.call和Function.prototype.apply這個兩個函數能夠給調用的函數顯示傳遞thisArg參數,下列的方法都給函數調用顯示傳遞提供thisArg:
須要注意的是若是傳遞給thisArg的null或者undefined,在非嚴格模式ixia函數中this仍是全局對象,記得咱們提到過,進入到函數代碼,建立運行上下文以前的判斷嗎:
判斷攜帶進來的thisArg的值:
- 若是是strict,使barExecutionContext.ThisBinding = thisArg;
- 不是strict
- 若是thisArg是undefined,使barExecutionContext.ThisBinding = globalobject;
- 若是thisArg不是undefined,使barExecutionContext.ThisBinding = toObject(thisArg);
var obj = {
a:1
};
function print() {
console.log(this);
}
print.call(null);//window
print.call(undefined);//window
print.call(obj);//obj
複製代碼
Function.prototype.bind,的只要功能是建立一個和原函數同樣body和[[scope]]的函數,可是它的this則綁定爲bind函數的第一個參數。在新函數中,不管函數如何被使用,this都會永久綁定到bind的第一個參數。
function f() {
return this.a;
}
var g = f.bind({a: 'azerty'});
console.log(g()); // azerty
複製代碼
箭頭函數是ES6的新內容,這裏講到this,順便提一下。 箭頭函數的this保存爲它被建立時運行上下文的this的值。在global下建立箭頭函數,爲箭頭函數this爲global,保持爲它被建立時的this值。在function裏建立也同樣,它的this保持爲在箭頭函數被建立時function運行上下文的this的值。
這個和咱們前面提到的函數都不同,前面提到的函數都是和調用時的運行上下文相關,箭頭函數的this卻和建立時的運行上下文相關。
還有一點就是,對箭頭函數調用call apply 和 bind 來綁定this是無效,沒有效果。
var globalObject = this;
var foo = (() => this);
console.log(foo() === globalObject); // true
var obj = {func: foo};
console.log(obj.func() === globalObject); // true
console.log(foo.call(obj) === globalObject); // true
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
複製代碼
分兩種狀況:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
function bluify(e) {
console.log(this);
}
var elements = document.getElementsByTagName('button');
elements[0].addEventListener('click', bluify, false);
複製代碼
JS三種可運行代碼,咱們已經提到過global、function,只上下eval還沒提到。由於eval比較少用,咱們也不細究。這裏大概講下,進入eval代碼之後,運行上下文的建立過程。
分兩種狀況:
eval("var definedInEval = 2;console.log(this);");//window
console.log(definedInEval); //2
var obj = {
method: function () {
eval('console.log(this)'); // obj
}
}
obj.method();
複製代碼
var evalcopy = eval;
evalcopy("console.log(this);");
var obj = {
method: function () {
evalcopy('console.log(this)'); //window
}
}
obj.method();
複製代碼