js中this指向有幾種情況
- 全局環(huán)境
- 函數(shù)調(diào)用
- 構(gòu)造調(diào)用
- apply柜砾、call轩拨、bind綁定
- 箭頭函數(shù)
全局環(huán)境
在瀏覽器中践瓷,無(wú)論是否在嚴(yán)格模式下,在全局執(zhí)行環(huán)境中(在任何函數(shù)體外部)this 都指向全局對(duì)象亡蓉。
在Nodejs環(huán)境晕翠,在全局執(zhí)行環(huán)境中,this指向module.exports砍濒。
函數(shù)調(diào)用
分析一般函數(shù)調(diào)用時(shí)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
引用類型是一個(gè)ES規(guī)范定義的抽象類型爸邢,是為了解釋delete樊卓、typeof等概念使用,并非js語(yǔ)言中的類型杠河。引用類型由3部分組成:
- base value碌尔,可能是undefined浇辜、object、boolean唾戚、string柳洋、symbol、number或一個(gè)環(huán)境記錄叹坦。是這個(gè)引用對(duì)應(yīng)的屬性所處的上下文熊镣。
- referenced name,即引用對(duì)應(yīng)的屬性的名募书。
- strict reference flag绪囱,標(biāo)記是否處于嚴(yán)格模式下。
GetValue
ES定義的一個(gè)抽象方法锐膜,用于獲取引用類型真實(shí)的值毕箍。
在解釋相關(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.如果沒(méi)有啟用嚴(yán)格模式种远,this為undefined時(shí)涩澡,會(huì)指向全局對(duì)象,則會(huì)輸出2
由于foo.bar = foo.bar采用了賦值操作坠敷,此時(shí)返回內(nèi)容為引用類型的真實(shí)值妙同,所以返回的并非引用類型,此時(shí)this為undefined膝迎。
構(gòu)造調(diào)用
當(dāng)一個(gè)函數(shù)被使用 new 操作符進(jìn)行調(diào)用時(shí)稱為構(gòu)造調(diào)用時(shí)粥帚,在進(jìn)行構(gòu)造調(diào)用時(shí),函數(shù)內(nèi)部的this綁定到正在構(gòu)造的新對(duì)象上限次。new相關(guān)內(nèi)容會(huì)在后續(xù)文章中詳細(xì)寫芒涡。
apply、call卖漫、bind綁定
使用apply费尽,call方法調(diào)用函數(shù)時(shí),this將綁定到方法的第一個(gè)參數(shù)羊始。例如:
var x = {
a: 1
}
function test() {
console.log(this.a);
}
test.apply(x); // 1 使用apply調(diào)用時(shí)旱幼,this.綁定到x上了
test.call(x); // 1
test(); //undefined
bind方法會(huì)創(chuàng)建一個(gè)新的函數(shù),新函數(shù)中的bind綁定到bind方法的第一個(gè)參數(shù)突委。bind創(chuàng)建的新函數(shù)不能再被bind進(jìn)行綁定速警,也不會(huì)被apply叹誉、call修改。bind創(chuàng)建的新函數(shù)在進(jìn)行構(gòu)造調(diào)用時(shí)會(huì)改變成指向新創(chuàng)建的對(duì)象闷旧。例如:
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進(jìn)行綁定
var b = a.bind(y);
b();//1 bind綁定創(chuàng)建的函數(shù)不能被二次綁定,this不再改變
a.call(y);//1 也不會(huì)因?yàn)閍pply钧唐、call調(diào)用改變this
new a(); // undefined 但是能被 new 操作符改變
var c = test.bind(z);
c();//3
箭頭函數(shù)
箭頭函數(shù)的this時(shí)根據(jù)詞法作用域的忙灼,this指向該函數(shù)創(chuàng)建時(shí)的環(huán)境。箭頭函數(shù)沒(méi)有進(jìn)行this綁定钝侠,在箭頭函數(shù)中使用this该园,相當(dāng)于尋找外部環(huán)境的this。此時(shí)this函數(shù)尋值跟普通變量尋值一樣帅韧,會(huì)在作用域鏈中逐層尋找里初。因?yàn)榧^函數(shù)沒(méi)有自己的this,所以有以下幾個(gè)特點(diǎn):
- 不能進(jìn)行構(gòu)造調(diào)用忽舟,如:
function foo() {
}
var bar = ()=>{
}
console.log(new foo()); // foo {}
console.log(new bar());// Uncaught TypeError: bar is not a constructor
- 不能通過(guò)apply双妨、bind、call直接改變箭頭函數(shù)中this
- 改變所在作用域中的this的同時(shí)會(huì)改變箭頭函數(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 因?yàn)榧^函數(shù)的this是在創(chuàng)建的時(shí)候確定的詞法作用域叮阅,實(shí)際上在test1.y()的時(shí)候就確定了刁品,不會(huì)在代碼執(zhí)行時(shí)動(dòng)態(tài)改變
test2.z();// 30 普通函數(shù)調(diào)用的this是動(dòng)態(tài)作用域的,由于z的base是test2的環(huán)境記錄浩姥,所以取到的是test2的x
test2.y.call(test3);//20 箭頭函數(shù)沒(méi)有自己的this挑随,所以不會(huì)被call改變
test2.z.call(test3);//40 普通函數(shù)會(huì)被call改變
test1.y.call(test2)();//30 箭頭函數(shù)在call的時(shí)候創(chuàng)建的,此時(shí)的上下文不是test1而是test2
test1.z.call(test2)();//10 普通函數(shù)的this是動(dòng)態(tài)作用域的勒叠,此時(shí)為undefined兜挨,被自動(dòng)轉(zhuǎn)化成了全局對(duì)象
箭頭函數(shù)常用于事件綁定、設(shè)置定時(shí)等回調(diào)函數(shù)眯分。例如以下例子中拌汇,原意時(shí)希望1秒后再檢查t的值,但因?yàn)槠胀ê瘮?shù)中的this是動(dòng)態(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ù)解決,由于箭頭函數(shù)的this不會(huì)隨調(diào)用環(huán)境動(dòng)態(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;