1. 程式人生 > 實用技巧 >理解 this 作用域

理解 this 作用域


理解this作用域

轉載https://github.com/AwesomeDevin/blog/issues/10


《javascript高階程式設計》中有說到:

this物件是在執行時基於函式的執行環境繫結的:在全域性函式中,this等於window ,而當函式被作為某個物件呼叫時,this等於那個物件。不過,匿名函式具有全域性性,因此this物件同常指向window

不過,在全域性函式中,this等於window,匿名函式具有全域性性,因此this物件通常指向window,針對於匿名函式this具有全域性性的觀點仍是有爭議的,可參考 https://www.zhihu.com/question/21958425

this的指向取決於函式(不包含箭頭函式)執行時的環境

驗證過程如下:

關於閉包經常會看到這麼一道題:

var name = "The Window";
    var object = {
        name : "My Object",
        getNameFunc : function(){
            return function(){
                return this.name;
            };
        }
    };
console.log(object.getNameFunc()());//result:The Window


在這裡,getNameFunc return了1個匿名函式,可能你會認為這就是輸出值為The Window的原因

但是,我們再來嘗試寫1個匿名函式

var name = "The Window";
 var object = {
  name : "My Object",
  getNameFunc : function(){
   return this.funAA;
  },
  funAA:function(){
   return this.name
  }
 };
 console.log(object.getNameFunc()(),object.funAA())


可以發現,同樣是匿名函式,卻輸出了The Window, My Object

在作用域鏈中,執行函式時會建立一個稱為“執行期上下文(execution context)”的內部物件,執行期上下文定義了函式執行時的環境。

因為函式在全域性作用域中被object.getNameFunc()獨立呼叫,funAA的作用域鏈被初始化為undefined即window的[[Scope]]所包含的物件,導致輸出結果為window.name

對作用域鏈不是很瞭解的同學,可以檢視這邊文章【Javascript】深入理解javascript作用域與作用域鏈

實踐是檢驗真理的唯一標準,讓我們用程式碼測試一下

var name = "The Window";
 var object = {
  name : "My Object",
  getNameFunc : function(){
   return this.funAA();
  },
  funAA:function(){
   return this.name
  }
 };
console.log(object.getNameFunc(),object.funAA())


可以發現,輸出了 My Object, My Object
getNameFunc仍為匿名函式,但是return的是this.funAA(),此時,this.funAA變成了由object呼叫,驗證了我們之前的猜想:

函式執行環境影響了this作用域,對這個demo的程式碼不太理解的同學,可以看一下另一個比較簡單的案例

this.x = 9;   
var module = {
  x: 81,
  getX: function() { console.log(this.x) }
};

module.getX(); // 81

var retrieveX = module.getX;
function A(){
this.x = 22;
retrieveX() //22
}
A()

new運算子對this作用域的影響

還是實踐出真理,我們先來寫一段程式碼

var a = 2
function test(){
    this.a = 1
    console.log(window.a)
}
new test()
test()


可以看出輸出結果為2,1
new運算子改變了test函式內this的作用域,改變的原理是通過在函式內建立一個物件obj,並通過test.call(obj),執行obj.test(),call函式原理:

Function.prototype.call1 = function(obj,...args){
	obj.fn = this
	obj.fn(...args)
	delete obj.fn
}

這樣test函式被物件obj呼叫,test複製的是obj的作用域鏈,而不是window

function subNew(){
    var obj = {}
    var res = test.call(obj,...arguments)
}
subNew()   // 作用等於new test()

let/var/const對this作用域的影響

繼續寫程式碼通過事實來說明

var a = 1 // 全域性作用域
let b = 1   // 塊級作用域
const c = 1   // 塊級作用域
function foo(){
  var d = 1  // 函式作用域
  this.a = 2
  this.b = 2
  this.c = 2
  this.d = 2
  console.log(a,b,c,d) // 2,1,1,1
}
foo()

a為全域性作用域中的變數,可以被this物件訪問,b/c/d則不行

可以發現,全域性作用域中的a變數被改變,b變數與c變數都沒有被改變,說明在fn()中通過this訪問不到window作用域中的b/c變數
注:這裡說的訪問不到與const定義的變數是常量沒有關係,因為如果訪問到的話,是會報typeError的

箭頭函式對this作用域的影響

var num = 1
const object = {
  num:2,
  foo: function(){
    return ()=>{
      console.log(this.num)
    }
  }
}
object.foo()  // 2

箭頭函式 this 指向 所處環境的上下文的 this 值,與是否獨立呼叫或作為屬性被呼叫,沒有關係。
箭頭函式沒有arguments/prototype,不能作為建構函式,不能使用new

總結

  1. this的指向取決於函式執行時所建立執行期上下文(execution context)的內部物件,它與當前執行函式的[[scope]]所包含的物件組成了1個新的物件,這個物件就是活動物件,然後此物件會被推入作用域鏈的前端
  2. 如果呼叫的函式,被某一個物件所擁有,那麼該函式在呼叫時,內部的this指向該物件。
  3. this指向與匿名函式沒有關係,如果函式在全域性作用域window中被獨立呼叫,那麼該函式內部的this,則指向undefined。但是在非嚴格模式中,當this指向undefined時,它會被自動指向全域性物件。
  4. 在函式被獨立呼叫時,並處於非嚴格模式下,函式內的this物件有能力也僅能訪問到全域性作用域中定義的變數即window物件, 塊級作用域/函式作用域內的變數都無法被訪問
  5. 箭頭函式 this 指向 所處環境的上下文的 this 值,與是否獨立呼叫或作為屬性被呼叫,沒有關係。

相關知識點

不理解new的實踐可以檢視我的這篇文章【Javascript】徹底捋清楚javascript中 new 運算子的實現
對作用域鏈不是很瞭解的同學,可以檢視這邊文章【Javascript】深入理解javascript作用域與作用域鏈