5.完全搞懂javascript-this

在第二章運行上下文(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

global code中的this

這個咱們提過: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

函數中this

在function code裏this的值就比較多變了,咱們在函數調用一篇中提到,用不一樣的調用方式調用函數後,進入function code以前會設置不一樣thisArg,並傳遞到function code代碼中 。進入function code後,會建立function的運行上下文,且設置其ThisBinding爲thisArg。所以函數裏的this的值,就和調用關係又很大的關係。

這裏強調一下,函數中的this和函數的調用方式相關而與在哪被建立無關。與函數的詞法環境的"靜態"相比它是動態的。它之和當前運行上下文的ThisBings相關。

咱們在函數運行講過:

那這五種調用方式,在進入函數代碼運行以前,攜帶進去的,要做爲this的"東西"都是啥呢?

  1. 帶undefined 進去的:函數調用functionName();和 當即調用函數表達式(function(){})(),(function functionName(){})();
  2. 帶對象進去的:
    • 方法調用:如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;也就是:

  • 在非strict mode下,普通函數調用中this爲全局對象
  • 在strict mode下,普通函數調用中this爲undefined

方法調用

針對如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 調用

  • 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調用

call和apply調用,可以顯示的傳遞給函數thisArg。其實不僅是Function.prototype.call和Function.prototype.apply這個兩個函數能夠給調用的函數顯示傳遞thisArg參數,下列的方法都給函數調用顯示傳遞提供thisArg:

  • Function.prototype.apply( thisArg, argArray )
  • Function.prototype.call( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Function.prototype.bind( thisArg [ , arg1 [ , arg2, ... ] ] )
  • Array.prototype.every( callbackfn [ , thisArg ] )
  • Array.prototype.some( callbackfn [ , thisArg ] )
  • Array.prototype.forEach( callbackfn [ , thisArg ] )
  • Array.prototype.map( callbackfn [ , thisArg ] )
  • Array.prototype.filter( callbackfn [ , 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
複製代碼

bind

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
複製代碼

事件綁定函數中this

分兩種狀況:

  • 做爲event handler,addEventListener添加爲元素的事件回調函數,其中this爲觸發該事件的元素對象(若是不是addEventListener添加,有些瀏覽器不遵循此規則)
  • inline event handler: this爲監聽函數所在的元素,只有最外層的this是這樣,若是是是內層function裏的this則爲全局或者undefined
<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);
複製代碼

Eval Code 中的this

JS三種可運行代碼,咱們已經提到過global、function,只上下eval還沒提到。由於eval比較少用,咱們也不細究。這裏大概講下,進入eval代碼之後,運行上下文的建立過程。

分兩種狀況:

  1. 直接調用,則運行上下文的建立過程至關於複製一份調用eval的那個運行上下文,在全局上直接調用eval,則複製一份全局運行上下文,在函數中直接運行eval,則複製函數的運行上下文,因此這個種狀況下下,eval種的this和調用它的那個運行上下文的this相同。
eval("var definedInEval = 2;console.log(this);");//window 

console.log(definedInEval); //2 

var obj = {
    method: function () {
        eval('console.log(this)'); // obj
    }
}
obj.method(); 

複製代碼
  1. 非直接調用,無論在哪調用,則其運行上下文都是複製全局運行上下文,因此this都是全局對象
var evalcopy = eval;

evalcopy("console.log(this);");

var obj = {
    method: function () {
        evalcopy('console.log(this)'); //window
    }
}
obj.method(); 
複製代碼
相關文章
相關標籤/搜索