《You Don't Know JS》閱讀理解——this

1. this的誕生

假設咱們有一個speak函數,經過this的運行機制,當使用不一樣的方法調用它時,咱們能夠靈活的輸出不一樣的name。chrome

var me = {name: "me"};

function speak() {
  console.log(this.name);
}

speak.call(me) //me

可是若是沒有this, 這時咱們須要顯示的傳遞上下文給該函數。這時必須硬性的指定上下文,代碼的複雜度增長,靈活性也欠缺。app

function speak(context) {
  console.log(context.name);
}

2. this的運行機制

2.1 運行原理

When a function is invoked, an activation record, otherwise known as an execution context, is created. This record contains information about where the function was called from (the call-stack), how the function was invoked, what parameters were passed, etc. One of the properties of this record is the this reference which will be used for the duration of that function's execution.函數

當函數被調用時, 函數會建立一個activation object(執行上下文), 這個對象包括了函數在哪裏被調用(調用棧),函數的調用方式,傳入的參數,以及this值。this

所以,咱們能夠看到,this值是在函數調用時賦值的,而不是在聲明的時候。是動態的。spa

2.2 運行規則

根據this的運做原理,咱們能夠看到,this的值和調用棧(經過哪些函數的調用運行到調用當前函數的過程)以及如何被調用有關。prototype

2.2.1 Default Binding(默認綁定)

當函數是被獨立調用時,this值在非嚴格模式下爲全局對象, 嚴格模式下爲undefined.debug

var a = 1;
function foo() {
  var a = 2;
  console.log(this.a);
}

function bar() {
  debuuger;
  foo();
}

bar();

打開chrome devtool能夠看到,在調用foo時,函數的調用棧爲bar -> foo,調用方式是獨立調用,且是在非嚴格模式下,此時this值指向window,輸出1。code

圖片描述

2.2.2 Implicit Binding(隱式綁定)

var = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
}

obj.foo(); //2

此時,調用foo時,函數前加上了對obj這個對象的引用,輸出obj.aorm

所以,若是有上下文對象引用了函數,隱式綁定規則會指定this值爲該引用對象。對象

圖片描述

可是咱們再看看下面這種狀況。要注意的是,bar的值是對函數foo的引用,所以此時foo的調用並無上下文對象的引用,所以應用的是default binding, 輸出1。要注意這種賦值的狀況。

var a = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
}

var bar = obj.foo;

bar(); //1

2.2.3 Explicit Binding(顯式綁定)

上面兩種狀況,要麼this值爲全局對象(非嚴格模式),要麼經過對象方法調用,this指向調用的對象。
那我想不經過對象調用,而是獨立調用時又能指定this值爲某個對象呢?這時,call,apply就誕生了。它的第一個參數是this值,幫助咱們明確指定函數調用時this的值。

var a = 1;
function foo() {
  debugger;
  console.log(this.a);
}
var obj = {
  a: 2
}

foo.call(obj); //2

圖片描述

經過call, apply,咱們能夠在調用時明確指定this值。還有一種狀況是,有時候咱們但願this值綁定在咱們給定的對象上,而函數只須要接受一些參數。特別是在第三方庫中,它會提供一種方法,接收方法須要的參數,可是不但願你意外的修改了方法的this值,這時它可能會採用bind這種硬性綁定的方法明確的指出this值。

在ES5中提供了Function.prototype.bind,它的應用場景就是幫助你predicable的綁定this值。經常使用的應用場景爲包裹函數、事件綁定函數、setTimeout中綁定this

//包裹函數,用來接受參數
function multiple(num) {
  console.log(this.pen, num);
  return this.pen * num;
}

var priceMapping = {
  pen: 10
}

function calTotalPrices() {
  return multiple.apply(priceMapping, arguments);
}

var total = calTotalPrices(3);
console.log(total); //30
//事件綁定
var states = {
  clickCount: 0
}
function clickHandler() {
  this.clickCount++;
  console.log(this.clickCount);
}
button.addEventListener('click', clickHandler.bind(states));

注意:當使用顯示綁定時,若是第一個參數是null, undefined,則應用默認綁定規則。爲避免傳入null, undefined時錯誤的改變了全局值,最好建立一個空對象代替null, undefined

var ø = Object.create(null);

foo.call(ø);

2.2.4 new Binding(new綁定)

明白new的運做原理:

  1. 建立一個新對象;

  2. 對象連接到[[prototype]]上;

  3. this綁定到這個新對象上;

  4. 有顯式的return,返回return,不然返回這個新對象。

2.2.5 優先級

new > 顯示綁定(call,apply,bind) > 隱式綁定(方法調用) > 默認綁定(獨立函數調用)

function foo(sth) {
  this.b = sth;
  console.log("a:", this.a, "b:", this.b);
}

var a = "window";
var obj1 = {
  a: "obj1",
  foo: foo,
}

var obj2 = {
  a: "obj2",
  foo: foo,
}

obj1.foo("obj1"); //a: obj1 b: obj1
obj1.foo.call(obj2, "obj2"); //a: obj2 b: obj2; 顯示 > 隱式
var bar = foo.bind(obj1);
new bar("new"); //new > 顯示

4. 箭頭函數

箭頭函數中的this並不適用於以上四種規則。由於這裏的this不是使用的傳統this機制,而是使用的詞法做用域,根據外層的做用域來決定this。應用機制不同,該this也不能經過顯示綁定來修改。

5. 總結

下一次再看到this的時候,咱們問本身兩個問題:

  1. where to call: 函數的調用位置是?

  2. how to call: 函數的調用方法是?應用的規則是?

  3. 應用規則4條(按優先級排序):

    • new (新建立的對象)

    • 顯式綁定 (綁定到指定對象,call, apply, bind:可預測的this);

    • 隱式綁定 (調用的上下文對象,注意間接引用的錯誤);

    • 默認綁定 (全局對象或undefined注意函數體是否爲嚴格模式);

相關文章
相關標籤/搜索