本文參考了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
指向window
,window.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
引用obj
的foo
屬性,即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
call
和apply
是一樣的,都是將foo
的this
指向obj
,再去指向foo
的方法,區別在於apply
的第二個參數是arguments
對象或者一個數組,call
可以傳遞多個參數
顯式調用----硬綁定
bar
函數內部將foo
的this
綁定到obj
對象上,之後無論如何調用bar
函數,foo
的this
總是指向到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、call
、apply
綁定後立刻執行,bind
是延遲執行。換言之,當你希望改變上下文環境之後並非立即執行,而是回調執行的時候,就使用bind()
方法
4、new
綁定
使用
new
來調用foo()
時,會構造一個新對象,並把它綁定到foo()
調用中的this上
function foo() {
this.f1 = 'test';
}
var b = new foo();
console.log(b.f1); // test