1. 程式人生 > >JS中的this原理詳解

JS中的this原理詳解

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>