JS中的this原理詳解
阿新 • • 發佈:2019-01-25
this的用法
- 需要關注的是,不同調用位置上的呼叫形式,決定了this的指向;
獨立呼叫
-
預設繫結規則:this繫結給window;
-
在嚴格模式下,預設繫結規則會把this繫結undefined上;
function foo() { console.log( this.a ); } var a = 2; (function(){ "use strict"; foo(); //2 })();
-
這裡有一個微妙但是非常重要的細節,雖然 this 的繫結規則完全取決於呼叫位置,但是隻有 foo()執行在非 strict mode 下時,預設繫結才能繫結到全域性物件; 嚴格模式下呼叫foo()不會影響預設繫結規則;
function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); //undefined
-
無論函式是在哪個作用域中被呼叫,只要是獨立呼叫則就會按預設繫結規則被繫結到全域性物件或者undefined上
隱式呼叫:(使用物件的屬性呼叫)
隱式繫結
- 隱式繫結的規則:this給離函式最近的物件;是呼叫位置是否有上下文物件,或者說是否被某個物件擁有或者包含;
// 隱式繫結的規則是呼叫位置是否有上下文物件,或者說是否被某個物件擁有或者包含 // 當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的 this 繫結到這個上下文物件 function foo() { console.log( this.a );//2 } var obj = { a: 2, foo: foo }; obj.foo();
- 當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的 this 繫結到這個上下文物件;
- 物件屬性引用鏈中只有最頂層或者說最後一層會影響呼叫位置;
//物件屬性引用鏈中只有最頂層或者說最後一層會影響呼叫位置
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); //42
隱式丟失
- 將函式通過隱式呼叫的形式賦值給一個變數;
function foo() { console.log( this.a );//oops, global } var a = "oops, global"; var obj = { a: 2, foo: foo }; var bar = obj.foo; //把obj.foo賦予別名bar,造成了隱式丟失,因為只是把foo()函式賦給了bar,而bar與obj物件則毫無關係 bar(); //等價於 var a = "oops, global"; var bar = function foo(){ console.log( this.a ); } bar();//oops, global
- 將函式通過隱式呼叫的形式進行傳參;
var a = 0;
function foo(){
console.log(this.a);
};
function bar(fn){
fn();
}
var obj = {
a : 2,
foo:foo
}
//把obj.foo當作引數傳遞給bar函式時,有隱式的函式賦值fn=obj.foo。與上例類似,只是把foo函式賦給了fn,而fn與obj物件則毫無關係。
bar(obj.foo);//0
//等價於
var a = 0;
function bar(fn){
fn();
}
bar(function foo(){
console.log(this.a);
});
- 內建函式:內建函式與上例類似,也會造成隱式丟失
var a = 0;
function foo(){
console.log(this.a);
};
var obj = {
a : 2,
foo:foo
}
setTimeout(obj.foo,100);//0
//等價於
var a = 0;
setTimeout(function foo(){
console.log(this.a);
},100);//0
顯式繫結
- 通過call()、apply()、bind()方法把物件繫結到this上,叫做顯式繫結。
//普通物件的屬性查詢
function foo(a,b) {
console.log( this.a,a,b );
}
var obj = {
a:2
};
foo.call( obj,"a","b"); //2 a b
foo.apply(obj,["a","b"])//2 a b
- 顯式繫結規則:
- 硬繫結:硬繫結是顯式繫結的一個變種,使this不能再被修改。
// 我們來看看這個顯式繫結變種到底是怎樣工作的。我們建立了函式 bar() ,並在它的內部手動呼叫了 foo.call(obj) ,因此強制把 foo 的 this 繫結到了 obj 。無論之後如何呼叫函式 bar ,它總會手動在 obj 上呼叫 foo 。這種繫結是一種顯式的強制繫結,因此我們稱之為硬繫結。
function foo() {
console.log( this.a );/
}
var a =1;
var obj = {a:2};
var obj_test = {a:"test"};
var bar = function() {
console.log( this.a );
foo.call( obj );};
bar(); // 1 2
setTimeout( bar, 1000 ); // 1 2
bar.call( obj_test ); //test 2
//硬繫結的bar不可能再修改它的this(指的是foo中的this)
// 硬繫結的典型應用場景就是建立一個包裹函式,傳入所有的引數並返回接收到的所有值
function foo(arg1,arg2) {
console.log( this.a,arg1,arg2);
return this.a + arg1;
}
var obj = {a:2};
var bar = function() {
return foo.apply( obj, arguments);
};
var b = bar(3,2); // 2 3 2
console.log( b ); // 5
new繫結
//3. 這個新物件會繫結到函式呼叫的 this 。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
//使用 new 來呼叫 foo(..) 時,我們會構造一個新物件並把它繫結到 foo(..) 呼叫中的 this 上。 new 是最
//後一種可以影響函式呼叫時 this 繫結行為的方法,我們稱之為 new 繫結。
繫結例外
- 箭頭函式:this的繫結和作用域有關。如果在當前的箭頭函式作用域中找不到變數,就像上一級作用域裡去找。
function foo() {
setTimeout(() => {
console.log('id:', this.id); //id: 42
}, 100);
}
var id = 21;
foo.call({ id: 42 })
- 被忽略的this:當被繫結的是null,則使用的是預設繫結規則;
// 如果你把 null 或者 undefined 作為 this 的繫結物件傳入 call 、 apply 或者 bind ,這些值在呼叫時會被忽略,實際應用的是預設繫結規則;
function foo() {
console.log( this.a );
}
var a = 2222;
foo.call( null ); // 2
- 柯里化
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// 把陣列“展開”成引數
foo.apply( null, [2, 3] ); // a:2, b:3
// 使用 bind(..) 進行柯里化
var bar = foo.bind( null, [2] );
bar( 3 ); // a:2, b:3
//升級版:不會汙染全域性
function foo(a,b) {
this
console.log( "a:" + a + ", b:" + b );
}
// 我們的DMZ空物件,“DMZ”(demilitarized zone,非軍事區)
var ø = Object.create( null );//{}
// 把陣列展開成引數
foo.apply( ø, [2, 3] ); // a:2, b:3
// 使用bind(..)進行柯里化
var bar = foo.bind( ø, 2 );
bar( 3 ); // a:2, b:3
總結
-
this是函式執行的上下文物件;
-
根據函式呼叫的方式不同this的值也不同:
- 1.以函式的形式呼叫,this是window
- 2.以方法的形式呼叫,this是呼叫方法的物件
- 3.以建構函式的形式呼叫,this是新建立的那個物件
- 4.使用call和apply呼叫的函式,第一個引數就是this
- 5.在全域性作用域中,this是window
- 6.在響應函式中,給誰繫結的事件this就是誰。
-
在事件處理函式中:獲取觸發當前事件的元素;
-
在普通函式中(直接呼叫):獲取的是window物件;
-
作為物件的方法中:獲取的是當前物件。
-
在全域性作用域中:this指代window物件。
-
建構函式中的this:指代建立的物件。
-
注意:
- 當呼叫方式不同時,this指代的含義不同,得到的結果也不同。
- this本身不具備任何含義。
總結程式碼示例
<script>
// TODO 一、以全域性&呼叫普通的函式的形式呼叫,this是window.
function fn(){
console.log(this);
}
fn();
//二、建構函式
//如果函式作為建構函式使用,那麼其中的this就代表即將new出來的物件
function Objfn(){
this.a = 10;
console.log(this);//此時輸出的是物件 Objfn {a: 10}
}
var objfn = new Objfn();
console.log('objfn.a='+objfn.a);//objfn.a=10
//但是如果直接呼叫Objfn1()函式,而不是new Objfn1(),那麼情況就變成了Objfn()是普通函式
function Objfn1(){
this.a = 10;
console.log(this);//此時輸出的是物件 Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}
}
var objfn1 = Objfn1();
//console.log('objfn.a='+objfn1.a);//錯誤,Cannot read property 'a' of undefined
//三、物件方法
//如果函式作為物件的方法,方法中的this指向該物件
var obj={
a:10,
foo:function () {
console.log(this);//Object {a: 10, foo: function}
console.log(this.a);//10
}
}
obj.foo();
//注意,要是此時在物件方法中定義函式,那麼情況就不同了
//此時的函式fn雖然是在 obj1.foo1內部定義的,但它仍然屬於一個普通的函式,this仍然指向window.
var obj1={
a1:10,
foo1:function () {
function fn(){
console.log(this);//Window {stop: function, open: function, alert: function, confirm: function, prompt: function…}
console.log(this.a1);//undefined
}
fn();
}
}
obj1.foo1();
//另外,如果此時foo2不作為物件方法被呼叫,則
var obj2 = {
x: 10,
foo2: function () {
console.log(this); //Window
console.log(this.x); //undefined
}
};
var fn2 = obj2.foo2;
//等價於fn2 = function () {
//console.log(this); //Window
//console.log(this.x); //undefined
//}
fn2();//此時又是在全局裡執行的普通函式。
//四、建構函式的prototype屬性
//在 Foof.prototype.getX函式中,this 指向的 Foof物件。不僅僅如此,即便是在整個原型鏈中,this 代表的也是當前Foof物件的值。
function Foof(){
this.x = 10;
}
Foof.prototype.getX = function () {
console.log(this); //Foof {x: 10}
console.log(this.x); //10
}
var foof = new Foof();
foof.getX();
//五、函式用call、apply或者bind呼叫
var obja = {
x: 10
}
function fooa(){
console.log(this); //Object {x: 10}
console.log(this.x); //10
}
fooa.call(obja);
fooa.apply(obja);
fooa.bind(obja)();
</script>