圖文結合深入理解 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,
完,如有不恰當之處,歡迎指正哦.
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/125550.html
標籤:其他
上一篇:求c51大神指教
下一篇:html5+css3的神奇搭配
