問題由來
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指向同一個(gè)函數(shù),但是執(zhí)行結(jié)果可能不一樣。
this指的是函數(shù)運(yùn)行時(shí)所在的環(huán)境,對于obj.foo來說,foo運(yùn)行在obj環(huán)境,所以this指向obj;對于foo()來說,foo()運(yùn)行在全局環(huán)境,所以this指向全局環(huán)境。
函數(shù)的運(yùn)行環(huán)境到底是怎么決定的?
內(nèi)存的數(shù)據(jù)結(jié)構(gòu)
-
將對象賦值給變量
obj
var obj = { foo: 5 };
JavaScript 引擎會(huì)先在內(nèi)存里面,生成一個(gè)對象{ foo: 5 },然后把這個(gè)對象的內(nèi)存地址賦值給變量obj。
即變量obj是一個(gè)地址。后面如果要讀取obj.foo,引擎先從obj拿到內(nèi)存地址,然后再從該地址讀出原始的對象,返回它的foo屬性。
avatar -
將函數(shù)賦值給變量
obj
var obj = { foo: function () {} };
JavaScript 引擎會(huì)將函數(shù)單獨(dú)保存在內(nèi)存中,然后再將函數(shù)的地址賦值給foo屬性的value屬性。
avatar
由于函數(shù)是一個(gè)單獨(dú)的值,所以它可以在不同的環(huán)境(上下文)執(zhí)行。
this指向的規(guī)律
- 在函數(shù)體中,簡單調(diào)用函數(shù)時(shí)(非顯示/隱式綁定下),嚴(yán)格模式下
this綁定到undefined,否則綁定到全局對象window/global; - 一般由上下文對象調(diào)用,綁定在該對象上;
- 一般由
bind/call/apply方法顯示調(diào)用,綁定到指定參數(shù)的對象上; - 一般構(gòu)造函數(shù)
new調(diào)用,綁定到新創(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/嚴(yán)格模式下undefined
</pre>
</details>
直接不帶任何引用形式去調(diào)用函數(shù),則this會(huì)指向全局對象,因?yàn)闆]有其他影響去改變this,this默認(rèn)就是指向全局對象(瀏覽器是window,Node中是global)的。這個(gè)結(jié)論是在非嚴(yán)格模式的情況下,嚴(yán)格模式下這個(gè)this其實(shí)是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>
一句話,誰去調(diào)用這個(gè)函數(shù)的,這個(gè)函數(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
即使是這種串串燒的形式,結(jié)果也是一樣的,test()中的this只對直屬上司(直接調(diào)用者obj)負(fù)責(zé)。
</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來調(diào)用testCopy,并且傳入了你想要 this 指向的上下文,那么this就會(huì)按照你的指示行事。
- "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這個(gè)操作符其實(shí)是new了一個(gè)新對象出來,而被new的test我們稱為構(gòu)造函數(shù),我們可以在這個(gè)構(gòu)造函數(shù)里定義一下將要到來的新對象的一些屬性。那么在構(gòu)造函數(shù)里,我們怎樣去描述這個(gè)還未出生的新對象呢?沒錯(cuò),就是用this。所以構(gòu)造函數(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í)所在的作用域,也就是說箭頭函數(shù)本身沒有this,內(nèi)部的this就是外層代碼塊作用域中的this。
測試題
var a = 1
var obj = {
a: 2,
test: ()=> {
console.log(this.a)
}
}
obj.test()
<details>
<summary>答案</summary>
<pre><code>
1
func在foo調(diào)用時(shí)定義,此時(shí)的foo所在作用域?yàn)閛bj,因此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>
隨機(jī)測試題
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方法在調(diào)用時(shí)都是全局調(diào)用,綁定的對象都是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; //內(nèi)部變量替換
// 內(nèi)部函數(shù)
var moveX = function(x) {
that.x = x;
};
// 內(nèi)部函數(shù)
var moveY = function(y) {
that.y = y;
}
moveX(x); //這里依然是全局調(diào)用,但是在給變量賦值時(shí),不再是this指向,而是that指向,而that指向的對象是 point。
moveY(y);
}
};
point.moveTo(1, 1);
console.log(point.x); // 1
console.log(point.y); // 1
console.log(x) // 報(bào)錯(cuò) 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的指向問題了_個(gè)人文章 - SegmentFault 思否
JavaScript 的 this 原理 - 阮一峰的網(wǎng)絡(luò)日志

