this作用域詳解


  大家在使用Javascript的時候經常被this這個家伙搞得暈頭轉向的。在Javascript中它卻顯得古靈精怪的,因為它不是固定不變的,而是隨着它的執行環境的改變而改變。在Javascript中this總是指向調用它所在方法的對象。接下來我們一個一個方面,舉例說明

一、全局的this(瀏覽器)

console.log(this.document === document) //true
console.log(this === window) //true
this.a = 37;
console.log(window.a); //37

 

二、一般函數的this(瀏覽器)

function f1(){
    return this;
}
f1()===window; //true,global object

 

 

三、作為對象方法的函數的this

var o = {
    prop:37,
    f:function(){
        return this.prop;
    }
};
console.log(o.f()); //37
f作為一個對象的方法,那么作為對象的方法去調用的時候,比如o.f調用的時候,這種情況,這個this,一般會指向對象

var o = {
    prop:37
};
function indepedent(){
    return this.prop;
}
o.f = indepedent;
console.log(o.f()); //37
這里並不是去看函數是再怎么樣創建的,而是只要將這個函數作為對象的方法,這個o.f去調用的話,那么這個this就會指向這個o 

關於this,一般來說,誰調用了方法,該方法的this就指向誰,如:
function foo(){
  console.log(this.a)
}
var a = 3;
var obj = {
    a: 2,
    foo: foo
};
obj.foo(); // 輸出2,因為是obj調用的foo,所以foo的this指向了obj,而obj.a = 2
 
如果存在多次調用,對象屬性引用鏈只有上一層或者說最后一層在調用位置中起作用,如:
function foo() {
  console.log( this.a )
}
var obj2 = {
  a: 42,
  foo: foo
}
var obj1 = {
  a: 2,
  obj2: obj2
}
obj1.obj2.foo(); // 42

 

 

四、對象原型鏈上的this

var o = {
    f:function(){ return this.a + this.b; } } var p = Object.create(o); p.a = 2; p.b = 6; console.log(p.f()); //8 這里p的原型是o,那么p.f的時候調用的是對象o上面的函數屬性f,this也指向p 

 

五、get/set方法與this

function modules(){
  return Math.sqrt(this.re * this.re + this.im * this.im); } var o = {   re :1,   im:-1,   get phase(){     return this.im+this.re;   } }; Object.defineProperty(o,'modules',{   get:modules,enumerable:true,configurable:true }); console.log(o.phase,o.modules); // 0 根號2

 

六、構造器中的this

function MyClass(){
    this.a = 37; } var o = new MyClass(); console.log(o.a); //37
function C2(){ this.a = 37; return{ a:38 } } o=new C2(); console.log(o.a); //38 new一個MyClass空對象,如果沒有返回值,默認會返回this,有返回值,那么o就指向返回的對象了

 

在傳統面向類的語言中,使用new初始化類的時候會調用類中的構造函數,但是JS中new的機制實際上和面向類和語言完全不同。

使用new來調用函數,或者說發生構造函數調用時,會自動執行下面的操作:

  • 創建(或者說構造)一個全新的對象
  • 這個新對象會被執行[[Prototype]]連接
  • 這個新對象會綁定到函數調用的this
  • 如果函數沒有返回其他對象,那么new表達式中的函數會自動返回這個新對象 如:
function foo(a){
    this.a = a
}
 
var bar = new foo(2);
console.log(bar.a); // 2

使用new來調用foo(…)時,我們會構造一個新對象並把它綁定到foo(…)調用中的this上。new是最后一種可以影響函數調用時this綁定行為的方法,我們稱之為new綁定。

 

 

 

七、bind方法與this

function f(){
  return this.a; } var g = f.bind({ a:'test' }); console.log(g()); //test var o = { a:37, f:f, g:g }; console.log(o.f(),o.g()); //37,test 

 

八、call/apply方法與this

function add(c,d){
    return this.a + this.b+c+d; } var o = { a:1, b:3 }; add.call(o,5,7); //1+3+5+7 = 16 add(5,7) add.apply(o,[10,20]); //1+3+10+20 = 34 function bar(){ console.log(Object.prototype.toString.call(this)); } bar.call(7); //[Objedt Number]

 

function foo( something ) {
    console.log( this.a, something)
    return this.a + something
}

var obj = {
    a: 2
}

var bar = function() {
    return foo.apply( obj, arguments)
}

var b = bar(3); // 2 3
console.log(b); // 5

這里簡單做一下解釋: 在bar函數中,foo使用apply函數綁定了obj,也就是說foo中的this將指向obj,與此同時,使用arguments(不限制傳入參數的數量)作為參數傳入foo函數中;所以在運行bar(3)的時候,首先輸出obj.a也就是2和傳入的3,然后foo返回了兩者的相加值,所以b的值為5

同樣,本例也可以使用bind:

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

 

 

 

九、隱式丟失

一個最常見的this綁定問題就是被隱式綁定的函數會丟失綁定對象,也就是說他回應用默認綁定,從而把this綁定到全局對象或者undefined上,取決於是否是嚴格模式。

function foo() {
    console.log( this.a )
}
 
var obj1 = {
    a: 2,
    foo: foo
}
 
var bar = obj1.foo; // 函數別名!
 
var a = "oops, global"; // a是全局對象的屬性
 
bar(); // "oops, global"

雖然bar是obj.foo的一個引用,但是實際上,它引用的是foo函數本身,因此此時的bar()其實是一個不帶任何修飾的函數調用,因此應用了默認綁定

一個更微妙、更常見並且更出乎意料的情況發生在傳入回調函數時

function foo() {
    console.log( this.a )
}
 
function doFoo( fn ){
    // fn 其實引用的是 foo
    fn(); //

參數傳遞其實就是一種隱式賦值,因此我們傳入函數時也會被隱式賦值,所以結果和上一個例子一樣,如果把函數傳入語言內置的函數而不是傳入自己聲明的函數(如setTimeout等),結果也是一樣的

 

 

 

十、this的優先級

毫無疑問,默認綁定的優先級是四條規則中最低的,所以我們可以先不考慮它。

隱式綁定和顯式綁定哪個優先級更高?我們來測試一下:

十三、this在箭頭函數中的應用

箭頭函數不使用this的四種標准規則,而是根據外層(函數或者全局)作用域來決定this。

我們來看一下箭頭函數的詞法作用域:

function foo() {
    // 返回一個箭頭函數
    return (a) => {
        // this繼承自foo()
        console.log(this.a)
    };
}
 
var obj1 = {
    a: 2
};
 
var obj2 = {
    a: 3
};
 
var bar = foo.call(obj1);
bar.call(obj2); // 2, 不是3!

foo()內部創建的箭頭函數會捕獲調用時foo()的this。由於foo()的this綁定到obj1,bar(引用箭頭函數)的this也會綁定到obj1,箭頭函數的綁定無法被修改。(new也不行!)

 

總結:

如果要判斷一個運行中的函數的this綁定,就需要找到這個函數的直接調用位置。找到之后就可以順序應用下面這四條規則來判斷this的綁定對象。

  1. 由new調用?綁定到新創建的對象。
  2. 由call或者apply(或者bind)調用?綁定到指定的對象。
  3. 由上下文對象調用?綁定到那個上下文對象。
  4. 默認:在嚴格模式下綁定到undefined,否則綁定到全局對象。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM