js中this指向有幾種情況
- 全局環(huán)境
- 函數(shù)調(diào)用
- 構(gòu)造調(diào)用
- apply、call、bind綁定
- 箭頭函數(shù)
全局環(huán)境
在瀏覽器中,無論是否在嚴格模式下,在全局執(zhí)行環(huán)境中(在任何函數(shù)體外部)this 都指向全局對象。
在Nodejs環(huán)境,在全局執(zhí)行環(huán)境中,this指向module.exports。
函數(shù)調(diào)用
分析一般函數(shù)調(diào)用時this的指向,需要先引入引用類型的定義。
引用類型
A Reference is a resolved name or property binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Symbol, a Number, or an Environment Record (8.1.1). A base value of undefined indicates that the Reference could not be resolved to a binding. The referenced name is a String or Symbol value.——ES2015 6.2.3 The Reference Specification Type
引用類型是一個ES規(guī)范定義的抽象類型,是為了解釋delete、typeof等概念使用,并非js語言中的類型。引用類型由3部分組成:
- base value,可能是undefined、object、boolean、string、symbol、number或一個環(huán)境記錄。是這個引用對應的屬性所處的上下文。
- referenced name,即引用對應的屬性的名。
- strict reference flag,標記是否處于嚴格模式下。
GetValue
ES定義的一個抽象方法,用于獲取引用類型真實的值。
在解釋相關(guān)概念后回到主題,普通的函數(shù)調(diào)用,this是根據(jù)函數(shù)調(diào)用操作符【(...)】左邊的值決定的,如果左邊的是引用類型的值,則this指向該引用類型的base。若非引用類型,則為undefined。
例如:
'use strict'
var x = 2;
var foo = {
x : 1,
bar: function () {
console.log(this.x);
}
};
foo.bar(); //1
(foo.bar = foo.bar)(); //Uncaught TypeError: Cannot read property 'x' of undefined
//ps.如果沒有啟用嚴格模式,this為undefined時,會指向全局對象,則會輸出2
由于foo.bar = foo.bar采用了賦值操作,此時返回內(nèi)容為引用類型的真實值,所以返回的并非引用類型,此時this為undefined。
構(gòu)造調(diào)用
當一個函數(shù)被使用 new 操作符進行調(diào)用時稱為構(gòu)造調(diào)用時,在進行構(gòu)造調(diào)用時,函數(shù)內(nèi)部的this綁定到正在構(gòu)造的新對象上。new相關(guān)內(nèi)容會在后續(xù)文章中詳細寫。
apply、call、bind綁定
使用apply,call方法調(diào)用函數(shù)時,this將綁定到方法的第一個參數(shù)。例如:
var x = {
a: 1
}
function test() {
console.log(this.a);
}
test.apply(x); // 1 使用apply調(diào)用時,this.綁定到x上了
test.call(x); // 1
test(); //undefined
bind方法會創(chuàng)建一個新的函數(shù),新函數(shù)中的bind綁定到bind方法的第一個參數(shù)。bind創(chuàng)建的新函數(shù)不能再被bind進行綁定,也不會被apply、call修改。bind創(chuàng)建的新函數(shù)在進行構(gòu)造調(diào)用時會改變成指向新創(chuàng)建的對象。例如:
var x = {
foo: 1
}
var y = {
foo: 2
}
var z = {
foo: 3
}
function test() {
console.log(this.foo);
}
var a = test.bind(x);
a();//1 使用bind進行綁定
var b = a.bind(y);
b();//1 bind綁定創(chuàng)建的函數(shù)不能被二次綁定,this不再改變
a.call(y);//1 也不會因為apply、call調(diào)用改變this
new a(); // undefined 但是能被 new 操作符改變
var c = test.bind(z);
c();//3
箭頭函數(shù)
箭頭函數(shù)的this時根據(jù)詞法作用域的,this指向該函數(shù)創(chuàng)建時的環(huán)境。箭頭函數(shù)沒有進行this綁定,在箭頭函數(shù)中使用this,相當于尋找外部環(huán)境的this。此時this函數(shù)尋值跟普通變量尋值一樣,會在作用域鏈中逐層尋找。因為箭頭函數(shù)沒有自己的this,所以有以下幾個特點:
- 不能進行構(gòu)造調(diào)用,如:
function foo() {
}
var bar = ()=>{
}
console.log(new foo()); // foo {}
console.log(new bar());// Uncaught TypeError: bar is not a constructor
- 不能通過apply、bind、call直接改變箭頭函數(shù)中this
- 改變所在作用域中的this的同時會改變箭頭函數(shù)中的this
例如:
var x = 10;
var test1 = {
x : 20,
y : function(){
return ()=>{
console.log(this.x);
}
},
z : function(){
return function(){
console.log(this.x);
}
}
}
var test2 = { x : 30};
var test3 = { x : 40};
test2.y = test1.y();
test2.z = test1.z();
test2.y(); // 20 因為箭頭函數(shù)的this是在創(chuàng)建的時候確定的詞法作用域,實際上在test1.y()的時候就確定了,不會在代碼執(zhí)行時動態(tài)改變
test2.z();// 30 普通函數(shù)調(diào)用的this是動態(tài)作用域的,由于z的base是test2的環(huán)境記錄,所以取到的是test2的x
test2.y.call(test3);//20 箭頭函數(shù)沒有自己的this,所以不會被call改變
test2.z.call(test3);//40 普通函數(shù)會被call改變
test1.y.call(test2)();//30 箭頭函數(shù)在call的時候創(chuàng)建的,此時的上下文不是test1而是test2
test1.z.call(test2)();//10 普通函數(shù)的this是動態(tài)作用域的,此時為undefined,被自動轉(zhuǎn)化成了全局對象
箭頭函數(shù)常用于事件綁定、設(shè)置定時等回調(diào)函數(shù)。例如以下例子中,原意時希望1秒后再檢查t的值,但因為普通函數(shù)中的this是動態(tài)改變的,并不能得到想要的結(jié)果。
function test(){
this.x = 1;
this.y = function(){
console.log(this.x);
}
}
var t = new test();
setTimeout(t.y, 1000); //undefined
t.x = 2;
此時可以使用箭頭函數(shù)解決,由于箭頭函數(shù)的this不會隨調(diào)用環(huán)境動態(tài)改變,所以可以得到想要的結(jié)果
function test(){
this.x = 1;
this.y = ()=>{
console.log(this.x);
}
}
var t = new test();
setTimeout(t.y, 1000);//2
t.x = 2;