JavaScript進階之(一) this指標
學習整理用,地址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this
函式的 this 關鍵字在 JavaScript 中的表現略有不同,此外,在嚴格模式和非嚴格模式之間也會有一些差別。
(1)在絕大多數情況下,函式的呼叫方式決定了 this 的值(執行時繫結)。this 不能在執行期間被賦值,並且在每次函式被呼叫時 this 的值也可能會不同。
(2)ES5 引入了 bind 方法來設定函式的 this 值,而不用考慮函式如何被呼叫的。
(3)ES2015 引入了箭頭函式,箭頭函式不提供自身的this 繫結(this 的值將保持為閉合詞法上下文的值)。
一、語法
當前執行上下文(global、function 或 eval)的一個屬性,在非嚴格模式下,總是指向一個物件,在嚴格模式下可以是任意值。
(1)全域性上下文
無論是否在嚴格模式下,在全域性執行環境中(在任何函式體外部)this 都指向全域性物件。
// 在瀏覽器中, window 物件同時也是全域性物件:
console.log(this === window); // true
a = 37;
console.log(window.a); // 37
this.b = "MDN";
console.log(window.b) // "MDN"
console.log(b) // "MDN"
Note: 你可以使用 globalThis 獲取全域性物件,無論你的程式碼是否在當前上下文執行。
(2)函式上下文
在函式內部,this的值取決於函式被呼叫的方式。
因為下面的程式碼不在嚴格模式下,且 this 的值不是由該呼叫設定的,所以 this 的值預設指向全域性物件,瀏覽器中就是 window。
function f1(){
return this;
}
//在瀏覽器中:
f1() === window; //在瀏覽器中,全域性物件是window
//在Node中:
f1() === globalThis;
然而,在嚴格模式下,如果進入執行環境時沒有設定 this 的值,this 會保持為 undefined,如下:
function f2(){
"use strict"; // 這裡是嚴格模式
return this;
}
f2() === undefined;
在第二個例子中,this 應是 undefined,因為 f2 是被直接呼叫的,而不是作為物件的屬性或方法呼叫的(如 window.f2())。有一些瀏覽器最初在支援嚴格模式時沒有正確實現這個功能,於是它們錯誤地返回了window物件。
(3)類上下文
this 在 類 中的表現與在函式中類似,因為類本質上也是函式,但也有一些區別和注意事項。
(1)在類的建構函式中,this 是一個常規物件。類中所有非靜態的方法都會被新增到 this 的原型中:
class Example {
constructor() {
const proto = Object.getPrototypeOf(this);
console.log(Object.getOwnPropertyNames(proto));
}
first(){}
second(){}
static third(){}
}
new Example();
注意:靜態方法不是 this 的屬性,它們只是類自身的屬性
2)派生類
不像基類的建構函式,派生類的建構函式沒有初始的 this 繫結。在建構函式中呼叫 super()會生成一個 this 繫結,並相當於執行如下程式碼,Base為基類: this=new Base();
警告:在呼叫 super() 之前引用 this 會丟擲錯誤。
class Polygon {
constructor(height, width) {
this.name = 'Rectangle';
this.height = height;
this.width = width;
}
sayName() {
console.log('Hi, I am a ', this.name + '.');
}
get area() {
return this.height * this.width;
}
set area(value) {
this._area = value;
}
}
class Square extends Polygon {
constructor(length) {
this.height; // ReferenceError,super 需要先被呼叫!
// 這裡,它呼叫父類的建構函式的,
// 作為Polygon 的 height, width
super(length, length);
// 注意: 在派生的類中, 在你可以使用'this'之前, 必須先呼叫super()。
// 忽略這, 這將導致引用錯誤。
this.name = 'Square';
}
}
派生類不能在呼叫 super() 之前返回,除非其建構函式返回的是一個物件,或者根本沒有建構函式。
class Base {}
class Good extends Base {} //沒有建構函式
class AlsoGood extends Base {
constructor() {
return {a: 5}; //返回了一個物件
}
}
class Bad extends Base {
constructor() {} // 沒有返回物件,有建構函式 出錯:Must call super constructor in derived class before accessing 'this' or returning from derived constructor
}
new Good();
new AlsoGood();
new Bad();
二、示例
(1) 函式上下文中的this
==函式的呼叫方式決定了 this 的值(執行時繫結)==
// An object can be passed as the first argument to call or apply and this will be bound to it.
var obj = {a: 'Custom'}; //當一個物件作為第一個引數傳遞給call or apply時 this=obj
// We declare a variable and the variable is assigned to the global window as its property.
var a = 'Global'; //全域性的變數,分配給windows
function whatsThis() {
return this.a; // The value of this is dependent on how the function is called //取決於呼叫方式
}
whatsThis(); // 'Global' as this in the function isn't set, so it defaults to the global/window object
// 全域性的windows==this,訪問windows上的a屬性
whatsThis.call(obj); // 'Custom' as this in the function is set to obj
//this=obj,obj作為this作用與wahtsThis函式內
whatsThis.apply(obj); // 'Custom' as this in the function is set to obj
(2) this 和物件轉換
function add(c, d) {
return this.a + this.b + c + d;
}
var o = {a: 1, b: 3};
// 第一個引數是用作“this”的物件
// 其餘引數用作函式的引數
add.call(o, 5, 7); // 16
// 第一個引數是用作“this”的物件
// 第二個引數是一個陣列,陣列中的兩個成員用作函式引數
add.apply(o, [10, 20]); //34
在非嚴格模式下使用 call 和 apply 時,如果用作 this 的值不是物件,則會被嘗試轉換為物件。null 和 undefined 被轉換為全域性物件。
原始值如 7 或 ‘foo’ 會使用相應建構函式轉換為物件。因此 7 會被轉換為 new Number(7) 生成的物件,字串 ‘foo’ 會轉換為 newString(‘foo’) 生成的物件。
function bar() {
console.log(Object.prototype.toString.call(this));
}
bar.call(7); // [object Number]
bar.call('foo'); // [object String]
bar.call(undefined); [object global]
(3)bind方法
ECMAScript 5 引入了 Function.prototype.bind()。呼叫f.bind(someObject)會建立一個與f具有相同函式體和作用域的函式,但是在這個新函式中,this將永久地被繫結到了bind的第一個引數,無論這個函式是如何被呼叫的。
function f(){
return this.a;
}
var g = f.bind({a:"azerty"});
console.log(g()); // azerty
var h = g.bind({a:'yoo'}); // bind只生效一次!,第二次繫結無效
console.log(h()); // azerty
var o = {a:37, f:f, g:g, h:h};
console.log(o.a, o.f(), o.g(), o.h()); // 37, 37, azerty, azerty
(4)箭頭函式(較難理解)
為什麼叫Arrow Function?因為它的定義用的就是一個箭頭:
(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression
// equivalent to: (param1, param2, …, paramN) => { return expression; }
// Parentheses are optional when there's only one parameter name:
(singleParam) => { statements }
singleParam => { statements }
// A function with no parameters should be written with a pair of parentheses.
() => { statements }
在箭頭函式中,this與封閉詞法環境的this保持一致。在全域性程式碼中,它將被設定為全域性物件:
var globalObject = this;
var foo = (() => this); //一個匿名函式,返回this
console.log(foo() === globalObject); //true
注意:如果將this傳遞給call、bind、或者apply來呼叫箭頭函式,它將被忽略。不過你仍然可以為呼叫新增引數,不過第一個引數(thisArg)應該設定為null。
// 接著上面的程式碼
// 作為物件的一個方法呼叫
var obj = {foo: foo};
console.log(obj.foo() === globalObject); // true
// 嘗試使用call來設定this
console.log(foo.call(obj) === globalObject); // true
// 嘗試使用bind來設定this
foo = foo.bind(obj);
console.log(foo() === globalObject); // true
無論如何,foo 的 this 被設定為他被建立時的環境(在上面的例子中,就是全域性物件)。
這同樣適用於在其他函式內建立的箭頭函式:這些箭頭函式的this被設定為封閉的詞法環境的。
// 建立一個含有bar方法的obj物件,
// bar返回一個函式,
// 這個函式返回this,
// 這個返回的函式是以箭頭函式建立的,
// 所以它的this被永久繫結到了它外層函式的this。
// bar的值可以在呼叫中設定,這反過來又設定了返回函式的值。
var obj = {
bar: function() {
var x = (() => this); //永久繫結到了它外層函式的this 即 obj
return x;
}
};
// 作為obj物件的一個方法來呼叫bar,把它的this繫結到obj。
// 將返回的函式的引用賦值給fn。
var fn = obj.bar();
// 直接呼叫fn而不設定this,
// 通常(即不使用箭頭函式的情況)預設為全域性物件
// 若在嚴格模式則為undefined
console.log(fn() === obj); // true
// 但是注意,如果你只是引用obj的方法,
// 而沒有呼叫它
var fn2 = obj.bar;
// 那麼呼叫箭頭函式後,this指向window,因為它從 bar 繼承了this。
console.log(fn2()() == window); //true
在上面的例子中,一個賦值給了 obj.bar的函式(稱為匿名函式 A),返回了另一個箭頭函式(稱為匿名函式 B)。因此,在 A 呼叫時,函式B的this被永久設定為obj.bar(函式A)的this。當返回的函式(函式B)被呼叫時,它this始終是最初設定的。在上面的程式碼示例中,函式B的this被設定為函式A的this,即obj,所以即使被呼叫的方式通常將其設定為 undefined 或全域性物件(或者如前面示例中的其他全域性執行環境中的方法),它的 this 也仍然是 obj 。
(5)作為物件的方法
當函式作為物件裡的方法被呼叫時,this 被設定為呼叫該函式的物件。
下面的例子中,當 o.f() 被呼叫時,函式內的 this 將繫結到 o 物件。
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); //37
請注意,這樣的行為完全不會受函式定義方式或位置的影響。在前面的例子中,我們在定義物件o的同時,將其中的函式定義為成員 f 。但是,我們也可以先定義函式,然後再將其附屬到o.f。這樣做的結果是一樣的:
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f());// 37
這表明函式是從 o 的 f 成員呼叫的才是重點。
同樣,this 的繫結只受最接近的成員引用的影響。在下面的這個例子中,我們把一個方法g當作物件o.b的函式呼叫。在這次執行期間,函式中的this將指向o.b。事實證明,這與他是物件 o 的成員沒有多大關係,最近的引用才是最重要的。
o.b = {g: independent, prop: 42};
console.log(o.b.g());//42
原型鏈中的 this
對於在物件原型鏈上某處定義的方法,同樣的概念也適用。如果該方法存在於一個物件的原型鏈上,那麼 this 指向的是呼叫這個方法的物件,就像該方法就在這個物件上一樣。
var o = {
f: function() {
return this.a + this.b;
}
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f());
在這個例子中,物件 p 沒有屬於它自己的 f 屬性,它的 f 屬性繼承自它的原型。雖然最終是在 o 中找到 f 屬性的,這並沒有關係;查詢過程首先從 p.f 的引用開始,所以函式中的 this 指向p。也就是說,因為f是作為p的方法呼叫的,所以它的this指向了p。這是 JavaScript 的原型繼承中的一個有趣的特性。
getter 與 setter 中的 this
再次,相同的概念也適用於當函式在一個 getter 或者 setter 中被呼叫。用作 getter 或 setter 的函式都會把 this 繫結到設定或獲取屬性的物件。
function sum() {
return this.a + this.b + this.c;
}
var o = {
a: 1,
b: 2,
c: 3,
get average() {
return (this.a + this.b + this.c) / 3;
}
};
Object.defineProperty(o, 'sum', {
get: sum, enumerable: true, configurable: true});
console.log(o.average, o.sum); //2 .6
(6) 作為建構函式
當一個函式用作建構函式時(使用new關鍵字),它的this被繫結到正在構造的新物件。
雖然建構函式返回的預設值是 this 所指的那個物件,但它仍可以手動返回其他的物件(如果返回值不是一個物件,則返回 this 物件)。
/*
* 建構函式這樣工作:
*
* function MyConstructor(){
* // 函式實體寫在這裡
* // 根據需要在this上建立屬性,然後賦值給它們,比如:
* this.fum = "nom";
* // 等等...
*
* // 如果函式具有返回物件的return語句,
* // 則該物件將是 new 表示式的結果。
* // 否則,表示式的結果是當前繫結到 this 的物件。
* //(即通常看到的常見情況)。
* }
*/
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {a:38};//手動返回其他的物件
}
o = new C2();
console.log(o.a); // logs 378
在剛剛的例子中(C2),因為在呼叫建構函式的過程中,手動的設定了返回物件,與this繫結的預設物件被丟棄了。(這基本上使得語句 “this.a = 37;”成了“殭屍”程式碼,實際上並不是真正的“殭屍”,這條語句執行了,但是對於外部沒有任何影響,因此完全可以忽略它)。
(7) 作為一個DOM事件處理函式
當函式被用作事件處理函式時,它的 this 指向觸發事件的元素(一些瀏覽器在使用非 addEventListener 的函式動態地新增監聽函式時不遵守這個約定)。
// 被呼叫時,將關聯的元素變成藍色
function bluify(e){
console.log(this === e.currentTarget); // 總是 true
// 當 currentTarget 和 target 是同一個物件時為 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 獲取文件中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify作為元素的點選監聽函式,當元素被點選時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}
(8) 作為一個內聯事件處理函式
當程式碼被內聯 on-event 處理函式 呼叫時,它的this指向監聽器所在的DOM元素:
<button onclick="alert(this.tagName.toLowerCase());">
Show this
</button>
上面的 alert 會顯示 button。注意只有外層程式碼中的 this 是這樣設定的:
<button onclick="alert((function(){return this})());">
Show inner this
</button>
在這種情況下,沒有設定內部函式的 this,所以它指向 global/window 物件(即非嚴格模式下呼叫的函式未設定 this 時指向的預設物件)。
(9)類中的this
和其他普通函式一樣,方法中的 this 值取決於它們如何被呼叫。有時,改寫這個行為,讓類中的 this 值總是指向這個類例項會很有用。為了做到這一點,可在建構函式中繫結類方法:
class Car {
constructor() {
// Bind sayBye but not sayHi to show the difference
this.sayBye = this.sayBye.bind(this);
//sayBye的this繫結繫結為建立的car變數
//sayHi的this 沒有
}
sayHi() {
console.log(`Hello from ${this.name}`);
}
sayBye() {
console.log(`Bye from ${this.name}`);
}
get name() {
return 'Ferrari';
}
}
class Bird {
get name() {
return 'Tweety';
}
}
const car = new Car();
const bird = new Bird();
// The value of 'this' in methods depends on their caller
car.sayHi(); // Hello from Ferrari
bird.sayHi = car.sayHi;
bird.sayHi(); // Hello from Tweety
// For bound methods, 'this' doesn't depend on the caller
bird.sayBye = car.sayBye;
bird.sayBye(); // Bye from Ferrari
注意:類內部總是嚴格模式。呼叫一個 this 值為 undefined 的方法會丟擲錯誤。
相關文章
- JavaScript(1)之——this指標JavaScript指標
- c-指標進階篇指標
- C++進階(智慧指標)C++指標
- javascript 進階之 - PromiseJavaScriptPromise
- JavaScript進階之繼承JavaScript繼承
- JavaScript進階之原型鏈JavaScript原型
- 【C進階】28、指標和陣列分析指標陣列
- 【C進階】26、指標的本質分析指標
- 指標高階指標
- 指標初階指標
- JavaScript進階JavaScript
- JavaScript進階之函式柯里化JavaScript函式
- 前端進階之 Javascript 抽象語法樹前端JavaScript抽象語法樹
- Rust 程式設計影片教程(進階)——009 智慧指標Rust程式設計指標
- JavaScript進階之模擬new Object()過程JavaScriptObject
- JavaScript進階之模擬new Object過程JavaScriptObject
- JavaScript進階之模擬call,apply和bindJavaScriptAPP
- 前端入門13-JavaScript進階之原型前端JavaScript原型
- C/C++高階訓練之指標初識C++指標
- Rust 程式設計視訊教程(進階)——009 智慧指標Rust程式設計指標
- 前端入門15-JavaScript進階之原型鏈前端JavaScript原型
- 前端入門19-JavaScript進階之閉包前端JavaScript
- Go語言高階資料型別之指標篇Go資料型別指標
- 【Android進階】RecyclerView之ItemDecoration(一)AndroidView
- 《前端之路》之 JavaScript 高階技巧、高階函式(一)前端JavaScript函式
- 前端入門16-JavaScript進階之EC和VO前端JavaScript
- Rust 程式設計影片教程(進階)——028_1 函式指標Rust程式設計函式指標
- 前端進階之養成一個良好的程式碼風格--JavaScript standard style前端JavaScript
- JavaScript進階教程日記JavaScript
- Rust 程式設計視訊教程(進階)——028_1 函式指標Rust程式設計函式指標
- Rust 程式設計影片教程(進階)——025_2 解引用裸指標Rust程式設計指標
- 深入理解Go系列一之指標變數Go指標變數
- javascript進階教程第一章案例實戰JavaScript
- Rust 程式設計影片教程(進階)——013 使用 Rc 引用計數智慧指標Rust程式設計指標
- Rust 程式設計視訊教程(進階)——025_2 解引用裸指標Rust程式設計指標
- Web前端進階之JavaScript模組化程式設計知識Web前端JavaScript程式設計
- 一文詳盡系列之模型評估指標模型指標
- JVM之壓縮指標(CompressedOops)JVM指標OOP