Javascript的This機制

先簡單說兩句

相信很多朋友在剛學習Javascript都被Javascriptthis弄得很混亂,由於我一開始就是學習Java先的,很自然就Java中的this代入了Javascript中,其實這是一個重大的錯誤,有人()說「如果上帝用七天打造這個世界,那麼Javascript就是在最後0.01秒才匆匆設計的」,現在就讓我跟大家詳細說說Javascriptthis吧!

this的意義

Java中,this是一個引用對象,引用的就是這個對象自身,在Javascript中也會有這種場景,如以下代碼的this便指向new出來的那個對象自身

1
2
3
4
5
6
7
8
9
10
11
12
13
export class People{
constructor(name, age){
this.name = name;
this.age = age;
}

sayHi(){
return `Hi, My name is ${this.name} and I am ${this.age} old`;
}
}

let patrick = new People("Patrick", 21);
console.log(patrick.sayHi()); // Hi, My name is Patrick and I am 21 old

又或者在使用Javascript的對象時,this也是指向car對象本身,但這種情況上面跟通過new關鍵字又有一些不一樣。

1
2
3
4
5
6
const car = {
speed: "100km/h",
introduce: function(){
console.log(`I can run ${this.speed}`);
}
}

在我現在的工作中,經過需要使用到React,一般在Reactconstructor中需要做以下這件事,就是需要bind綁定,為甚麼需要呢,不綁定會怎樣?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export class Input extends React.Component{
constructor(props){
super(props);
// here
this.handleChange = this.handleChange.bind(this);

this.state = {value: ""};
}

handleChange(value){
this.setState({value: value});
}

render(){
return (
<input value={this.state.value} onChange={this.onChange}/>
)
}
}

還有一種更簡單的情況,在瀏覽器打開chrome devTools,在console面版中輸入this,看看會出現甚麼東西。為甚麼會輸出window呢?

接著我跟大家解釋釋,究竟this的意義是甚麼,其實this是指運行期間的上下文,根據不同的情況有不同的綁定規則。

this的綁定規則

默認綁定

默認綁定就是最簡單的情況,在全域或者不在函數內部使this的時候,this指向的便全局變量,在瀏覽器中是window,在nodejs中便是global,默認綁定會在嚴格下失效,即嚴格模式,默認綁定的this會指向undefined。上面第四個例子便是默認綁定。

隱式綁定

當函數被調用時,若函數引用具有上下文對象,則隱式綁定會將 this 綁定到這個上下文對象。第二個例子說的就是隱式綁定,在this的綁定規則裡,最容易出錯的就是隱式綁定,來看一段代碼,最後的那兩個函數會分別返回asurprise,這種情況叫做隱式丟失,丟失的就是thisbarobj.foo的一個函數別名,在調用bar的時候會丟失對objthis引用,而直接是綁定到全局的對象中。

1
2
3
4
5
6
7
8
9
10
11
12
// 非嚴格模式
this.a = "surprise"
const obj = {
a: "a",
foo: function(){
return this.a;
}
}

const bar = obj.foo;
obj.foo();
bar();
顯式綁定

顯式綁定就要為了避免隱式丟失的出現,通過applycallbind三個方法來顯式的綁定要調用函數內部的this,看看例子最直接,在例子就直接指定this的值,這樣可以避免出現丟失的問題,所以現在為甚麼第三個例子中React constructor需要用bind綁定一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const obj1 = {
content: "i am object 1"
};
const obj2 = {
content: "i am object 2"
};
const obj3 = {
content: "i am object 3"
}
function foo(x, y ,z){
return `${this.content} arg ${x} ${y} ${z}`;
}

// use apply
foo.apply(obj1, ["a", "a", "a"]); // i am object 1 arg a a a

// use call
foo.call(obj2, "b", "b", "b"); // i am object 2 arg b b b

// use bind
const bindedFoo = foo.bind(obj3); // i am object 3 arg c c c
bindedFoo("c", "c", "c");

new綁定

第一個例子中,我們通過一個new關鍵字「實例化」了一個對象,實際上的new過程只做了下面幾件事:

  1. 創建一個新的對象
  2. 將新的對象的__prototype__屬性指向函數的prototype
  3. 將函數的this綁定向新的對象
  4. 調用函數

注意第三步,這裡就是實際上new綁定出現的原因,因為在new關鍵字的執行過程已經把新創建的對象綁定到函數的this裡,所以在new出來的對象裡面的this在運行時是會指向這個對象的。

優先級

依次為new綁定顯式綁定隱式綁定默認綁定

Javascript中的this機制其實還有一層,就是thisprototype原型鏈上的查找,可以理解this是一個二維平面,水平方向是綁定規則,垂直方向就是prototype原型鏈和JS的各種作用域。

下期將為大家介紹Javascript的原型鏈和作用域。

本人獻醜了,大家加油~!

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×