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調用函數時,具體都做了什麼:

  1. 創建一個全新的對象
  2. 這個新對象會被執行【原型】連接
  3. 這個新對象會綁定到函數調用的this
  4. 如果函數沒有返回其他對象,那麼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