javascript的this原理

前言

不知道看了多少篇關於this的文章,很難理解,每次看到的文基本都是說明了this的幾個不一樣環境的指向。而後像記住歷史同樣,記在了腦子裏,但這種記憶隨着時間的變化會愈來愈分辨不清。javascript

主要麼,也是我沒有真正深刻的去了解this。真的去深刻了,才發現this涉及的內容真的好多。html

this原理

在《高程3》中的p182的第9行寫到:在全局函數中,this等於window;當函數做爲某個對象的方法調用時,this等於那個對象;匿名函數的執行環境具備全局性,因此this等於window前端

這個概念,應該是一個前端必備的知識點吧?那麼問題來了,爲何會有這樣一個斷定呢?java

先了解一個概念:瀏覽器

執行上下文:也叫一個執行環境,有 全局執行環境函數執行環境eval數據結構

每一個執行環境中包含這三部分:變量對象/活動對象,做用域鏈,this的值app

下面這段代碼你們確定都不陌生函數

var obj = {
    name: 'a',
    say: function(){
        console.log(this.name)
    }
}

var say = obj.say;
obj.say();  //行1 a
say();  //行2 undefined
複製代碼

以上那幾行代碼,其實包含了兩個我曾經一度沒有深刻理解的內容:ui

  1. this是在函數運行時基於的函數的執行上下文綁定的。這個時候須要瞭解一下 執行上下文的建立過程
  1. 對象的值都是存入內存的,而this跟內存裏面的數據結構有關係,瞭解一下阮大大的 javascript 的 this 原理

對象的內存結構

我這裏直接進入對象中的函數的聲明與調用,以上文中的obj爲例。this

在建立執行上下文中,變量的聲明的步驟依次爲:建立、初始化、賦值。對象是一種特殊的變量,因此它也存在這3個步驟。

在初始化以後賦值以前,obj的值爲undefined,在賦值操做時,javascript引擎會在內存裏建立一個下面的對象,而後將這個對象的內存地址賦值給 obj 這個變量。

{
    name: 'a',
    say: function(){
        console.log(this.name)
    }
}
複製代碼

若要讀取 obj.name ,引擎會先拿到obj的內存地址,而後到這個內存地址中找到name這個屬性的value屬性。

當給對象賦值的是一個函數時,如上面的obj.say的地址,引擎會把函數單獨保存在內存中,而後再將函數的地址賦值給say屬性的value屬性。

{
  name: {
    [[value]]: 'a'
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  },
  say: {
    [[value]]: 函數的地址
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}
複製代碼

大概的內存結構是這樣的

這張圖就能夠很清晰的看出來了,由於函數是一個獨立的地址,那麼執行var say = obj.say;時,實際上是把函數的地址賦值給了say,那麼say就能夠獨立運行了,當執行say時,引擎能夠直接拿到函數的地址而再也不經過obj的內存了。

大概的結構是這樣的

環境變量

js容許在函數體內部,引用當前環境的其餘變量。

好比下面的代碼,x表明着當前環境的變量。

var f = function(){
    console.log(x);
}
複製代碼

那麼問題來了,函數能夠在不一樣的環境裏運行,那麼怎麼獲取到它的當前運行環境呢?因此,this就出現了,它的設計目的就是在函數體內部,指代函數當前的運行環境。

var x = 'a';

var f = function(){
    console.log(this.x);
}

var obj = {
    f:f,
    x: 'b'
}

f();  //a 在全局環境下單獨運行,this指代全局環境,全局的x爲a
obj.f(); //b 在obj中運行,this指代obj環境,obj的x 爲b

複製代碼

this的綁定

常見的有兩種,一種是直接在全局運行,函數內部的this直接綁定到全局,一個是經過對象調用,函數內部的this綁定到調用他的對象上。

除了常見的兩種,還有兩種:

1.是從一個環境傳到另外一個環境中,須要使用callapply,俗稱改變this的指向。

2.不綁定。箭頭函數獨有,此時的this繼承於做用域鏈的上一層,這裏難免要聊一下做用域鏈上的this了。

call and apply

仍是用上面的粒子

var x = 'a';

var f = function(){
    console.log(this.x);
}

var obj = {
    f:f,
    x: 'b'
}

f();  //a 在全局環境下單獨運行,this指代全局環境,全局的x爲a
f.call(obj, x); //b 
f.apply(obj, [x]); //b
複製代碼

callapply都是將this綁定到它第一個參數上。若其第一個參數不是對象,則會使用js的類型強制將第一個參數轉爲對象。

bind

bindcallapply不同的是,它會生成一個新的函數,且這個新生成的函數的this將永久地被綁定到了bind的第一個參數。具體緣由還不清楚,如有知悉的望告知。

繼續上面的那個粒子

var g = f.bind(obj);
var obj2 = {
    x: 'c'
}
var h = g.bind(obj2);
g(); //b
h(); //b g的this指向不會被bind改變
複製代碼

箭頭函數不綁定this

箭頭函數不綁定this,此時的this繼承於做用域鏈的上一層

var x = 1;
function a(){  //函數做用域上沒有x,向上找,最後找到window
    setTimeout(() => {
        console.log(this.x);
    })
}
function c(){  //函數做用域上沒有x,向上找,最後找到window
    setTimeout(() => {
        console.log(this.x);
    })
}
var obj = {
    x: 2, //對象的原型鏈上有x
    c: c
}
var obj1 = {//對象的原型鏈上沒有x
    c: c
}
a(); //1 執行環境是全局
obj.c(); //2 執行環境是對象
obj1.c(); //undefined 執行環境是對象
複製代碼

箭頭函數上的call和apply,bind

因爲箭頭函數沒有本身的this指針,經過 call() 或 apply(),bind()方法調用一個函數時,只能傳遞參數,不能綁定this,他們的第一個參數會被忽略。

var x = 1;
var a = (b) =>{  //函數做用域上沒有x,向上找,最後找到window
    console.log(this.x + b);
}
var obj = {
    b: 2,
    x: 3
}
a.call(obj, obj.b); //3
複製代碼

文末

由於寫到這道題,發現本身並不清楚爲何結果是window,而後翻了下this的原理,算是作一份總結吧。 回顧一下上面的內容,由於js引擎在生成對象的內存時,obj這個變量還未被賦值,因此它的值爲undefined,在es5中以明確,當bind第一個參數爲undefined時,在瀏覽器上將執行window。

var obj = {
        say: function () {
          function _say() {
            console.log(this)
          }
          return _say.bind(obj)
        }()
      }
 obj.say() //window
複製代碼

如有理解不合理的地方,還望指正。

相關文章
相關標籤/搜索