—恢復內容開始—
一、this是什麼東東?
this是指包含它的函式作為方法被呼叫時所屬的物件。這句話理解起來跟卵一樣看不懂,但是如果你把它拆分開來變成這三句話後就好理解一點了。
1.包含它的函式
2.作為方法被呼叫時
3.所屬的物件
二、this的繫結規則
1、預設繫結
var x = 0; function num(){ this.x = 1; } console.log(this.x);//0 num(); console.log(this.x);//1
//當沒有使用let或者var時,宣告的變數是全域性變數,指向window,
//在這一形態下,其函式內部的this是與全域性作用域時一樣,直接指向window,執行完num()後,更改了x的值,所以其形態 依然等價於window。
function foo(){
console.log(this.a) } var a = 2; foo(); //2 console.log(this.document === document); // true // 在瀏覽器中,全域性物件為 window 物件: console.log(this === window); // true this.a = 3; console.log(window.a); // 3 this指向全域性物件。在非嚴格模式中,當funcion被不帶任何修飾的函式直接引用進行呼叫的,只能使用預設繫結,無法應用其他規則
2、隱式繫結
先看兩段例子
function foo(){ console.log(this.a) } var obj = { a:2, foo:foo } obj.foo() //2
//隱式繫結————呼叫位置使用obj上下文來引用函式,可以說函式被呼叫時obj物件“擁有”或者“包含”它,它就指向誰。
function foo(){ console.log(this.a) } var obj2 = { a:42, foo:foo } var obj1 = { a:2, obj2:obj2,
foo:foo }
obj1.foo() //2 obj1.obj2.foo() //42 //此時可以控制檯檢視obj1,obj2物件裡究竟包含了什麼
//當函式引用有上下文物件時,隱式繫結規則會把函式呼叫中的this繫結到這個上下文物件
//物件屬性引用鏈中只有最後一層在呼叫位置中起作用
下面思考這一段會輸出什麼呢?
function foo(){ console.log(this.a) } var obj = { a:2, foo:foo } var bar = obj.foo; //這裡bar將引用foo函式本身,所以不帶有函式物件的上下文,this會直接指向window
bar() //?
//為什麼沒有隱式繫結?這種情況稱為隱式丟失。
//因為bar=obj.foo 而obj.foo指向foo 也就是bar = function foo(){console.log(this.a)} foo中的this指向window,
//在window中並沒有對a進行定義,so,結果是undefined
接下來再看一段會是什麼結果呢?(引數傳遞時的隱式賦值)
function foo(){ console.log(this.a) } function doback(fn){ fn() } var obj = { a:2, foo:foo } var a = `global`; doback(obj.foo) //? 顯然答案是global,但是為什麼呢?請繼續往下看!
//隱式丟失--被隱式繫結的函式會丟失繫結物件然後應用預設繫結。
//最後函式執行 doback(obj.foo)時,會進行引數傳遞,也就是 fn = obj.foo,就和上一個例子一樣了。既this指向的是window。
3、顯示繫結
function foo(){ console.log(this.a) } var obj = { a:2 }
foo.call(obj) //2
//顯式繫結--第一個引數是一個物件,接著在呼叫函式時將其繫結到this,通過foo.call(obj),我們可以在呼叫foo時強制把他的this繫結到obj上
function foo(){ console.log(this.a) } var obj = { a:2 } var a = `3333333`; var bar = function(){ foo.call(obj) }
bar() // 2
bar.call(window) //2
// 硬繫結後bar無論怎麼呼叫,都不會影響foo函式的this繫結
// 通過建立函式bar(),並在內部呼叫foo.call(obj),強制把foo的this繫結到obj上。硬繫結的bar之後無論如何呼叫函式,都只會在obj上呼叫foo。
我們來看一下他的應用:
function foo(num) { console.log( this.a, num); return this.a + num; } var obj = { a: 2 }; var bar = function() { return foo.call( obj, ...arguments ); // 將obj物件硬編碼進去
//return foo.apply( obj, arguments ); // 也可以使用apply將obj物件硬編碼進去
}; var b = bar( 3 ); // 2 3 console.log( b ); // 5
function fn1(){ console.log(1); } function fn2(){ console.log(2); } fn1.call(fn2); //輸出 1 fn1.call.call(fn2); //輸出 2 這個暫時沒有搞懂,但是東鴿子大師中有講解,感興趣的小夥伴可以看看。
4、new繫結
我們不去深入瞭解建構函式,但要知道new來呼叫函式,或者說發生建構函式呼叫時,執行了哪些
當程式碼 new foo(…) 執行時:
(1) 建立一個新物件,它繼承自foo.prototype.
(2) 將建構函式的作用域賦給新物件(因此 this 就指向了這個新物件);new foo 等同於 new foo(),只能用在不傳遞任何引數的情況。
(3) 執行建構函式中的程式碼(為這個新物件新增屬性) ;
(4) 返回新物件, 那麼這個物件會取代整個new出來的結果。如果建構函式沒有返回物件,那麼new出來的結果為步驟1建立的物件。
function foo(a){ this.a = a; } var bar = new foo(2); //建立一個新物件bar,它繼承了foo.prototype. 也就是bar這個物件有a這個屬性,且傳進去的是2,使用new來呼叫foo(..)時,會構造一個新物件,並把它繫結到foo(..)呼叫中的this上
console.log(bar.a) //2
三、下面做一個小練習看看你學會了嗎?
例一:
function foo(a){ console.log(this.a) } var obj1 = { a:2, foo:foo } var obj2 = { a:3, foo:foo } obj1.foo() //? obj2.foo() //? obj1.foo.call(obj2) //? obj2.foo.call(obj1) //?
答案:
obj1.foo() //2 隱式繫結
obj2.foo() //3 隱式繫結
obj1.foo.call(obj2) //3 顯式繫結
obj2.foo.call(obj1) //2 顯式繫結
通過答案得出:顯示繫結 > 隱式繫結
顯示繫結優先順序更高,所以在判斷時 應當 優先考慮 是否 存在 顯示繫結
例二:
function foo(someting){ this.a = someting } var obj1 = { foo:foo } var obj2 = {}
obj1.foo(2)
var bar = new obj1.foo(4)
console.log(obj1.a)//? console.log(bar.a)//?
答案:
console.log(obj1.a) //2
console.log(bar.a) //4
通過答案得出: new繫結 > 隱式繫結
顯示繫結優先順序更高,所以在判斷時 應當 優先考慮 是否 存在 new繫結
需要注意的是:new和call/apply無法一起使用,因此無法通過new foo.call(obj)來直接測試
function foo(someting){ this.a = someting } var obj1 = {} var bar = foo.bind(obj1) //不知道bind()方法的同學可以直接點選此處檢視最騷的就是你同學貢獻的詳解。 bar(2) console.log(obj1.a)//? var baz = new bar(3) console.log(obj1.a)//? console.log(baz.a)//?
答案:2 2 3
通過答案得出:new繫結 > 顯示繫結
new修改了硬繫結呼叫bar(..)中的this,因為使用了new繫結,得到了一個名字為baz的新物件,並且baz.a的值為3.
所以 new繫結 > 顯示繫結 > 隱式繫結 > 預設繫結
在此特別鳴謝同事Jason大哥的share!!!
—恢復內容結束—