入坑----關於this調用位置和指向問題

本文參考了https://segmentfault.com/a/1190000009393621、https://blog.csdn.net/joyvonlee/article/details/94360946兩篇文章,大家也都可以去看看

一、調用位置

如何查看函數調用的位置

可以使用debugger來查看函數的調用棧以及this指向

function foo(a){
    debugger
    console.log(this,a);
}
foo(1);
var F=foo(2);
var F1=new foo(3);

在這裏插入圖片描述
在這裏插入圖片描述

二、this四種綁定規則

1、默認綁定

默認綁定指函數沒有任何引用進行調用的情況下運行

function test(){
    console.log(this.a,this.b);
}
let b=0;
var a = 1;
test();

在這裏插入圖片描述
test()函數是直接運行的,調用位置的作用域是全局作用域,所以this指向了window,所以this.a=1,
但是…this.b=undefined??? 這裏需要注意,這是一個坑!!!,對於關鍵字let,此刻變量依然是全局變量,作用於全局,但是不再作爲全局對象的屬性存在了,這裏需要和var區分一下!

這裏附一道面試題~~~

let length = 10;
function fn() {
	console.log(this.length);
}
 
var obj = {
	length: 5,
	method: function(fn) {
		fn();	
		arguments[0]();		
		
	}
}
 
obj.method(fn, 1);//0,2

首先來看代碼執行,obj.method(fn, 1);
obj對象裏邊的method是一個函數,它裏邊的fn()執行的時候,應用了this默認綁定規則,所以this指向windowwindow.length是等於0(length對象使用let關鍵字聲明的,它不屬於全局window對象的一個屬性,所以window.length 和它(let length = 10)是兩個不同的對象。這是一個坑!),所以執行這行語句的時候會輸出 0 ,接下來是執行arguments[0](); arguments對象是一個類數組,它的第0位下標是實參列表的第1個參數,也就是fn函數。
當這個fn函數調用的時候,它的this被綁定到arguments對象上。因爲obj.method傳入了兩個參數,所以arguments對象的length屬性爲2

2、隱式綁定

隱式綁定指一個函數,被當作引用屬性添加到了一個對象中,然後以 “對象名.函數名()” 形式進行調用,這時如果函數引用有上下文對象,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。

function test(){
    console.log(this.a);
}
var obj = {
    a:3,
    test:test
};
obj.test();//3

在這裏插入圖片描述
隱式綁定----鏈式調用

對象屬性引用鏈中,只有上一層或者說最後一層在調用位置中起作用

 function test(){
     console.log(this.a);
 }
 var obj2 = {
     a:4,
     test:test
 };
 var obj1 = {
     a:400,
     obj2:obj2
 }
 obj1.obj2.test()//4

在這裏插入圖片描述
隱式綁定----隱式丟失

原本應用隱式綁定的,因爲丟失綁定對象,變回應用默認綁定了!把this綁定到全局對象或undefined
當對象將其引用屬性給了新的引用,再次調用這個新的引用時,原本this指向的對象就會改爲指向window

function foo(){
     console.log(this.a);
 }
 var obj = {
     a:5,
     foo:foo
 };
 var bar = obj.foo; //這句就是關鍵 bar是obj的引用屬性foo
 var a = "oops, global";
 bar(); // "oops, global"

bar引用objfoo屬性,即foo()函數, var bar = obj.foo;就等價於 var bar=foo();,當bar函數調用的時候,由於是在全局作用域下調用,所以它的this指向了全局window,所以最後輸出了"oops, global"

3、顯式調用

如果不想在對象內部包含函數引用,而想在某個對象強制調用一個函數,這時就用到了函數的call()apply()方法。


function foo() {
    console.log(this.a)
}
 
var obj = {
    a: 10
}
 
foo();  // undefined
foo.call(obj);  // 10

callapply是一樣的,都是將foothis指向obj,再去指向foo的方法,區別在於apply的第二個參數是arguments對象或者一個數組,call可以傳遞多個參數

顯式調用----硬綁定

bar函數內部將foothis綁定到obj對象上,之後無論如何調用bar函數,foothis總是指向到obj對象的,這種綁定是一種顯式的強制綁定。

第一種方式

function foo() {
    console.log(this.a)
}
 
var obj = {
    a: 10,
    foo: foo
}
 
var bar = function() {
    foo.call(obj);
}
 
var test = bar;
 
test();     // 10
test.call(window);  // 10

第二種方式
硬綁定----bind()綁定


function foo(arg) {
    console.log(this.a, arg)
    return this.a + arg;
}
 
// 輔助函數
function bind(fn, obj) {
    return function() {
        return fn.apply(obj, arguments)
    }
}
 
var obj = {
    a: 2
}
 
var bar = bind(foo, obj);
var b = bar(3);     // 2 3
console.log(b)      // 5

這種實現方式就是ES5的內置方法Function.prototype.bind的實現原理,下面是內置函數bind的用法

function foo(arg) {
    console.log(this.a, arg)
    return this.a + arg;
}
 
var obj = {
    a: 2
}
 
var bar = foo.bind(obj);
var b = bar(3);     // 2 3
console.log(b)      // 5

硬綁定bind()延遲調用

function test(){
     console.log(this.a);
 }
 var obj2 = {
     a:4,
     test:test
 };
 var obj1 = {
     a:400,
     obj2:obj2
 }
var bar=obj2.test.bind(obj2)//4
bar();
var bar1=obj2.test.call(obj2)//報錯
bar1();
var bar2=obj2.test.apply(obj2)//報錯
bar2();

在這裏插入圖片描述在這裏插入圖片描述
在這裏插入圖片描述
爲什麼後兩個會報錯?
因爲call和apply都是綁定後立刻執行的,都執行完了,bar就只是一個沒有賦值的變量而已

關於apply、call、bind
共同點:
1、都用於控制this指向;
2、第一個參數都是this需要指向的對象,也就是上下文;
3、都可以後續參數傳遞;
4、沒有任何參數時,this都指向全局對象window
區別:
1、callapply綁定後立刻執行,bind是延遲執行。換言之,當你希望改變上下文環境之後並非立即執行,而是回調執行的時候,就使用bind()方法

4、new綁定

使用new來調用foo()時,會構造一個新對象,並把它綁定到foo()調用中的this上


function foo() {
    this.f1 = 'test';
}
 
var b = new foo();
console.log(b.f1);  // test

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章