問題由來
var obj = {
foo: function () {
console.log(this.bar)
},
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
雖然obj.foo
和foo
指向同一個函數(shù)裆针,但是執(zhí)行結果可能不一樣功偿。
this
指的是函數(shù)運行時所在的環(huán)境,對于obj.foo
來說,foo
運行在obj
環(huán)境堰酿,所以this
指向obj
;對于foo()
來說宵蕉,foo()
運行在全局環(huán)境耕赘,所以this
指向全局環(huán)境。
函數(shù)的運行環(huán)境到底是怎么決定的贞绳?
內存的數(shù)據(jù)結構
-
將對象賦值給變量
obj
var obj = { foo: 5 };
JavaScript 引擎會先在內存里面谷醉,生成一個對象{ foo: 5 }
,然后把這個對象的內存地址賦值給變量obj
冈闭。
即變量obj
是一個地址俱尼。后面如果要讀取obj.foo
,引擎先從obj
拿到內存地址萎攒,然后再從該地址讀出原始的對象遇八,返回它的foo
屬性矛绘。
-
將函數(shù)賦值給變量
obj
var obj = { foo: function () {} };
JavaScript 引擎會將函數(shù)單獨保存在內存中,然后再將函數(shù)的地址賦值給foo
屬性的value屬性刃永。
由于函數(shù)是一個單獨的值货矮,所以它可以在不同的環(huán)境(上下文)執(zhí)行。
this指向的規(guī)律
- 在函數(shù)體中揽碘,簡單調用函數(shù)時(非顯示/隱式綁定下)次屠,嚴格模式下
this
綁定到undefined
,否則綁定到全局對象window/global
雳刺; - 一般由上下文對象調用劫灶,綁定在該對象上;
- 一般由
bind/call/apply
方法顯示調用掖桦,綁定到指定參數(shù)的對象上本昏; - 一般構造函數(shù)
new
調用,綁定到新創(chuàng)建的對象上枪汪; - 箭頭函數(shù)中涌穆,根據(jù)外層上下文綁定的
this
決定this
的指向。
四類場景
- "test()"形式
function f1(){
console.log(this)
}
var arr = [f1,2,3];
var f2 = arr[0];
f2();
<details>
<summary>答案</summary>
<pre>
this指向 window/嚴格模式下undefined
</pre>
</details>
直接不帶任何引用形式去調用函數(shù)雀久,則this會指向全局對象宿稀,因為沒有其他影響去改變this,this默認就是指向全局對象(瀏覽器是window赖捌,Node中是global)的祝沸。這個結論是在非嚴格模式的情況下,嚴格模式下這個this其實是undefined的越庇。
- "XXX.test()"形式
var a = 1
function test(){
console.log(this.a)
}
var obj = {
a:2,
test
}
obj.test()
<details>
<summary>答案</summary>
<pre>
2
</pre>
</details>
一句話罩锐,誰去調用這個函數(shù)的,這個函數(shù)中的this就綁定到誰身上卤唉。
測試題
var a = 1
function test(){
console.log(this.a)
}
var obj = {
a:2,
test
}
var testCopy = obj.test
testCopy()
<details>
<summary>答案</summary>
<pre><code>
1
</code></pre>
</details>
var a = 1
function test(){
console.log(this.a)
}
var obj = {
a:2,
test
}
var obj0 = {
a:3,
obj
}
obj0.obj.test()
<details>
<summary>答案</summary>
<pre>
<code>
2
即使是這種串串燒的形式涩惑,結果也是一樣的,test()中的this只對直屬上司(直接調用者obj)負責桑驱。
</code>
</pre>
</details>
- "test.call(xxx) / test.apply(xxx) / test.bind()"形式
var a = 1
function test(){
console.log(this.a)
}
var obj = {
a:2,
test
}
var testCopy = obj.test
testCopy.call(obj)
<details>
<summary>答案</summary>
<pre><code>
2
</code></pre>
</details>
可以看到竭恬,我們通過call/apply
來調用testCopy
,并且傳入了你想要 this
指向的上下文熬的,那么this
就會按照你的指示行事萍聊。
- "new test()"形式
var a = 1
function test(a){
this.a = a
}
var b = new test(2)
console.log(b.a)
<details>
<summary>答案</summary>
<pre><code>
2
</code></pre>
</details>
new這個操作符其實是new了一個新對象出來,而被new的test我們稱為構造函數(shù)悦析,我們可以在這個構造函數(shù)里定義一下將要到來的新對象的一些屬性寿桨。那么在構造函數(shù)里,我們怎樣去描述這個還未出生的新對象呢?沒錯亭螟,就是用this挡鞍。所以構造函數(shù)里的this指的就是將要被new出來的新對象。
特別的 箭頭函數(shù)
var a = 1;
var test = () => {
var a = 3;
console.log(this.a)
}
var obj = {
a: 2,
test
}
obj.test()
<details>
<summary>答案</summary>
<pre><code>
1
</code></pre>
</details>
箭頭函數(shù)中this對象就是定義時所在的作用域预烙,也就是說箭頭函數(shù)本身沒有this墨微,內部的this就是外層代碼塊作用域中的this。
測試題
var a = 1
var obj = {
a: 2,
test: ()=> {
console.log(this.a)
}
}
obj.test()
<details>
<summary>答案</summary>
<pre><code>
1
func在foo調用時定義扁掸,此時的foo所在作用域為obj翘县,因此this指向obj
</code></pre>
</details>
var a = 1
function foo(){
var test = () => {
console.log(this.a)
}
return test
}
var obj = {
a : 2,
foo:foo
}
obj.foo()()
<details>
<summary>答案</summary>
<pre><code>
2
</code></pre>
</details>
隨機測試題
var b = {
a: 23,
c: 3,
d: {
a: 78,
e: {
a: 100,
f: function () {
console.log(this.a);
}
}
}
}
var fn = b.d.e.f;
fn();
b.d.e.f();
<details>
<summary>答案</summary>
<pre><code>
undefined
100
</code></pre>
</details>
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
this.x = x;
console.log(this.x);
console.log(this);
var moveX = function(x) {
this.x = x;
};
var moveY = function(y) {
this.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x);
console.log(point.y);
console.log(x);
console.log(y);
<details>
<summary>答案</summary>
<pre><code>
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
this.x = x;
console.log(this.x); // 1
console.log(this); // point對象
var moveX = function(x) {
this.x = x;
};
var moveY = function(y) {
this.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x); // 1
console.log(point.y); // 0
console.log(x); // 1
console.log(y);// 1
</code>
point對象下的moveTo方法中的moveX與moveX方法在調用時都是全局調用,綁定的對象都是window谴分。
</pre>
</details>
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
var that = this;
var moveX = function(x) {
that.x = x;
};
var moveY = function(y) {
that.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x);
console.log(point.y);
console.log(x)
console.log(y)
<details>
<summary>答案</summary>
<pre><code>
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
var that = this; //內部變量替換
// 內部函數(shù)
var moveX = function(x) {
that.x = x;
};
// 內部函數(shù)
var moveY = function(y) {
that.y = y;
}
moveX(x); //這里依然是全局調用锈麸,但是在給變量賦值時,不再是this指向牺蹄,而是that指向忘伞,而that指向的對象是 point。
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x); // 1
console.log(point.y); // 1
console.log(x) // 報錯 x is not defined
console.log(y) //
</code></pre>
</details>
const obj = {
name: " jsCoder",
skill: ["es6", "react", "angular"],
say: function() {
for (var i = 0, len = this.skill.length; i < len; i++) {
setTimeout(function() {
console.log("No." + i + this.name);
console.log(this.skill[i]);
console.log("--------------");
}, 0);
console.log(i);
}
}
};
obj.say();
參考地址
不要再問我this的指向問題了_個人文章 - SegmentFault 思否
JavaScript 的 this 原理 - 阮一峰的網(wǎng)絡日志