不知道看了多少篇關於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
this
是在函數運行時基於的函數的執行上下文綁定的。這個時候須要瞭解一下 執行上下文的建立過程
- 對象的值都是存入內存的,而
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綁定到調用他的對象上。
除了常見的兩種,還有兩種:
1.是從一個環境傳到另外一個環境中,須要使用
call
和apply
,俗稱改變this的指向。
2.不綁定。箭頭函數獨有,此時的this繼承於做用域鏈的上一層,這裏難免要聊一下做用域鏈上的this了。
仍是用上面的粒子
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
複製代碼
call
和apply
都是將this綁定到它第一個參數上。若其第一個參數不是對象,則會使用js的類型強制將第一個參數轉爲對象。
bind
和call
、apply
不同的是,它會生成一個新的函數,且這個新生成的函數的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繼承於做用域鏈的上一層
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 執行環境是對象
複製代碼
因爲箭頭函數沒有本身的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
複製代碼
如有理解不合理的地方,還望指正。