this怎麼那麼難呢?(接上篇-1)
- 2019 年 12 月 31 日
- 筆記
如果沒有能力做選擇,那就只能被選擇~
2019年12月22日,星期六。距離新年還有9天了,寶寶們可以提前想想2020年的目標了。也可以把目標留言在文章下方,目標說給別人聽能起到督促自己的作用,不信你就試試!

接上篇this的綁定方式,第一種是默認綁定,也就是獨立函數的調用,這種情況就看作是無法應用其他規則的默認綁定。
第二種是隱式綁定,我們接着看:
隱式綁定
調用位置是否有上下文,或者說被某個對象擁有或者包含。思考下面代碼:
function foo(){ console.log(this.a); } var obj = { a :2, foo: foo } obj.foo(); // 2
以上,首先需要注意的是foo()的聲明方式,然後是如何被當做引用屬性添加到obj中的。但是無論是直接在obj中定義還是先定義再添加為引用屬性,這個函數嚴格來說都不屬於obj對象。
然而,調用位置會使用obj上線文來引用函數,因此可以說函數調用時obj對象「擁有」或「包含」它。
當函數引用有上下文對象時,隱式綁定規則會把函數調用中的this綁定到這個上下文對象。因調用foo()時this被綁定到obj,因此this.a和obj.a是一樣的。
對象引用鏈中只有最頂層或者最後一層會影響調用位置。
function foo(){ console.log(this.a) } var obj2={ a:42, foo: foo } var obj1={ a: 2, obj2: obj2 } obj1.obj2.foo() // 42
在我看來foo被調用的位置 實際上還是obj2中,所以this指向了obj2。
隱式丟失
function foo(){ console.log(this.a) } var obj = { a :2, foo: foo } var bar = obj.foo; // 給將函數foo賦值給bar var a = "000000" bar(); // "000000" 這裡才是foo真正調用的地方。是默認綁定
function foo(){ console.log(this.a); } function doFoo(fn){ fn() // 被調用的位置 } var obj ={ a :2, foo: foo } var a = "123" doFoo(obj.foo); // "123"
把函數傳入語言內置的函數而不是傳入自己聲明的函數,結果是一樣的:
function foo(){ console.log(this.a) } var obj = { a:2, foo: foo } var a = "hello" setTimeout(obj.foo, 100); // "hello"
js環境內置的setTimeout()函數實際和下面的偽代碼類似:
function setTimeout(fn,delay){ // 等待delay毫秒 fn(); // 真正調用位置 }
顯式綁定
使用call和apply;他們的第一個參數是一個對象,他們會把這個對象綁定到this,接着調用函數時指定這個this。
function foo(){ console.log(this.a) } var obj = { a: 2 } foo.call(obj); //2
如果傳入的第一個參數是原始值(string、boolean、number)來當做this的綁定對象,這個原始值會被轉換成它的對象形式(也就是new String(…)、new Boolean()、new Number())。這通常被稱作「裝箱」。
function foo(){ console.log(this.a); } car obj = { a : 2 } var bar = function (){ foo.call(obj) } bar(); // 2 setTimeout(bar,100) // 2 bar.call(window); // 2 硬綁定的bar不可能再修改它的this
以上,創建了函數bar,並在他的內部手動調用了doo.call(obj),因此強制把foo的shis綁定到了obj。無論之後如何調用函數bar,它總會手動在obj上調用foo。這種綁定是一種顯示的強制綁定,即硬綁定。
硬綁定用法1:創建一個包裹函數,傳入所有的參數並返回接收到的所有值:
function foo(something){ console.log(this.a,something); return this.a + something; } var obj = { a :2 } var bar = functiong(){ return foo.apply(obj, arguments); } var b = bar(3) // 2 3 console.log(b) // 5
硬綁定用法2:創建一個i可以重複使用的輔助函數:
function foo(something){ console.log(this.a, something); return this.a + something } functiong 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,用法如下:
function foo(something){ console.log(this.a, something); return this.a + something } var obj = { a :2 } var bar = foo.bind(obj); var b = bar(3); // 2 3 console.log(b) // 5
bind(…)會返回一個硬編碼的新函數,他會把參數設置為this的上下並調用原始函數。
new綁定
這是第四條也是最後一條this的綁定規則。
在JS中,構造函數只是一些使用new操作符時被調用的函數。他們並不會屬於某個類,也不會實例化一個類。實際上,他們甚至都不會說是一種特殊的函數類型,他們只是被new操作符調用的普通函數而已。
實際上並不存在所謂的「構造函數」,只有對於函數的「構造調用」。
使用new調用函數時,具體都做了什麼:
- 創建一個全新的對象
- 這個新對象會被執行【原型】連接
- 這個新對象會綁定到函數調用的this
- 如果函數沒有返回其他對象,那麼new表達式中的函數調用會自動返回這個新對象
優先級
默認綁定的優先級是四條規則中最低的。
我們來看看隱式綁定和顯示綁定哪個優先級更高:
function foo() { console.log( this.a ); } var obj1 = { a: 2, foo: foo }; var obj2 = { a: 3, foo: foo }; obj1.foo(); // 2 obj2.foo(); // 3 obj1.foo.call( obj2 ); // 3 obj2.foo.call( obj1 ); // 2
so , 顯示綁定的優先級高於隱式綁定
看看new和隱式綁定的優先級:
function foo(something) { this.a = something; } var obj1 = { foo: foo }; var obj2 = {}; obj1.foo( 2 ); console.log( obj1.a ); // 2 obj1.foo.call( obj2, 3 ); console.log( obj2.a ); // 3 var bar = new obj1.foo( 4 ); console.log( obj1.a ); // 2 console.log( bar.a ); // 4
so, new綁定的優先級高於隱式綁定。但是new 和顯示綁定的優先級誰更高呢?
new和call/apply無法一起使用,因此無法進行直接測試。我們用bind進行測試:
function foo(something) { this.a = something; } var obj1 = {}; var bar = foo.bind( obj1 ); bar( 2 ); console.log( obj1.a ); // 2 var baz = new bar(3); console.log( obj1.a ); // 2 console.log( baz.a ); // 3
以上,new 修改了硬綁定調用bar中的this.我們得到了一個新對象,並且baz.a的值是3.
也就是說優先級為:new > call/apply > 隱式綁定> 默認綁定
願我們有能力不向生活繳械投降—Lin
