圖文結合深入理解 JS 中的 this 值 在 中最常見的莫過於函數了,在函數(方法)中 的出現頻率特別高,那麼 到底是什麼呢,今天就和大家一起學習總結一下 中的 。 1. 初探this 在 中是一個關鍵字,不是變數也不是屬性名, 中不允許給this賦值。 它是函數運行時,在函數體內部自動生成的一個 ...
圖文結合深入理解 JS 中的 this 值
在 JS
中最常見的莫過於函數了,在函數(方法)中 this
的出現頻率特別高,那麼 this
到底是什麼呢,今天就和大家一起學習總結一下 JS
中的 this
。
1. 初探this
this
在 JS
中是一個關鍵字,不是變數也不是屬性名, JS
中不允許給this賦值。
它是函數運行時,在函數體內部自動生成的一個對象,只能在函數體內部使用。
this
指向的是函數運行時所在的環境,也就是說函數在哪個環境中運行,this
的值就指向哪個環境。
先看下麵這段代碼的輸出結果:
function f() {
console.log(this.x);
}
var obj = {
f: f,
x: 1
};
var x = 2;
f(); // 2
obj.f(); // 1
有點奇怪,obj.f
和 f
明明指向的是同一個函數為什麼執行結果是不同的呢?
原因就在於這兩個函數運行時所在的環境是不同的。
可以結合下麵的兩張圖來理解
圖一描述了上面這段代碼的作用域鏈
圖二描述了運行 obj.f()
時的部分執行過程
圖一
圖二
如圖二所示,執行obj.f()
時,obj
對象需要先找到 f
屬性,然後通過 f
屬性中的 value
值獲取到 f
函數的地址,通過這個地址再獲取到 f
函數實際的代碼開始運行,因此此時 f
函數運行時所在的環境是 obj
環境。因為 obj
環境下 x
的值是 1
,所以最終輸出的值為 1
。
執行 f()
時,實際上是從全局對象 window
中找到 f
函數,然後再執行。此時 f
函數運行時所在的環境是全局環境,因為全局環境下的 x
的值為 2
,因此最終輸出的值為 2
。
下麵是另外一個值得註意的地方:
this
值沒有作用域的限制,嵌套函數不會從它的包含函數中繼承 this
,很多人誤以為調用嵌套函數時 this
值會指向它的外層函數的變數對象,其實並不是這樣的。
如果想訪問這個外層函數的 this
值,需要將 this
值保存在一個變數里,通常使用 self
來保存this
。
再看下麵這段代碼:
let foo = function() {
var self = this;
console.log(this === obj); // true, this就是obj對象
f(); // 嵌套函數f當做普通函數調用
function f() {
// 上面f()是被當做普通函數調用的,執行環境是全局作用域,因此f內部的this的值指向全局對象window
console.log(this === obj) // false,this在這裡指向全局對象
// self保存的是外部方法中的this,指向對象obj
console.log(self === obj) // true, self中保存的是外層函數中的this值
}
};
var obj = {
m: foo
};
obj.m();
下麵這張圖描述了執行 obj.m()
時內部運行的部分流程:
圖三
執行obj.m()
時,obj對象需要先找到 m
屬性,然後通過讀取 m
屬性中的 value
值來調用 foo
函數,所以此時 foo
函數運行時所在的環境是 obj
環境,所以 foo
內部的 this
指向 obj
環境,所以第一個 console.log
的輸出結果為 true
。
在 foo
函數內部調用 f
時,直接寫成了 f()
這種普通函數調用的方式,記住當被當做普通函數調用時,f
內部的 this
在是指向全局環境的。(嚴格模式下是 undefined
非嚴格模式下指向全局環境,一般情況下都是用的非嚴格模式 )。
因此,f
函數內部的 this
是全局對象 window
而不是obj
,這也說明瞭內層函數不會繼承外部函數的 this
。
所以,第二個 console.log
會輸出 false
,因為此時 f
內部的 this
指向全局對象 window
。第三個 console.log
會輸出 true
,因為 self
里存放的是外層函數的 this
,外層函數的 this
指向 obj
環境。
看到這裡可能有的小伙伴還是對於 this
的值到底是什麼還是有一點疑惑,能不能再歸納一下呢?好,那接下來就根據不同的情況再做一下總結,其實這個總結是之前看的阮一峰老師歸納的,在這裡加上一點自己的理解,拿過來借花獻佛。
2. this指向總結
再重申一下,this
是在函數運行時,自動生成的一個對象,this
的指向不同,歸根結底在於函數調用方式的不同,下麵就以四種不同的函數調用方式來分析 this
的指向問題。
2.1 普通函數調用
如果一個函數被當做普通函數調用,在非嚴格模式下這個函數中的 this
值就指向全局對象 window
,在嚴格模式下 this
值就是 undefined
。
下麵結合代碼和配圖來說明一下:
var x = 1;
function foo() {
console.log(this.x);
}
foo();
圖四
運行foo()
時 foo
是被當做普通函數調用,window
對象需要先找到 foo
屬性,然後通過裡面保存的地址找到 foo
函數的代碼開始運行,因此 foo
函數的運行環境是 window
環境,此時 this
的值指向 window
環境。因為 window
環境中 x
屬性的值為 1
,因此最終的輸出結果為 1
。
2.2 對象的方法調用
當某個函數被某個對象當做方法來調用時,this
就指向這個對象。
function foo() {
console.log(this.x);
}
var obj = {
x : 1,
foo : foo
}
obj.foo();
圖五
運行 obj.foo()
時 foo
函數被當做 obj
對象的方法來調用,此時 foo
函數的運行環境是 obj
環境,因此 this
指向 obj
,因為 obj.x = 1
, 所以最終輸出 1
。
2.3 構造函數調用
使用 new 構造函數
的語法會創建一個新的對象,此時 this
就指向這個新的對象。
要想明白其中的原理,就要從 new
操作符說起, 使用 new
操作符時實際上 JS
引擎做了四件事:
- 創建一個新對象(創建
person1
對象) - 將構造函數的環境賦給新對象(
this
指向了person1
) - 執行構造函數中的代碼(為
person1
對象添加屬性和方法,即name
,age
屬性,eat
方法) 返回這個新對象(將新創建的對象的地址賦給
person1
)註:上面的1,2,3步中不應該出現
person1
,因為最後一步才將新創建的對象的地址賦給person1
,上面那樣寫是為了理解方便。
function eat() {
console.log('I am eating');
}
function Person(name, age) {
this.name = name;
this.age = age;
this.eat = eat;
}
let person1 = new Person('zhangsan', '18');
console.log(person1.name); // 'zhangsan'
console.log(person1.age); // 18
person1.eat(); // 'I am eating'
圖六
通過 new
操作符的第二步,我們就可以看出 Js
引擎將構造函數的環境賦給了新的對象(person1
),因此 this
就指向了那個新創建的對象(person1
)。
2.4 利用call,apply,bind方法調用函數
這幾個都是函數的方法,它們可以改變函數運行時的環境, this
就指向它們的參數所指定的運行環境。
var obj1 = {
x : 1
};
var obj2 = {
x : 2
};
var obj3 = {
x : 3
};
var x = 4;
function foo() {
console.log(this.x);
}
var foo1 = foo.bind(obj1);
foo1(); // 1
foo.call(obj2); // 2
foo.apply(obj3); // 3
foo(); // 4
圖 七
var foo1 = foo.bind(obj1); foo1();
將函數運行的環境修改為 obj1
,this
指向 obj1
,因此輸出 1
。
foo.call(obj2);
將函數的運行環境修改為 obj2
,this
指向 obj2
,因此輸出為 2
。
foo.apply(obj3)
將函數的運行環境修改為obj3
,this
指向 obj3
,因此輸出為 3
。
foo()
純粹的函數調用,運行環境為 全局對象window, this
指向 obj4
,因此輸出為 4
。
完,如有不恰當之處,歡迎指正哦.