在函數(shù)執(zhí)行時,this
總是指向調用該函數(shù)的對象。要判斷 this
的指向,其實就是判斷this
所在的函數(shù)是誰在調用。至于函數(shù)在哪里定義,定義的方式(是聲明方式還是表達式方式),都無關緊要。
- 有對象就指向調用對象
- 沒調用對象就指向全局對象
- 用new構造就指向新對象
- 通過
apply
或call
或bind
來改變this
的所指。
this
指向調用的對象
- 在何處或者如何定義調用函數(shù)完全不會影響到
this
的行為。誰調用函數(shù)才是關鍵。 - 當對象嵌套時,
this
指向的是調用函數(shù)的那一個對象。至于外面嵌套的,以及里面被嵌套的,都不會去查找。
比如下面的例子:
b
通過屬性g()
調用函數(shù)independent
時,this
就指向b
。這時候b
只管自己,一看沒找到指定屬性prop
,就輸出undefined
。他不管外面的o
或者里面的c
有沒有要找的屬性prop
;他不會去找的。
function independent() {
return this.prop; // 函數(shù)全局定義,但是this要調用的時候才能確定,現(xiàn)在不知道
}
var o = {
prop: 37,
f: independent,
b: {
g: independent,
c: {
prop: 50,
h: independent
}
}
};
// 這里的調用者是o,所以this指向o
console.log(o.f()); // logs: 37
// 這里的調用者是b,所以this指向b;對于外面的o,以及里面嵌套的c,都不可見
console.log(o.b.g()); // logs: undefined
// 這里的調用者是c,所以this指向c
console.log(o.b.c.h()); // logs: 50
- 原型鏈中的 this指向的也是調用函數(shù)的對象,至于函數(shù)是在子類中定義的還是在父類中定義的,無關緊要。
比如下面的例子:
子類p
中的函數(shù)f
是從父類o
中繼承來的。當p
調用函數(shù)f的時候,this
就指向p
var o = {
f : function(){
return this.a + this.b;
}
};
// 原型鏈:p --> o --> Object.prototype --> null
var p = Object.create(o);
p.a = 1;
p.b = 4;
// 調用者是p,this指向p
console.log(p.f()); // 5
// 調用者是o,this指向o;o沒有a和b,所以輸出NaN
console.log(o.f()); // NaN
this
指向全局對象
- 在全局運行上下文中(在任何函數(shù)體外部),
this
指代全局對象
console.log(this.document === document); // true
// 在瀏覽器中,全局對象為 window 對象:
console.log(this === window); // true
this.a = 37;
console.log(window.a); // 37
- 在函數(shù)中使用
this
,不過調用函數(shù)的地方是全局的,此時this
就是指向全局的。
function f1() {
return this;
}
// 全局調用,this指向全局,在瀏覽器下是window
f1() === window; // true
- 如果找不到調用函數(shù)的具體對象,(不是函數(shù)的作用域),那么默認就是全局對象。
function fn1() {
var a = 2;
fn2();
}
function fn2() {
console.log( this.a );
}
// 調用fn2的是函數(shù)fn1,找不到具體的對象,所以fn2中的this指向全局對象window
fn1(); // log: undefined
this
應該指向一個具體的對象,或者全局對象,比如window
,跟函數(shù)的作用域{}
一點關系沒有。另外,this的確定是在運行時,而不是在定義的時候。所以哪個對象調用了函數(shù)才是確定this到底指向哪個對象的關鍵點。
下面代碼對this的理解和使用都是錯誤的,應該避免這種用法
function fn1() {
var a = 2;
this.fn2(); // 以為this引用的是fn1的詞法作用域是錯誤的,這里的this要刪除,沒有作用。
// 另外這里是函數(shù)定義,this的具體指向不確定。比如本例中,下面代碼調用fn1的是全局對象window,所以this指向了全局對象window
}
function fn2() {
console.log( this.a );
}
// 調用fn2的是函數(shù)fn1,找不到具體的對象,所以fn2中的this指向全局對象window
// 調用fn1的是全局對象window,所以fn1中的this指向全局對象window
fn1(); // log: undefined
- 失去隱式綁定的情況: 如果找不到具體的調用對象,就會指向默認的全局對象。下面這種情況也是需要注意區(qū)分的:
function fn() {
console.log( this.a );
}
var a = "全局"; // 定義全局變量
var obj = {
a: "obj內部", // 定義內部變量
fn: fn
};
var bar = obj.fn; // 函數(shù)引用傳遞; 其實就是bar = fn; ==> bar和obj.fn都指向了fn
// 相當于fn(); 而不是obj.fn(); 調用者是全局,所以this指向全局
bar(); // "全局"
// 調用者是obj,所以this指向obj
obj.fn(); // "obj內部"
構造函數(shù)中的this
- 當一個函數(shù)被作為一個構造函數(shù)來使用(使用
new
關鍵字),它的this
與即將被創(chuàng)建的新對象綁定。
function C(){
this.a = 37; // 這里只是函數(shù)定義,還沒有運行,this指誰還不確定
}
// 構造過程就是構造函數(shù)的執(zhí)行,已經是運行時,(函數(shù)調用也是運行時了),this可以確定。這里和o綁定,this指向o
var o = new C();
console.log(o.a); // logs 37
- 在
js
中,構造函數(shù)、普通函數(shù)、對象方法、閉包,這四者沒有明確界線。如果使用不當,構造函數(shù)就會改變其原來的意義。
比如下面這種用法就強烈不推薦使用:
function C2(){
this.a = 37;
// 這里手動返回了對象,默認的this被取消;
// new出來的對象和返回的這個對象綁定,而不是跟this。
// 這就導致this.a = 37;成了“僵尸”代碼。執(zhí)行了,但是外界根本拿不到
return {a:38};
}
// 這里o和對象{a:38}綁定,而不是this
o = new C2();
console.log(o.a); // logs 38
- 構造函數(shù)功能和其他語言中的類差不多,
ES6
引入的class
關鍵字,就可以看做是構造函數(shù)改頭換面而來的。
在構造函數(shù)的使用上,需要遵循一定的規(guī)范,充分發(fā)揮其本職功能。到ES6
切換到class
關鍵字,也就水到渠成。
(1)定義用聲明形式。不要用表達式形式。
表達式格式的函數(shù)定義方式是為了讓函數(shù)成為變量,可以當做參數(shù),返回值等使用,這給函數(shù)式編程帶來了很大的方便。這個適合普通函數(shù),對于構造函數(shù),強烈不推薦。
(2)構造函數(shù)的命名采用大駝峰式,和類的命名習慣一致
(3)構造函數(shù)不要有返回值
(4)函數(shù)或者共享變量,定義在構造函數(shù)的原型上,節(jié)省內存;定義的方式應該在構造函數(shù)的外面。構造函數(shù)名.prototype = ...;
通過 apply
或 call
或 bind
來改變 this
的所指
- 當一個函數(shù)的函數(shù)體中使用了
this
關鍵字時,通過所有函數(shù)都從Function
對象的原型中繼承的call()方法和apply()方法調用時,它的值可以綁定到一個指定的對象上。
function add(c, d){
return this.a + this.b + c + d;
}
var o = {a:1, b:3};
// The first parameter is the object to use as 'this', subsequent parameters are passed as
// arguments in the function call
add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16
// The first parameter is the object to use as 'this', the second is an array whose
// members are used as the arguments in the function call
add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34
- 使用
call
和apply
函數(shù)的時候要注意,如果傳遞的this
值不是一個對象,JavaScript
將會嘗試使用內部ToObject
操作將其轉換為對象。
因此,如果傳遞的值是一個原始值比如7
或 'foo' ,那么就會使用相關構造函數(shù)將它轉換為對象,所以原始值7
通過new Number(7)
被轉換為對象,而字符串'foo'
使用new String('foo')
轉化為對象,例如:
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call("foo"); // [object String]
bar.call(7); // [object Number]
-
ECMAScript 5
引入了 Function.prototype.bind
。調用f.bind(someObject)
會創(chuàng)建一個與f具有相同函數(shù)體和作用域的函數(shù),但是在這個新函數(shù)中,this
將永久地被綁定到了bind
的第一個參數(shù),無論這個函數(shù)是如何被調用的。
function f(){
return this.a;
}
// function g 與對象{a:"azerty"}綁定,并且不變
var g = f.bind({a:"azerty"});
// 全局調用g,this仍然指向{a:"azerty"}
console.log(g()); // azerty
// 對象o調用f,this指向o;對象o調用g,this仍然指向{a:"azerty"}
var o = {a:37, f:f, g:g};
console.log(o.f(), o.g()); // 37, azerty
內部函數(shù)
- 在內部函數(shù)中,
this
沒有按預想的綁定到外層函數(shù)對象上,而是綁定到了全局對象。這里普遍被認為是JavaScript
語言的設計錯誤,因為沒有人想讓內部函數(shù)中的this
指向全局對象。
var name = "global";
var person = {
name : "person",
hello : function(sth) {
var sayhello = function(sth) {
console.log(this.name + " says " + sth);
};
sayhello(sth);
}
}
person.hello("hello world"); // global says hello world
- 一般的處理方式是將
this
作為變量保存下來,一般約定為that
或者self
:
var name = "global";
var person = {
name : "person",
hello : function(sth) {
var that = this;
var sayhello = function(sth) {
console.log(that.name + " says " + sth);
};
sayhello(sth);
}
}
person.hello("hello world"); // person says hello world
參考文章
this
這篇文章的例子挺不錯的
javascript中this的四種用法
開頭總結的幾句話不錯,方便理解
詳解 JavaScript 中的 this
內部函數(shù)的例子來自這里,不過格式錯誤,要稍微改一下
別再為了this發(fā)愁了:JS中的this機制
這篇文章中,那個this
不是指函數(shù)作用域{}
的例子很好。
“this
跟函數(shù)在哪里定義沒有半毛錢關系,函數(shù)在哪里調用才決定了this
到底引用的是啥。”這句話也講得非常到位。