JS之this的指向理解
1.this是什麼?
this是物件自動生成的一個內部物件,是在執行時基於函式的執行環境繫結的,因為函式的呼叫場合不同,this的值也有變化。
this指向什麼,完全取決於 什麼地方以什麼方式呼叫,而不是 建立時 。這句話目前也只能說在ES5中才是正確的,而在ES6的箭頭函式中,this的指向就是在定義的時候就確定的。
2. this的繫結規則
this的繫結總共差不多有下面五種:
- 預設繫結
- 隱式繫結
- 顯示繫結
- new繫結
- ES6箭頭函式中的this
2.1.預設繫結
function foo(){ console.log(this.a); // 10 } var a = 10; foo();
作為獨立函式的呼叫,this指向的是全域性物件window,而在嚴格模式下,不能將全域性物件用於預設繫結,this會繫結到undefined。只有函式執行在非嚴格模式下,預設繫結才能繫結到全域性物件。
2.2.隱式繫結
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
foo(); //undefined
obj.foo(); // 2
foo() 這個就是預設繫結,等價於列印 window.a ,故輸出 undefined ;
obj.foo(),函式有上下文物件,即obj,這種情況下, 函式裡的this預設繫結為上下文物件 ,等價於列印 obj.a ,故輸出2 。如果是鏈性的關係,比如 xx.yy.obj.foo(); , 上下文取函式的直接上級,即緊挨著的那個,或者說物件鏈的最後一個。
再看下一個情況:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var bar = obj.foo; // 函式別名
var a =,10; // a是全域性物件的屬性
bar(); // 10
雖然bar為obj.foo的引用,但實際上引用的是foo函式本身,此時this指向為預設繫結,指向的window,所以輸出10
2.3.顯示繫結
顯示繫結就是call apply bind,call和apply它們的作用都是改變函式的this指向 , 第一個引數都是 設定this物件 。
兩個函式的區別:
call從第二個引數開始所有的引數都是 原函式的引數。
apply只接受兩個引數,且第二個引數必須是陣列,這個陣列代表原函式的引數列表。
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2 呼叫foo時強制把foo的this繫結到obj上
bind只有一個函式,且不會立刻執行,只是將一個值繫結到函式的this上,並將繫結好的函式返回。
function foo(){
console.log(this.a);
}
var obj = { a : 10 };
foo = foo.bind(obj);
foo(); // 10
2.4.new繫結
js中的只要用new修飾的 函式就是’建構函式’,準確來說是 函式的 構造呼叫 ,因為在js中並不存在所謂的’建構函式’。
那麼用new 做到函式的 構造呼叫 後,js幫我們做了什麼工作呢:
function createNew()
let obj = new Object() // 建立一個空的物件
let Con = [].shift.call(arguments) // 獲得建構函式
obj.__proto__ = Con.prototype // 連結到原型
let result = Con.apply(obj, arguments) // 繫結 this,執行建構函式
return typeof result === 'object' ? result : obj // 確保 new 出來的是個物件
}
1、建立(或者說構造)一個新物件。
2、這個新物件會被執行[[Prototype]]連線。
3、這個新物件會繫結到函式呼叫的this。
4、如果函式沒有返回其他物件,那麼new表示式中的函式呼叫會自動返回這個新物件。
function foo(){
this.a = 10;
console.log(this);
}
foo(); // window物件
console.log(window.a); // 10 預設繫結
var obj = new foo(); //// 等價於 foo { a : 10 }; var obj = foo;
console.log(obj.a); // 10 new繫結
特別注意 : 如果原函式返回一個物件型別,那麼將無法返回新物件,你將丟失繫結this的新物件,例:
function foo(){
this.a = 10;
return new String("啊哈哈");
}
var obj = new foo();
console.log(obj.a); // undefined
console.log(obj); // "啊哈哈"
2.5 ES6箭頭函式中的this
foo()內部建立的箭頭函式會捕獲呼叫時foo()的this。由於foo()的this繫結到obj1,bar(引用箭頭函式)的this也會繫結到obj1,箭頭函式的繫結無法被修改(new也不行)。
function foo() {
return (a) => { //返回一個箭頭函式
console.log( this.a ); // this繼承自foo()
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
}
var bar = foo.call( obj1 );
bar.call( obj2 ); // 2,不是3!
3. 優先順序
new 繫結 > 顯示繫結 > 隱式繫結 > 預設繫結
4. 總結
1.如果函式是在某個 上下文物件 下被呼叫:this繫結的是那個上下文物件,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj
2.如果函式是使用 call,apply,bind 來呼叫的:this繫結的是 call,apply,bind 的第一個引數.例: foo.call(obj); , foo 中的 this 就是 obj
3.如果函式被 new 修飾:this繫結的是新建立的物件,例:var bar = new foo(); 函式 foo 中的 this 就是一個叫foo的新建立的物件 , 然後將這個物件賦給bar
4.如果都不是,則是預設繫結
5.常見題目分析
var x = 10;
var obj = {
x: 20,
f: function(){
console.log(this.x); // 20
//隱式繫結,this指向上下文obj
var foo = function(){
console.log(this.x);
}
foo(); //10 預設繫結 指向的是window物件
}
};
obj.f();
function foo(arg){
this.a = arg;
return this
};
var a = foo(1);
var b = foo(10);
console.log(a.a); // ?undefined
console.log(b.a); // ?10
分析:foo(1)執行,應該不難看出是預設繫結吧 , this指向了window,函式裡等價於 window . a = 1,return window;
var a = foo(1) 等價於 window . a = window , 很多人都忽略了 var a 就是window.a ,將剛剛賦值的 1 替換掉了。
所以這裡的 a 的值是 window , a . a 也是window , 即window . a = window ; window . a . a = window;
foo(10) 和第一次一樣,都是預設繫結,這個時候, 將window.a 賦值成 10 ,注意這裡是關鍵,原來window.a = window ,現在被賦值成了10,變成了值型別,所以現在 a.a = undefined。(驗證這一點只需要將var b = foo(10);刪掉,這裡的 a.a 還是window)
var b = foo(10); 等價於 window.b = window;
本題中所有變數的值,a = window.a = 10 , a.a = undefined , b = window , b.a = window.a = 10;
var num = 1;
var myObject = {
num: 2,
add: function() {
this.num = 3;
(function() {
console.log(this.num); //預設繫結 指向的window 輸出1
this.num = 4;
})();
console.log(this.num);
},
sub: function() {
console.log(this.num)
}
}
myObject.add();
console.log(myObject.num);
console.log(num);
var sub = myObject.sub;
sub();
依次輸出 1 3 3 4 4