【Javascript】深刻理解this做用域問題

理解this做用域

《javascript高級程序設計》中有說到:javascript

this對象是在運行時基於函數的執行環境綁定的:在全局函數中,this等於window ,而當函數被做爲某個對象調用時,this等於那個對象。不過,匿名函數具備全局性,所以this對象同常指向window前端

不過,在全局函數中,this等於window,匿名函數具備全局性,所以this對象一般指向window,針對於匿名函數this具備全局性的觀點還是有爭議的,可參考 www.zhihu.com/question/21…java

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

驗證過程以下:

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

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個匿名函數github

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做用域與做用域鏈測試

實踐是檢驗真理的惟一標準,讓咱們用代碼測試一下ui

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

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做用域與做用域鏈

相關文章
相關標籤/搜索